Os sistemas de automação residencial, como costumam ser chamados de “casa inteligente”, eram terrivelmente caros e apenas pessoas ricas podiam pagar por eles. Hoje, no mercado, você encontra kits bastante baratos com sensores, botões / interruptores e atuadores para controlar a iluminação, tomadas, ventilação, fornecimento de água e outros consumidores. E mesmo o DIY-shnik
mais krivoruky pode se juntar aos belos e coletar dispositivos para uma casa inteligente e barata.

Como regra, os dispositivos propostos são sensores ou atuadores. Eles facilitam a implementação de cenários como "acender a luz quando o sensor de movimento é acionado" ou "o interruptor na saída apaga a luz em todo o apartamento". Mas a telemetria de alguma forma não deu certo. Na melhor das hipóteses, este é um gráfico de temperatura e umidade ou potência instantânea em uma tomada específica.
Recentemente, instalei um hidrômetro com uma saída de pulso. Um interruptor de palheta é acionado através de cada litro através do medidor e fecha o contato. A única coisa que resta é agarrar-se aos fios e tentar obter lucro com isso. Por exemplo, analise o consumo de água por hora e dia da semana. Bem, se houver vários risers de água no apartamento, é mais conveniente ver todos os indicadores atuais em uma tela do que escalar nichos de difícil acesso com uma lanterna.
Sob o corte, minha versão do dispositivo é baseada no ESP8266, que conta pulsos de hidrômetros e envia leituras para o servidor doméstico inteligente via MQTT. Vamos programar em micropython usando a biblioteca uasyncio. Ao criar o firmware, me deparei com várias dificuldades interessantes, que também serão discutidas neste artigo. Vamos lá!
Esquema

O coração de todo o circuito é o módulo no microcontrolador ESP8266. O ESP-12 foi originalmente planejado, mas o meu acabou com defeito. Eu precisava me contentar com o módulo ESP-07, que estava disponível. Felizmente, eles são os mesmos nas conclusões e na funcionalidade, a diferença está apenas na antena - o ESP-12 o possui e o ESP-07 possui um externo. No entanto, mesmo sem uma antena WiFi, o sinal no meu banheiro é capturado normalmente.
A ligação do módulo é padrão:
- botão de reset com um suspensor e um capacitor (embora ambos já estejam dentro do módulo)
- Ativar sinal (CH_PD) puxado para energia
- GPIO15 puxado para o chão. Isso é necessário apenas no início, mas ainda não tenho mais nada a que me agarrar a essa perna
Para colocar o módulo no modo de firmware, você precisa fechar o GPIO2 e aterrá-lo e, para torná-lo mais conveniente, forneci o botão Boot. Em condições normais, este pino é puxado para a energia.
O status da linha GPIO2 é verificado apenas no início do trabalho - quando a energia é aplicada ou imediatamente após uma redefinição. Portanto, o módulo carrega como de costume ou entra no modo de firmware. Após o carregamento, essa saída pode ser usada como um GPIO comum. Bem, como já existe um botão lá, você pode colocar alguma função útil nele.
Para programação e depuração, usarei o UART, que trouxe para o pente. Quando necessário - basta conectar um adaptador USB-UART. Você só precisa se lembrar que o módulo é alimentado por 3.3V. Se você esquecer de mudar o adaptador para essa tensão e aplicar 5V, o módulo provavelmente queimará.
Não tenho problemas com eletricidade no banheiro - a tomada está localizada a cerca de um metro dos medidores, por isso vou alimentá-lo a partir de 220V. Como fonte de energia, vou trabalhar com um pequeno
bloco de HLK-PM03 da Tenstar Robot. Pessoalmente, sou forte com eletrônicos analógicos e de energia, mas aqui está uma fonte de alimentação pronta em um estojo pequeno.
Para sinalizar os modos de operação, forneci um LED conectado ao GPIO2. No entanto, não comecei a soldá-lo, porque além disso, o módulo ESP-07 possui um LED conectado ao mesmo GPIO2. Mas deixe isso acontecer - de repente, quero trazer esse LED para o gabinete.
Passamos para o mais interessante. Os contadores de água não têm lógica, não podem ser solicitadas as leituras atuais. A única coisa que está disponível para nós são pulsos - fechando os contatos da palheta a cada litro. As conclusões dos comutadores reed estão definidas no GPIO12 / GPIO13. Vou ligar o resistor pull-up programaticamente dentro do módulo.
Inicialmente, esqueci de fornecer os resistores R8 e R9 e, na minha versão do quadro, eles não são. Mas como eu já estou colocando o esquema em exibição pública, vale a pena corrigir essa supervisão. Os resistores são necessários para não queimar a porta se o firmware estiver com defeito e colocar a unidade no pino, e o interruptor reed colocar esta linha no chão (no máximo, 3.3V / 1000Ohm = 3.3mA fluirá com o resistor).
É hora de pensar no que fazer se a eletricidade acabar. A primeira opção é solicitar ao servidor os contadores iniciais no início. Mas isso exigiria uma complicação significativa do protocolo de troca. Além disso, a operacionalidade do dispositivo, neste caso, depende do status do servidor. Se, depois de desligar a luz, o servidor não iniciar (ou iniciar mais tarde), o hidrômetro não poderá solicitar os valores iniciais e funcionará incorretamente.
Portanto, decidi implementar o armazenamento de valores de contador em um chip de memória conectado via I2C. Não tenho requisitos especiais para o tamanho da memória flash - preciso salvar apenas 2 números (o número de litros por medidores de água quente e fria). Até o menor módulo serve. Mas no número de ciclos de gravação, você precisa prestar atenção. Para a maioria dos módulos, são 100 mil ciclos, para alguns até um milhão.
Parece que um milhão é muito. Mas, durante 4 anos morando no meu apartamento, consumi pouco mais de 500 metros cúbicos de água, ou seja, 500 mil litros! E 500 mil entradas no flash. E isso é apenas água fria. Você pode, é claro, soldar o chip a cada dois anos, mas acontece que existem chips FRAM. Do ponto de vista da programação, é o mesmo I2C EEPROM, mas com um número muito grande de ciclos de reescrita (centenas de milhões). Isso é apenas até que tudo chegue à loja com esses chips de qualquer maneira, então por enquanto o 24LC512 usual permanecerá.
Placa de circuito
Inicialmente, planejei fazer uma taxa em casa. Portanto, o quadro foi projetado como unilateral. Mas, depois de uma longa hora com um ferro a laser e uma máscara de solda (sem ela de alguma forma vir il il), eu ainda decidi pedir placas aos chineses.

