Este documento fornece uma introdução sobre Internet das Coisas (IoT) e protocolos de comunicação, comparando MQTT e CoAP. Ele discute conceitos-chave de microcontroladores e apresenta exemplos de uso de MQTT em nuvem.
2. Conteúdo
1. IoT numa casca de noz
2. Introdução aos Microcontroladores
3. Breve revisão de protocolos de comunicação
4. Comparativo de sensores
5. Comparativo de atuadores
6. Estudo de Caso
7. Aplicação Real
8. Propostas de melhorias
9. Onde encontrar?
3. Quem sou eu?
Donato Azevedo Viana
Twitter: @donatoaz
LinkedIn: https://www.linkedin.com/in/donato-viana/
8. 2. Introdução a Microcontroladores
1. CPU 2. Memória 3. Periféricos
9. 1 - CPU - unidade de processamento
Operações aritméticas - ADDLW, SUBLW, INCF, etc
Atribui um valor a um registro ou a bits de um registro - MOVLW, CLRW, BSF, etc
Realiza operações booleanas - ANDLW, IORLW, XORLW, etc
Chama ou retorna de sub-rotinas - GOTO, CALL, RETFIE, etc
Não faz nada - NOP ;)
2. Introdução a Microcontroladores
10. 2 - Memória
Volátil - SRAM, DRAM
Não Volátil - EEPROM, Flash
(EEPROM = electrically erasable programmable
read-only memmory)
Antigamente:
ROM, PROM, EPROM
2. Introdução a Microcontroladores
11. 3 - Periféricos
Timers, counters, watchdog, geradores de PWM
Entradas e Saídas discretas e analógicas
Entradas e saídas seriais (I²C Bus, SPI Bus, UART, CAN-Bus, etc)
I²C = Inter-Integrated Circuit
SPI = Serial Peripheral Communication
UART = Universal Asynchronous Receiver Transceiver
CAN = Controller Area Network
2. Introdução a Microcontroladores
12. "Me dê uma CPU, uma memória e periféricos e
micro-controlarei o mundo"
-- Arquimedes
2. Introdução a Microcontroladores
13. (Sob minha perspectiva)
2001 - Curso técnico de Eletrônica (COLTEC UFMG)
PICs!
2. Introdução a Microcontroladores
17. Arduino (placa de desenvolvimento / diversão)
Ambiente de Desenvolvimento Simplificado!!
USB out-of-the-box!!
Programador integrado!!
Programável em C!! Comunidade!!
Código aberto!!
Much peripherals, very shields!!
2. Introdução a Microcontroladores
18. Enter the chinese!
Arduino clones… mais baratos, menos confiáveis.
Espressif!! ESP8266 ESP01 - baratinha com wifi
NodeMCU - Lua (É tetraaaaaaaaaaaaaaaaaa!!!11!!)
Wifi e Bluetooth BLE Integrados - ganhou meu coração.
2. Introdução a Microcontroladores
20. 2. Introdução a Microcontroladores
O ESP-01 vem com 512Kb de
flash.
É possível Ráqueá-lo para 4MB,
o que permite:
1. OTA (Over The Air updates)
2. Coisas de maior pegada
(mruby? TLS?)
21. ESP32 - "Evolução do ESP8266"
1. CPUS:
a. Dual core XTensa 32-bits, 240 MHZ
b. ULP co-processor -- deep sleep yaaaay
2. Memórias:
a. 448 kB de ROM para boot e funções core
b. • 520 kB de SRAM para dados e instruções
i. – 8 kB de SRAM em RTC, pode ser usada para dados; é acessada pela CPU principal
durante boot do modo deep sleep
2. Introdução a Microcontroladores
22. Periféricos:
12-bit SAR ADC up to 18 channels + 2 × 8-bit DACs
10 × touch sensors (capacitive sensing GPIOs)
Built in Temperature sensor
4 × SPI + 2 × I²S interfaces + 2 × I²C interfaces + 3 × UART
SD/SDIO/CE-ATA/MMC/eMMC host controller
SDIO/SPI slave controller
Ethernet MAC interface with dedicated DMA and IEEE 1588 Precision Time Protocol support
CAN bus 2.0
Infrared remote controller (TX/RX, up to 8 channels)
Motor PWM
LED PWM (up to 16 channels)
Hall effect sensor
Ultra low power analog pre-amplifier
2. Introdução a Microcontroladores
26. Existem diversos protocolos de comunicação.
Os fatores chave a serem considerados são:
1. Restrições do dispositivo embarcado
2. Requisitos de QoS - Quality of Service
3. Arquitetura
Outros fatores que podem ser avaliados:
1. É proprietário ou aberto?
2. É escalável (suporte a muitos dispositivos se comunicando concorrentemente)
3. Breve revisão de protocolos de comunicação
27. Existem diversas camadas de protocolos (Ye l OS ta ), alguns já são padrões
mais difundidos e aceitos, por exemplo, os protocolos de infraestrutura (IPv4/v6),
os protocolos de identificação (URI), os de transporte (WiFi, Bluetooth, LoRa)...
O foco aqui é discutir 2 protocolos de *comunicação de dados*, abertos,
existentes:
1. MQTT
2. CoAP
Lista compreensiva de protocolos:
https://www.postscapes.com/internet-of-things-protocols/
3. Breve revisão de protocolos de comunicação
28. Então vamos começar a falar do "problema":
Um dispositivo IoT coleta leituras de Temperatura através de um sensor, e
precisa enviar esta informação para uma aplicação web. O dispositivo também
possui um atuador que é comandado pela aplicação web (pelo usuário ou
automaticamente) conforme alguma regra pré-estabelecida.
3. Breve revisão de protocolos de comunicação
29. Situação 1:
O dispositivo pode enviar em intervalos predeterminados a temperatura coletada
para um servidor e paralelamente "perguntar" ao servidor se precisa atuar de
alguma maneira.
Desvantagens: Se a atuação for infrequente, gasta-se muito do recurso banda
com essas perguntas.
3. Breve revisão de protocolos de comunicação
30. Situação 2
O servidor pode ser o ente ativo, e periodicamente "perguntar" a temperatura, ou
comandar uma atuação.
Desvantagem: o dispositivo em campo precisa implementar um servidor e ficar
constantemente "ouvindo" por perguntas. Gasta-se muito com o recurso energia
desta forma.
3. Breve revisão de protocolos de comunicação
31. POLLING é quando algum dos entes precisa constantemente "perguntar /
comunicar" a outro(s) o seu estado.
Para dispositivos IoT, via-de-regra, realizar polling (com tamanho de mensagem
grande) é indesejável.
Mas e mensagem "pequena"…?
3. Breve revisão de protocolos de comunicação
32. MQTT Message Queueing Telemetry Transport
Protocolo recente, tipo Pub/Sub (Publish/Subscribe). Ele resolve o problema de
polling usando mensagens do tipo PINGREQ muito leves. A cada intervalo
predeterminado (parâmetro keepalive, em segundos) o assinante envia uma
mensagem PINGREQ para o árbitro para verificar se há alguma mensagem
destinada a ele. Se não há, o árbitro responde com outra mensagem do tipo
PINGREQ. Cada mensagem tem apenas 2 bytes, muito menos se comparado a
um protocolo convencional (26 bytes para HTTP - https://stackoverflow.com/a/25065027/119958).
3. Breve revisão de protocolos de comunicação
34. Conceitos importantes:
1. Tópico (topic)
2. Assinante (subscriber)
3. Produtor (publisher)
4. Árbitro (broker)
Arquitetura MQTT
3. Breve revisão de protocolos de comunicação
35. QoS 0 "Fire and Forget"
Melhor esforço. Tem apenas as garantias inerentes do protocolo de transmissão
(TCP). Cliente não se importa se o broker recebeu ou não. Dados pouco sensíveis.
3. Breve revisão de protocolos de comunicação
36. QoS 1 - "At least once"
Cliente espera que o árbitro confirme o recebimento com uma mensagem do tipo
PUBACK.
3. Breve revisão de protocolos de comunicação
37. QoS 1 em ação no Wireshark
3. Breve revisão de protocolos de comunicação
38. QoS 1 - atenção!
Se o cliente publica uma mensagem e não recebe um PUBACK em um tempo
determinado, ele reenvia -- normalmente este detalhe é transparente para nós
usuários.
Massss… não evita envios repetidos, porque às vezes o árbitro apenas realmente
demorou para confirmar o recebimento.
3. Breve revisão de protocolos de comunicação
39. QoS 2 - "At most once"
No máximo uma vez, porém é o mais lento, já que 4 mensagens são usadas.
3. Breve revisão de protocolos de comunicação
40. QoS 2 em ação
3. Breve revisão de protocolos de comunicação
41. Cliente que não suporta QoS2
Ao enviar uma mensagem com flag de QoS = 2, o árbitro responde com
uma mensagem Publish Received. Mas se o cliente não sabe interagir com
esta mensagem (e portanto não responde com uma mensagem de Publish
Release) o árbitro acha que o
cliente não recebeu a confirmação
e vai enviar incessantemente a
Mensagem Publish Received.
3. Breve revisão de protocolos de comunicação
42. Retenção de Mensagens
Árbitro mantém a última mensagem publicada retida, e os novos assinantes
sempre recebem esta mensagem ao se conectar.
3. Breve revisão de protocolos de comunicação
43. Vamos fazer um exemplo ao-vivo: MQTT Broker no google cloud
3. Breve revisão de protocolos de comunicação
44. CoAP - Constrained Application Protocol
Protocolo mais recente que o MQTT, muito semelhante ao REST (HTTP), porém
"emagrecido" para se adequar à realidade de dispositivos embarcados, que tem
restrições.
3. Breve revisão de protocolos de comunicação
45. Features CoAP
REST para IoT - parece que você está consumindo uma API (response object,
status code, content type… you name it!)
Integração transparente - uma aplicação não precisa nem saber que está
acessando um sensor, porque ela vai fazer um GET padrãozão.
RFC 7252 - projetado para durar décadas
Payload agnostic - assim como uma API, você pode transferir JSON, XML, Yaml,
etc
Discovery - o Tinder do IoT
Observe - subscribe do CoAP
3. Breve revisão de protocolos de comunicação
47. 3. Breve revisão de protocolos de comunicação
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 7: Message Format
VERSION (2 bits)
Por enquanto só
existe a 1
TYPE (2 bits)
Confirmable (0),
Non-confirmable (1),
Acknowledgement (2),
Reset (3)
TOKEN LENGTH (4
bits)
Indica o tamanho do
Token (0 a 8 bytes)
Código (8 bits: 3 MSB + 5
LSB)
Lê-se da forma: c.dd: c (0-7),
dd (0-31). Exemplos:
0.01 - GET request
2.00 - Response status OK
4.04 - Response Not Found
5.00 - Internal Server Error
Message ID: Usado
para evitar duplicações
e para associar
respostas de ACK/RST
←→ CON/NCON
Token: usado para associar
uma requisição a uma resposta
em casos de concorrência. Só
não vai existir quando a
implementação garantir que
não haverão requisições
Opções
Marcador de
Início de Carga
(Payload) 0xFF
51. Mas UDP não tem as garantias de entrega e ordem do TCP…
Verdade, mas o protocolo TCP é mais pesado e os especificadores do CoAP
entenderam que protocolos mais simples poderiam ser usados. CoAP especifica
um "Stop-and-Wait" [1] simplificado, com tentativas espaçadas exponencialmente
no tempo ("Exponential Backoff") [2].
[1] https://en.wikipedia.org/wiki/Stop-and-wait_ARQ
[2] https://en.wikipedia.org/wiki/Exponential_backoff
3. Breve revisão de protocolos de comunicação
52. 3. Breve revisão de protocolos de comunicação
Stop-and-wait
Exponential
Back Off
53. Características do sistema observado:
Variável: Temperatura
Dinâmica: Lenta (minutos ou até mesmo horas)
Elemento atuado: geladeira (on-off)
Atuação somente para resfriar: desvantagem: dinâmica de resfriamento bem
diferente da de aquecimento
4. Comparativo de sensores
57. Termopares
Vantagens:
Simples e relativamente baratos
Robustos e tem grande range de temperaturas (depende do tipo)
Desvantagens:
Não lineares, precisam de circuito externo para compensação (MAX6675 faz
compensação e digitalização com resolução de 12bits)
4. Comparativo de sensores
59. Esquema de conexão -- usa protocolo SPI (Serial Peripheral Interface) - 3 pinos.
OK para o ESP32, mas ruim para ESP8266-01…
4. Comparativo de sensores
64. Porém…
Não é a prova d'água out-of-the-box -- precisa de poço termométrico.
Precisa de 3 pinos do micro-controlador
Fiquei assustado com a possibilidade de erro de +/- 3oC
4. Comparativo de sensores
65. Sensor Integrado (I.C.) DS18b20
Vantagens:
Interface digital, baixo erro, usa apenas 1 pino (parasite power!)
Desvantagens:
(que não afetam o meu caso): baixo range -55oC a +125oC e baixo tempo de
resposta (em alguns casos mais de 10 segundos, devido ao encapsulamento a
prova d'água)
4. Comparativo de sensores
67. Exemplo de como o encapsulamento pode afetar (gráfico abaixo para LM35)
Encapsulamento a prova d'água do DS18b20
adiciona bastante tempo para estabilizar a leitura.
4. Comparativo de sensores
68. Ausência de Biblioteca One-Wire nativa para o ESP32? Sem problemas:
https://github.com/DavidAntliff/esp32-ds18b20 - versão multi-dispositivo
https://github.com/feelfreelinux/ds18b20 - versão simplificada para apenas 1
sensor
4. Comparativo de sensores
69. 5. Comparativo de Atuadores
Tipos de relé:
Eletro-mecânico
Estado Sólido
4. Comparativo de atuadores
71. Relés eletromecânicos cargas indutivas (ex: motor do compressor de uma
geladeira)
Arco voltáico - danifica os contatos do relé e diminui a vida útil.
5. Comparativo de atuadores
72. Mas e o diodo de "flyback"? Até funciona, mas nos relés chineses (baratos, do
aliexpress), ainda assim tive problemas com agarramento (sticking).
Sem diodo flyback Com diodo flyback
5. Comparativo de atuadores
73. Relé de estado sólido
Não possui partes mecânicas == não sofre de agarramento
Dependendo da carga, pode aquecer (e muito), se necessário deve-se utilizar um
dissipador + cooler
Isolamento físico (opto-acoplado)
É basicamente um opto-acoplador (circuito de acionamento) + um Triac (circuito
de carga)
5. Comparativo de atuadores
85. donato@porter ~/mosquitto $ cat config/mosquitto.conf
# Place your local configuration in /mqtt/config/conf.d/
pid_file /var/run/mosquitto.pid
persistence true
persistence_location /mqtt/data/
#user mosquitto
allow_anonymous true
port 1883
log_dest file /mqtt/log/mosquitto.log
log_dest stdout
log_type all
connection_messages true
log_timestamp true
include_dir /mqtt/config/conf.d
7. Projeto
86. Banco de Dados Postgresql
Docker Component
db:
image: postgres
environment:
POSTGRES_DB: cervejator
POSTGRES_USER: ****
POSTGRES_PASSWORD: ****
7. Projeto
87. Key-value Store - Redis DB
Docker Component
redis:
image: redis:3.2
7. Projeto
88. Assinante MQTT
É uma Rake Task (Roda em seu próprio processo, mas tem acesso ao ambiente
Rails da aplicação Web).
Contém um cliente MQTT que assina ao tópico: "sensor/#"
Para cada mensagem que recebe do tipo "sensor/#":
1. Cria tarefa para armazenar assíncronamente o dado -- não tem de ficar
bloqueado esperando uma operação de E/S
2. Faz um broadcast do valor recebido nos Canais Active Cable para stream em
tempo real no browser do cliente
7. Projeto
90. Background Jobs - Resque
Processamento Assíncrono de Tarefas:
1. Execução de escritas no banco de dados - PersistDataJob
class PersistDataJob < ApplicationJob
queue_as :readings
def perform(datum)
Datum.create(datum)
end
end
7. Projeto
91. Escalonador de malhas de controle
$ cat config/schedule.yml
development: &development
ScheduleControlLoopsJob:
description: 'Polls control loops'
queue: 'control_loops'
every:
- '20s'
test:
<<: *development
production:
<<: *development
class ScheduleControlLoopsJob < ApplicationJob
queue_as :control_loops
def perform
ControlLoop.all.each do |cl|
next unless cl.mode == "auto"
if Time.now >= cl.next_run
ControlLoopJob.perform_later(cl.id)
end
if cl.missed_deadline?
ActiveJob::Base.logger.error "Control Loop
##{cl.id}:#{cl.name} is missed deadline!"
end
end
end
end
7. Projeto
92. Background Jobs - Resque
Processamento Assíncrono
de Tarefas:
2. Execução das malhas de
controle com envio de
mensagem MQTTT
class ControlLoopJob < ApplicationJob
queue_as :run_control_loops
def perform(control_loop_id)
ctrl = ControlLoop.find(control_loop_id)
action = ctrl.run
return if action == :inactive
cli = MQTT::Client.connect(host: ENV['MQTT_BROKER_HOST'],
username: 'mosquitto',
port: ENV['MQTT_BROKER_PORT'])
cli.publish("actuator/#{ctrl.actuator.write_key}",
action,
false, 1)
cli.disconnect()
ctrl.update_attribute(:next_run,
Time.now + ctrl.sampling_rate.seconds)
end
end
7. Projeto
93. Stream de dados em
tempo real via
websockets usando
Action Cable
Server side (ruby):
class SensorChannel < ApplicationCable::Channel
def subscribed
sensor = Sensor.find(params[:id])
stream_for sensor,
coder: ActiveSupport::JSON do |message|
transmit message
end
end
end
class ActuatorChannel < ApplicationCable::Channel
def subscribed
actuator = Actuator.find(params[:id])
stream_for actuator, coder:
ActiveSupport::JSON do |message|
transmit message
end
end
end
7. Projeto
94. App.cable.subscriptions.create { channel: "SensorChannel", id: sensor_id },
received: (data) ->
@addDataToChart data
Stream de dados em
tempo real via
websockets usando
Action Cable
Client side (coffeescript):
7. Projeto
96. initialize_gpio
Configurando as entradas e
saídas do ESP32
void initialize_gpio(void)
{
gpio_pad_select_gpio(ACTUATOR_GPIO);
gpio_set_direction(ACTUATOR_GPIO,
GPIO_MODE_OUTPUT);
ds18b20_init(14);
}
GPIO = General Purpose Input/Output
7. Projeto
97. Esp_event_loop_init
Evita esperas bloqueadas, a
própria lib do ESP-IDF se
encarrega de criar uma task
que chama este callback
sempre que há um novo
evento.
static
esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id)
{
case SYSTEM_EVENT_STA_START:
ESP_LOGI(TAG, "System started, going to connect to wifi.");
wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "Got IP, conn to MQTT. %s : %d, user: %s, pass: %s",
MQTT_HOST, MQTT_PORT, MQTT_USER, MQTT_PASS);
esp_mqtt_start(MQTT_HOST, MQTT_PORT, "esp-mqtt", "", "");
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "WiFi lost, stop MQTTT and attempt a reconnect.");
esp_mqtt_stop();
esp_wifi_connect();
break;
default:
break;
}
return ESP_OK;
}
7. Projeto
98. typedef enum {
SYSTEM_EVENT_WIFI_READY = 0, /**< ESP32 WiFi ready */
SYSTEM_EVENT_SCAN_DONE, /**< ESP32 finish scanning AP */
SYSTEM_EVENT_STA_START, /**< ESP32 station start */
SYSTEM_EVENT_STA_STOP, /**< ESP32 station stop */
SYSTEM_EVENT_STA_CONNECTED, /**< ESP32 station connected to AP */
SYSTEM_EVENT_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */
SYSTEM_EVENT_STA_GOT_IP, /**< ESP32 station got IP from connected AP */
SYSTEM_EVENT_STA_LOST_IP, /**< ESP32 station lost IP and the IP is reset to 0 */
SYSTEM_EVENT_AP_STACONNECTED, /**< a station connected to ESP32 soft-AP */
SYSTEM_EVENT_AP_STADISCONNECTED, /**< a station disconnected from ESP32 soft-AP */
} system_event_id_t;
7. Projeto
99. Inicialização da lib MQTT
esp_mqtt_init(mqtt_status_cb, mqtt_message_cb, 256, 2000);
Status Call Back
Function
Chamado sempre
que o status
(conectado,
desconectado)
muda
Status Call Back Function
Chamado sempre que é
recebida uma mensagem
Buffer Size para
leitura/escrita de
payloads
Timeout para comandos
enviados ao árbitro (em
mS)
7. Projeto
100. Callback de
mensagens recebidas
(atuador)
static void
mqtt_message_cb(const char *topic, uint8_t *payload, size_t len)
{
printf("incomingt%s:%s (%d)n", topic, payload, (int)len);
if (strcmp((char *) payload,"on") == 0) {
ESP_LOGI(TAG, "Setting pin %d to high", ACTUATOR_GPIO);
gpio_set_level(ACTUATOR_GPIO, 1);
} else if (strcmp((char *) payload, "off") == 0) {
ESP_LOGI(TAG, "Setting pin %d to low", ACTUATOR_GPIO);
gpio_set_level(ACTUATOR_GPIO, 0);
} else {
ESP_LOGI(TAG, "Received an unknown actuator command");
}
}
7. Projeto
101. Callback de Status:
Se conectado,
começa a publicar.
Se desconectado,
para de publicar e
tenta conectar
novamente...
static void
mqtt_status_cb(esp_mqtt_status_t status)
{
switch (status)
{
case ESP_MQTT_STATUS_CONNECTED:
ESP_LOGI(TAG, "Cool, we got a CONNECT ACK from broker!");
esp_mqtt_subscribe(ACTUATOR_WRITE_KEY, 0);
xTaskCreatePinnedToCore(process, "process", 8192, NULL, 10, &task,
1);
break;
case ESP_MQTT_STATUS_DISCONNECTED:
ESP_LOGI(TAG, "Shoot, MQTT disconnected! Going to retry...
Stopping publishing task.");
vTaskDelete(task);
vTaskDelay(5000 / portTICK_PERIOD_MS);
esp_mqtt_start(MQTT_HOST, MQTT_PORT, "esp-mqtt", "", "");
break;
}
}
7. Projeto
105. 9. Propostas de Melhoria
Usar o EMQTT (Erlang) ao invés do Mosquitto
106. 9. Propostas de Melhoria
Usar CoAP, de modo que o aplicativo web seja mais passivo e os
dispositivos implementem e exponham o que quiserem (aplicativo web
vira um "Registry" --
https://www.monterail.com/blog/2016/iot-with-elixir-and-coap-part-1-example-on-how-to-easily-
prototype-and-build-an-iot-platform
108. Usar AnyCable ao invés de action cable
https://evilmartians.com/chronicles/anycable-actioncable-on-steroids
https://github.com/anycable/anycable-go
9. Propostas de Melhoria
109. 10. Onde Encontrar?
with 'github.com/donatoaz' do
esp8266-esp01-MAX6675 - conectando termopar ao esp8266
cervejator - aplicação dockerizada apresentada
Esp32-mqtt-cervejator - cliente mqtt para o esp32
end
@donatoaz (twitter)
113. Lib MQTT para ESP32
https://github.com/tuanpmt/esp_mqtt
https://github.com/256dpi/esp-mqtt
114. Docker cheatsheet
$ docker-compose up
-- o BD ainda não existe, então:
$ docker-compose run web bundle exec rake db:create db:migrate db:seed
-- Por algum motivo, o yarn install não instalou as paradas, então:
$ docker-compose run web yarn add jquery jquery-ujs amcharts lodash
-- Os ativos estáticos também não existem ainda, então:
$ docker-compose run web bundle exec rake assets:precompile