Quase antes de encomendar a placa, percebi que, além do chip de memória flash no barramento I2C, você pode pegar outra coisa útil, como uma tela. O que exatamente produzir para ele ainda é uma pergunta, mas você precisa criar no quadro. Bem, desde que eu encomendaria as placas na fábrica, não fazia sentido me limitar a uma placa de um lado, então as linhas no I2C são as únicas na parte de trás da placa.
Um batente grande também foi associado à fiação unilateral. Porque a placa foi desenhada de um lado, as faixas e os componentes SMD foram planejados para serem colocados de um lado e os componentes de saída, conectores e a fonte de alimentação do outro. Quando recebi as placas em um mês, esqueci o plano inicial e descompactei todos os componentes na parte frontal. E somente quando se tratava de soldar a fonte de alimentação, descobriu-se que o mais e o menos eram divorciados pelo contrário. Eu tive que fazer fazendas coletivas com jumpers. Na figura acima, eu já mudei a fiação, mas o solo é jogado de uma parte da placa para outra através das saídas do botão Boot (embora seja possível desenhar uma trilha na segunda camada).
Acabou assim

Habitação
O próximo passo é a caixa. Com uma impressora 3D, isso não é um problema. Não me incomodei muito - apenas desenhei uma caixa do tamanho certo e fiz recortes nos lugares certos. A tampa está presa ao corpo em pequenos parafusos.

Eu já mencionei que o botão Boot pode ser usado como um botão de uso geral - aqui nós o trazemos para o painel frontal. Para fazer isso, desenhei um "poço" especial onde o botão mora.

Dentro do gabinete, também existem tocos nos quais a placa é instalada e fixada com um único parafuso M3 (não havia mais espaço na placa)
A exibição foi selecionada quando imprimi a primeira versão de encaixe do estojo. O padrão de duas linhas não se encaixava nesse caso, mas um display OLED SSD1306 128x32 foi encontrado no cardan. É pequeno, mas não preciso olhar para ele todos os dias - ele vai rolar.
Estimando o caminho e como os fios serão colocados a partir dele, decidi colocar a tela no meio do gabinete. Ergonomia, é claro, abaixo do rodapé - um botão na parte superior, uma exibição na parte inferior. Mas eu já disse que a idéia de estragar a tela veio tarde demais e era muito preguiçoso reorganizar a placa para mover o botão.
O dispositivo está completo. O módulo de exibição é colado aos bicos de fusão a quente


O resultado final pode ser visto no KDPV
Firmware
Vamos para a parte do software. Para trabalhos tão pequenos, gosto muito de usar a linguagem Python (
micropython ) - o código é muito compacto e compreensível. Felizmente, não há necessidade de diminuir o nível de registros para comprimir microssegundos - tudo pode ser feito a partir de python.
Parece ser tudo simples, mas não muito - várias funções independentes estão descritas no dispositivo:
- O usuário aperta um botão e olha para o visor
- Litros marcam e atualizam valores na memória flash
- O módulo monitora o sinal WiFi e reconecta, se necessário
- Bem, sem uma luz piscando, você não consegue fazer nada
É impossível supor que uma função não funcione se a outra for estúpida por algum motivo. Já comi cactos em outros projetos e agora vejo falhas no estilo de “perdi outro litro, porque naquele momento a tela foi atualizada” ou “o usuário não pode fazer nada enquanto o módulo se conecta ao WiFi”. Obviamente, algumas coisas podem ser feitas através de interrupções, mas você pode ter uma limitação na duração, aninhamento de chamadas ou alteração não atômica de variáveis. Bem, o código que faz tudo e imediatamente se transforma rapidamente em uma bagunça.
Em
um projeto mais sério, usei a multitarefa preemptiva clássica e o FreeRTOS, mas, neste caso, o modelo de
corotinas e a biblioteca uasync mostraram-se muito mais adequados. Além disso, a implementação Pitonovskiy do corutin é apenas uma bomba - para o programador, tudo foi feito de forma simples e conveniente. Apenas escreva sua própria lógica, diga-me em quais lugares você pode alternar entre threads.
Sugiro explorar as diferenças entre crowding out e multitarefa competitiva opcionalmente. Agora, vamos finalmente avançar para o código.
Cada contador é processado por uma instância da classe Counter. Primeiro, o valor inicial do contador é subtraído da EEPROM (value_storage) - é assim que a recuperação da falta de energia é implementada.
O pino é inicializado com um pull-up integrado à energia: se o interruptor reed estiver fechado, será zero na linha, se a linha estiver aberta, a linha será puxada para a energia e o controlador lerá um.
Além disso, uma tarefa separada é iniciada aqui, que fará a pesquisa do pino. Cada contador executará sua própria tarefa. Aqui está o código dela
""" Poll pin and advance value when another litre passed """ async def _switchcheck(self): last_checked_pin_state = self._pin.value()
É necessário um atraso de 25ms para filtrar a rejeição do contato e, ao mesmo tempo, regula a frequência com que a tarefa é ativada (enquanto esta tarefa está em suspensão, outras tarefas funcionam). A cada 25ms, a função é ativada, verifica o pino e, se os contatos do interruptor reed estão fechados, outro litro passa pelo contador e isso precisa ser processado.
def _another_litre_passed(self): self._value += 1 self._value_changed = True self._value_storage.write(self._value)
Processar o próximo litro é trivial - o contador simplesmente aumenta. Bem, seria bom escrever um novo valor em uma unidade flash.
Para facilitar o uso, são fornecidos "acessadores".
def value(self): self._value_changed = False return self._value def set_value(self, value): self._value = value self._value_changed = False
Bem, agora vamos aproveitar os prazeres do python e da biblioteca uasync e tornar o objeto contador esperável (como isso pode ser traduzido para o russo? O que é esperado?)
def __await__(self): while not self._value_changed: yield from asyncio.sleep(0) return self.value() __iter__ = __await__
Essa é uma função tão conveniente que aguarda até que o valor do contador seja atualizado - a função é ativada periodicamente e verifica o sinalizador _value_changed. A piada dessa função é que o código de chamada pode adormecer em uma chamada para essa função e dormir até que um novo valor seja recebido.
Mas e as interrupções?Sim, neste lugar você pode me enganar, dizendo que ele mesmo falou sobre interrupções, mas na verdade ele organizou uma pesquisa estúpida do alfinete. De fato, interrupções são a primeira coisa que tentei. No ESP8266, você pode organizar uma interrupção na borda e até escrever um manipulador para essa interrupção em python. Nesta interrupção, você pode atualizar o valor de uma variável. Provavelmente, isso seria suficiente se o contador fosse um dispositivo escravo - um que aguarde até que seja solicitado esse valor.
Infelizmente (ou felizmente?) Meu dispositivo está ativo, ele deve enviar mensagens usando o protocolo MQTT e gravar dados na EEPROM. E aqui estão as restrições - você não pode alocar memória e usar uma pilha grande em interrupções, o que significa que você pode esquecer o envio de mensagens pela rede. Existem pãezinhos como micropython.schedule () que permitem executar algum tipo de função "o mais rápido possível", mas a pergunta é "qual é o sentido?". De repente, estamos enviando algum tipo de mensagem agora, e aqui a interrupção muda e estraga os valores das variáveis. Ou, por exemplo, um novo valor de contador chegou do servidor enquanto ainda não anotamos o antigo. Em geral, você precisa cercar a sincronização ou sair de alguma maneira diferente.
E, de tempos em tempos, o RuntimeError falha: agende a pilha cheia e quem sabe por quê?
Com uma pesquisa explícita e uasync, nesse caso, é de alguma forma mais bonita e mais confiável.
Comecei a trabalhar com a EEPROM em uma turma pequena
class EEPROM(): i2c_addr = const(80) def __init__(self, i2c): self.i2c = i2c self.i2c_buf = bytearray(4)
Em python, trabalhar com bytes diretamente é difícil, mas são bytes gravados na memória. Eu tive que corrigir a conversão entre números inteiros e bytes usando a biblioteca ustruct.
Para não transferir o objeto I2C e o endereço da célula de memória de cada vez, envolvi tudo em um clássico pequeno e conveniente
class EEPROMValue(): def __init__(self, i2c, eeprom_addr): self._eeprom = EEPROM(i2c) self._eeprom_addr = eeprom_addr def read(self): return self._eeprom.read(self._eeprom_addr) def write(self, value): self._eeprom.write(self._eeprom_addr, value)
O próprio objeto I2C é criado com esses parâmetros
i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4))
Abordamos a coisa mais interessante - a implementação da comunicação com o servidor via MQTT. Bem, o próprio protocolo não precisa ser implementado - uma
implementação assíncrona pronta foi encontrada na Internet. Aqui vamos usá-lo.
Tudo o mais interessante é coletado na classe CounterMQTTClient, que é baseada na biblioteca MQTTClient. Vamos começar da periferia
Aqui, são criados e configurados pinos de lâmpadas e botões, além de objetos de medidores de água fria e quente.
Com a inicialização, nem tudo é tão trivial
def __init__(self): self.internet_outage = True self.internet_outages = 0 self.internet_outage_start = ticks_ms() with open("config.txt") as config_file: config['ssid'] = config_file.readline().rstrip() config['wifi_pw'] = config_file.readline().rstrip() config['server'] = config_file.readline().rstrip() config['client_id'] = config_file.readline().rstrip() self._mqtt_cold_water_theme = config_file.readline().rstrip() self._mqtt_hot_water_theme = config_file.readline().rstrip() self._mqtt_debug_water_theme = config_file.readline().rstrip() config['subs_cb'] = self.mqtt_msg_handler config['wifi_coro'] = self.wifi_connection_handler config['connect_coro'] = self.mqtt_connection_handler config['clean'] = False config['clean_init'] = False super().__init__(config) loop = asyncio.get_event_loop() loop.create_task(self._heartbeat()) loop.create_task(self._counter_coro(self.cold_counter, self._mqtt_cold_water_theme)) loop.create_task(self._counter_coro(self.hot_counter, self._mqtt_hot_water_theme)) loop.create_task(self._display_coro())
Para definir os parâmetros de operação da biblioteca mqtt_as, um dicionário grande de diferentes configurações é usado - config. A maioria das configurações padrão nos convém, mas muitas configurações precisam ser definidas explicitamente. Para não registrar as configurações diretamente no código, eu as guardo no arquivo de texto config.txt. Isso permite alterar o código, independentemente das configurações, além de rebitar vários dispositivos idênticos com parâmetros diferentes.
O último bloco de código executa várias corotinas para atender a várias funções do sistema. Aqui está um exemplo de corotina que serve contadores
async def _counter_coro(self, counter, topic):
Em um ciclo, Corutin aguarda um novo valor de contador e, assim que aparece, envia uma mensagem usando o protocolo MQTT. O primeiro trecho de código envia o valor inicial, mesmo que a água não flua através do contador.
A classe base MQTTClient serve a si mesma, inicia uma conexão WiFi e reconecta quando a conexão é perdida. Quando o status da conexão WiFi muda, a biblioteca informa-nos chamando wifi_connection_handler
async def wifi_connection_handler(self, state): self.internet_outage = not state if state: self.dprint('WiFi is up.') duration = ticks_diff(ticks_ms(), self.internet_outage_start) // 1000 await self.publish_debug_msg('ReconnectedAfter', duration) else: self.internet_outages += 1 self.internet_outage_start = ticks_ms() self.dprint('WiFi is down.') await asyncio.sleep(0)
A função é honestamente lambida dos exemplos. Nesse caso, considera o número de desconexões (internet_outages) e sua duração. Quando uma conexão é restaurada, o tempo de inatividade é enviado ao servidor.
A propósito, o último sono é necessário apenas para que a função se torne assíncrona - na biblioteca é chamada via aguardar, e somente as funções no corpo das quais há outro aguardam podem ser chamadas.
Além de conectar-se ao WiFi, você também precisa estabelecer uma conexão com o broker MQTT (servidor). A biblioteca também faz isso, mas temos a oportunidade de fazer algo útil quando a conexão é estabelecida.
async def mqtt_connection_handler(self, client): await client.subscribe(self._mqtt_cold_water_theme) await client.subscribe(self._mqtt_hot_water_theme)
Aqui, assinamos várias mensagens - o servidor agora tem a capacidade de definir os valores atuais do contador enviando a mensagem apropriada.
def mqtt_msg_handler(self, topic, msg): topicstr = str(topic, 'utf8') self.dprint("Received MQTT message topic={}, msg={}".format(topicstr, msg)) if topicstr == self._mqtt_cold_water_theme: self.cold_counter.set_value(int(msg)) if topicstr == self._mqtt_hot_water_theme: self.hot_counter.set_value(int(msg))
Esta função processa as mensagens recebidas e, dependendo do tópico (nome da mensagem), os valores de um dos contadores são atualizados
Algumas funções auxiliares
Esta função envia mensagens se uma conexão for estabelecida. Se não houver conexão, a mensagem será ignorada.
E esta é apenas uma função conveniente que gera e envia mensagens de depuração.
async def publish_debug_msg(self, subtopic, msg): await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg))
Muito texto, mas não piscamos um LED. Aqui
Eu forneci 2 modos de piscar. Se a conexão for perdida (ou está sendo estabelecida), o dispositivo piscará rapidamente. Se a conexão for estabelecida, o dispositivo pisca uma vez a cada 5 segundos. Se necessário, aqui você pode implementar outros modos de piscar.
Mas o LED é tão mimos. Ainda acenamos para a tela.
async def _display_coro(self): display = SSD1306_I2C(128,32, i2c) while True: display.poweron() display.fill(0) display.text("COLD: {:.3f}".format(self.cold_counter.value() / 1000), 16, 4) display.text("HOT: {:.3f}".format(self.hot_counter.value() / 1000), 16, 20) display.show() await asyncio.sleep(3) display.poweroff() while self.button(): await asyncio.sleep_ms(20)
É sobre isso que eu falei - quão simples e conveniente com as corotinas. Esta pequena função descreve TODA a interação do usuário. Corutin apenas espera que um botão seja pressionado e liga o visor por 3 segundos. O visor mostra as leituras atuais do medidor.
Ainda existem algumas pequenas coisas. Aqui está a função que executa o farm inteiro (re). O ciclo principal lida apenas com o envio de várias informações de depuração uma vez por minuto. Em geral, cito como é - especialmente o comentário, eu acho, não é necessário
async def main(self): while True: try: await self._connect_to_WiFi() await self._run_main_loop() except Exception as e: self.dprint('Global communication failure: ', e) await asyncio.sleep(20) async def _connect_to_WiFi(self): self.dprint('Connecting to WiFi and MQTT') sta_if = network.WLAN(network.STA_IF) sta_if.connect(config['ssid'], config['wifi_pw']) conn = False while not conn: await self.connect() conn = True self.dprint('Connected!') self.internet_outage = False async def _run_main_loop(self):
Bem, mais algumas configurações e constantes para completar a descrição
Começa todo esse caminho
client = CounterMQTTClient() loop = asyncio.get_event_loop() loop.run_until_complete(client.main())
Algo com a minha memória se tornou
Então, todo o código está lá. Carreguei os arquivos usando o utilitário ampy - ele permite que eles sejam carregados na unidade flash interna (a que está no próprio ESP-07) e depois acessados do programa como arquivos normais. Lá, eu enviei as bibliotecas mqtt_as, uasyncio, ssd1306 e coleções (usadas dentro de mqtt_as) que eu uso.
Começamos e ... Recebemos MemoryError. Além disso, quanto mais eu tentava entender exatamente onde a memória estava vazando, mais eu organizava a depuração de impressões, mais cedo esse erro ocorria. Um pequeno gugelezh me levou a entender que, em princípio, o microcontrolador tem apenas 30kb de memória, no qual 65kb de código (junto com bibliotecas) não se encaixam de forma alguma.
Mas existe um caminho. Acontece que o micropython não executa o código diretamente do arquivo .py - esse arquivo é compilado primeiro. E compila diretamente no microcontrolador, transforma-se em bytecode, que é então armazenado na memória. Bem, para o compilador funcionar, você também precisa de uma certa quantidade de RAM.
O truque é salvar o microcontrolador da compilação que consome muitos recursos. Você pode compilar arquivos em um computador grande e preencher o código de código finalizado no microcontrolador. Para fazer isso, faça o download do firmware micropython e crie o
utilitário mpy-cross .
Não escrevi um Makefile, mas passei manualmente e compilei todos os arquivos necessários (incluindo bibliotecas) como este
mpy-cross water_counter.py
Resta apenas fazer upload de arquivos com a extensão .mpy, sem esquecer de excluir o .py correspondente do sistema de arquivos do dispositivo.
Realizei todo o desenvolvimento do programa (IDE?) ESPlorer. Ele permite que você envie scripts para o microcontrolador e os execute imediatamente. No meu caso, toda a lógica e criação de todos os objetos estão localizadas no arquivo water_counter.py (.mpy). Mas, para que tudo isso inicie automaticamente, no início deve haver outro arquivo chamado main.py. E deve ser exatamente .py, e não um .mpy pré-compilado. Aqui está o seu conteúdo trivial
import water_counter
Começamos - tudo funciona. Mas há perigosamente pouca memória livre - cerca de 1kb. Ainda tenho planos de expandir a funcionalidade do dispositivo, e esse kilobyte obviamente não será suficiente para mim. Mas acabou que existe uma saída para este caso.
Aqui está a coisa. Embora os arquivos sejam compilados no código de bytes e estejam localizados no sistema de arquivos interno, na verdade eles ainda são carregados na RAM e executados a partir daí. Mas o micropython é capaz de executar o bytecode diretamente da memória flash, mas para isso você precisa incorporá-lo diretamente no firmware. Isso não é difícil, embora tenha levado um tempo decente no meu netbook (somente lá eu tenho o Linux).
O algoritmo é o seguinte:
- Baixe e instale o ESP Open SDK . Essa coisa cria um compilador e bibliotecas para programas no ESP8266. É montado de acordo com as instruções na página principal do projeto (eu escolhi a configuração STANDALONE = yes)
- Baixar Micropython Sorts
- Solte as bibliotecas necessárias nas portas / esp8266 / modules dentro da árvore do micropython
- Montamos o firmware de acordo com as instruções no arquivo ports / esp8266 / README.md
- Coloque o firmware no microcontrolador (eu faço isso no Windows com programas ESP8266Flasher ou Python esptool)
É isso, agora 'import ssd1306' aumentará o código diretamente do firmware e a RAM não será gasta para isso. Com esse truque, baixei apenas o código da biblioteca no firmware, enquanto o código principal do programa é executado no sistema de arquivos. Isso permite que você modifique facilmente o programa sem recompilar o firmware. No momento, tenho cerca de 8,5kb de RAM livre. Isso permitirá implementar muitas funcionalidades úteis diferentes no futuro. Bem, se não houver memória suficiente, você também pode enviar o programa principal para o firmware.
E o que fazer com isso agora?
Ok, o hardware está soldado, o firmware está escrito, a caixa está impressa, o dispositivo está preso na parede e pisca alegremente com uma lâmpada. Mas enquanto tudo isso é uma caixa preta (no sentido literal e figurativo) e o sentido dela ainda não é suficiente. É hora de fazer algo com as mensagens MQTT enviadas ao servidor.
Minha “casa inteligente” está girando no
sistema Majordomo . O módulo MQTT está pronto para uso ou pode ser facilmente instalado no mercado de complementos - não me lembro mais de onde ele veio. A coisa do MQTT não é auto-suficiente - o chamado broker - um servidor que recebe, classifica e redireciona mensagens MQTT para clientes. Eu uso mosquito, que (como o majordomo) roda no mesmo netbook.
Após o dispositivo enviar uma mensagem pelo menos uma vez, o valor aparecerá imediatamente na lista.

Esses valores agora podem ser associados a objetos do sistema, podem ser usados em scripts de automação e submetidos a várias análises - tudo isso está fora do escopo deste artigo. Quem está interessado no sistema majordomo, posso recomendar
o canal Electronics In Lens - um amigo também constrói uma casa inteligente e fala de maneira inteligente sobre a instalação do sistema.
Vou mostrar apenas alguns gráficos. Este é um gráfico simples de valores diários.

Pode-se ver que quase ninguém usava água à noite. Algumas vezes alguém foi ao banheiro, e parece que um filtro de osmose reversa suga alguns litros por noite. De manhã, o consumo aumenta significativamente. Normalmente, uso água de uma caldeira, mas depois queria tomar um banho e mudar temporariamente para a água quente da cidade - isso também é claramente visível no gráfico inferior.
A partir dessa programação, aprendi que ir ao banheiro é de 6 a 7 litros de água, tomar banho - 20 a 30 litros, lavar a louça cerca de 20 litros e, para tomar um banho, você precisa de 160 litros. Por um dia, minha família consome algo em torno de 500-600l.
Para os mais curiosos, você pode ver as entradas para cada valor individual

A partir daqui, aprendi que, com a torneira aberta, a água flui a uma velocidade de cerca de 1 litro em 5 segundos.
Mas, dessa forma, as estatísticas provavelmente não são muito convenientes de se assistir. No majordomo ainda há a oportunidade de ver gráficos de consumo por dia, semana e mês. Por exemplo, um gráfico de consumo em colunas

Até agora, só tenho dados por uma semana. Em um mês, esse gráfico será mais indicativo - uma coluna separada corresponderá a cada dia. Os ajustes nos valores inseridos manualmente prejudicam um pouco a imagem (a maior coluna). E ainda não está claro se defini incorretamente os primeiros valores com quase um cubo a menos, ou se isso é um bug no firmware e nem todos os litros foram para a compensação. Leva mais tempo.
Ainda é necessário conjurar sobre os próprios gráficos, embranquecer, cores. Talvez eu também construa um gráfico do consumo de memória para fins de depuração - de repente algo vaza por aí. Talvez eu exiba de alguma forma períodos em que a Internet não estava disponível. Enquanto tudo isso está girando no nível das idéias.
Conclusão
Hoje, meu apartamento ficou um pouco mais inteligente. Com um dispositivo tão pequeno, será mais conveniente para mim monitorar o consumo de água em casa. Se antes eu estava indignado "novamente eles consumiram muita água em um mês", agora posso encontrar a fonte desse consumo.
Parece estranho para alguém assistir as leituras na tela se estiver a um metro de distância do próprio medidor. Mas em um futuro não muito distante, pretendo mudar para outro apartamento, onde haverá vários risers, e os medidores provavelmente estarão localizados no patamar. Portanto, um dispositivo de leitura remota seria muito útil.
Também pretendo expandir a funcionalidade do dispositivo. Eu já estou olhando para válvulas motorizadas. Agora, para trocar a água da cidade das caldeiras, preciso abrir 3 torneiras em um nicho inacessível. Seria muito mais conveniente fazer isso com um botão com a indicação apropriada. Bem, é claro, vale a pena implementar proteção contra vazamentos.
No artigo, contei a minha versão do dispositivo com base no ESP8266. Na minha opinião, recebi uma versão muito interessante do firmware micropython usando a coroutine - simples e bonita.
Tentei descrever as muitas nuances e escolas que encontrei na campanha. Talvez eu tenha descrito tudo com muitos detalhes; para mim, pessoalmente, como leitor, é mais fácil desperdiçar muito do que pensar no que não foi dito.Como sempre, estou aberto a críticas construtivas. Modelo de Gabinete de Circuito e Placa deCódigo Fonte