Quero compartilhar minha experiência no desenvolvimento de um dispositivo inteligente. Nesta publicação, descreverei o hardware (brevemente) e o software (em mais detalhes).
O controlador é projetado para analisar as leituras dos sensores (com e sem fio) e manter a temperatura definida (incluindo o cronograma, incluindo os dias da semana) em cada zona individual, ligando / desligando a caldeira e controlando os circuitos de aquecimento do piso de água usando as cabeças térmicas no coletor.
Hardware
Como controlador, ESP8266 (WeMos D1 mini) foi selecionado, como tem wifi a bordo. Em vez do ESP8266, qualquer outro controlador ou microcomputador pode ser usado - as idéias gerais permanecerão inalteradas, o principal é que um servidor WEB com suporte para soquetes WEB possa ser implantado no sistema selecionado. Os seguintes itens também foram utilizados no projeto:
- RTC: DS3231 - é necessário determinar o dia da semana e a hora atual. O projeto foi concebido como um dispositivo independente que pode funcionar sem a Internet; portanto, o NTP não é adequado.
- Sensores de temperatura sem fio: NoName, 433MHz, da estação meteorológica chinesa - uma solução pronta para usar, eles funcionam com baterias. Do que mais você precisa? Mas é necessário que o período de transferência de dados não seja fixo. O problema é que o período de transmissão é de 35 segundos e não nada muito. E há situações em que os sinais de dois sensores se sobrepõem. Nesse caso, um ou dois sensores ficam fora do sistema por um tempo. O problema pode ser resolvido usando sensores semelhantes, nos quais a troca de canal também altera o período de transmissão de dados.
- Receptor 433MHz: Rxb6 - Revisões da Internet e experiência pessoal não são um receptor ruim.
- Sensores de temperatura com fio: DS18B20 - muito conveniente, pois você não precisa conhecer o número de sensores com antecedência.
- Mestre do barramento 1Wire: DS2482-100 - O protocolo 1Wire é muito sensível aos tempos, portanto, todas as implementações do mestre do barramento de programa usam atraso, o que não é muito bom para multitarefa. Usando esse chip, você pode tirar proveito do barramento 1Wire e se livrar de suas deficiências transmitindo 1Wire <-> i2c. O protocolo i2c possui uma linha de sincronização, devido à qual não é crítico para horários e é frequentemente implementado em hardware nos controladores.
- Temporizador de vigilância: TPL5000DGST - o tempo de atividade contínuo não é tão importante para este projeto, no entanto, a acessibilidade é muito importante. O ESP8266 possui um timer de monitoramento interno. Mas, como a prática demonstrou, algumas vezes ainda existem situações em que ela não consegue lidar e o sistema congela. Um cronômetro externo de vigilância de hardware foi projetado para lidar com situações de emergência. Configurado para um atraso de 64 segundos. Anexado à perna TX - durante a operação, o sistema grava constantemente informações de depuração no Serial e a falta de atividade por mais de um minuto indica um travamento do sistema.
- Expansor de porta: 74HC595 - o uso desse expansor requer quatro pernas do controlador - três para transmitir o estado e uma para que os relés não cliquem quando a energia é aplicada. Na próxima vez que usar o PCF8574 - o barramento i2c ainda será usado, ou seja, não são necessárias pernas adicionais do MCU e as saídas 1 são definidas quando a energia é aplicada.
- Módulo de relé: NoName, 8 canais, 5V - não há nada a dizer, exceto que o relé é ativado em um nível baixo nas entradas do módulo. Relés de estado sólido não são permitidos neste projeto, pois Os contatos da caldeira devem ser trocados por um contato seco - em geral, não sei a tensão e a corrente direta ou alternada nos contatos.
Sistema operacional
O sistema operacional é todo o software que garante a operacionalidade do programa aplicativo. O sistema operacional também é uma camada entre o hardware e o programa aplicativo, fornecendo uma interface de alto nível para acessar recursos de hardware. No caso do ESP, os componentes do sistema operacional podem ser considerados:
Sistema de arquivos
O projeto usa SPIFFS, tudo parece ficar claro aqui, é a maneira mais fácil. Para facilitar o acesso externo aos arquivos no dispositivo, eu uso a biblioteca nailbuster / esp8266FTPServer.
Sistema de alocação de tempo da CPU
Essa é uma das principais funções do sistema operacional e o ESP não será uma exceção. Para a execução paralela de vários fluxos do algoritmo, o objeto global (singleton) Timers é responsável. A classe é bastante simples e fornece a seguinte funcionalidade:
- Execução periódica de uma função, com um determinado intervalo Exemplo de inicialização do timer:
Timers.add(doLoop, 6000, F("OneWireSensorsClass::doLoop"));
- Uma única execução de uma função após um período especificado. Por exemplo, uma verificação atrasada das redes Wi-Fi é realizada desta maneira:
Timers.once([]() { WiFi.scanNetworks(true);}, 1);
Assim, a função loop fica assim:
void loop(void) { ESP.wdtFeed(); Timers.doLoop(); CPULoadInfo.doLoop(); }
Na prática, a função loop contém mais algumas linhas, que serão descritas abaixo.
Uma lista da classe Timers está anexada.
Contagem de tempo da CPU
Função de serviço que não possui aplicação prática. No entanto, ela é. Implementado pelo CPULoadInfo singleton. Quando o objeto é inicializado, o número de iterações do loop vazio é medido por um curto período de tempo:
void CPULoadInfoClass::init() { uint32_t currTime = millis();
Então, contamos o número de chamadas de procedimento de loop por segundo, calculamos a carga do processador em porcentagem e salvamos os dados no buffer:
void CPULoadInfoClass::doLoop() { static uint32_t prevTime = 0; uint32_t currTime = millis(); LoopsInSecond++; if ((currTime - prevTime) > 1000) { memmove(CPULoadPercentHistory, &CPULoadPercentHistory[1], sizeof(CPULoadPercentHistory) - 1); int8_t load = ((MaxLoopsInSecond - LoopsInSecond) * 100) / MaxLoopsInSecond; CPULoadPercentHistory[sizeof(CPULoadPercentHistory) - 1] = load; prevTime = currTime; LoopsInSecond = 0; } }
O uso dessa abordagem permite obter o mesmo uso do processador por cada thread individual (se você conectar esse subsistema à classe Timers), mas como eu disse - não vejo nenhuma aplicação prática para isso.
Sistema de entrada / saída
Para se comunicar com o usuário, as interfaces UART-USB e WEB são usadas. Penso no UART que não preciso falar em detalhes. A única coisa que vale a pena prestar atenção é por conveniência e compatibilidade com não-ESP, a função serialEvent () é implementada:
void loop(void) {
Com a interface WEB, tudo é muito mais interessante. Dedicamos uma seção separada a ele.
Interface WEB
No caso de dispositivos inteligentes, na minha opinião, a interface WEB é a solução mais amigável.
Considero o uso de uma tela conectada ao dispositivo uma prática desatualizada - é impossível criar uma interface simples, conveniente e bonita ao usar uma tela pequena e um conjunto limitado de botões.
O uso de programas específicos para controlar o dispositivo impõe restrições ao usuário, acrescenta a necessidade de desenvolver e dar suporte a esses programas e também exige que o desenvolvedor cuide da entrega desses programas nos dispositivos terminais do usuário. De uma maneira boa, o aplicativo deve ser publicado nas lojas de aplicativos do Google, Apple, Windows e também disponível nos repositórios Linux na forma de pacotes deb e rpm - caso contrário, o acesso à interface do dispositivo pode ser difícil para alguma parte da audiência.
O acesso à interface WEB do dispositivo está disponível em qualquer sistema operacional - Linux, Windows, Android, MacOS, na área de trabalho, laptop, tablet, smartphone - apenas para ter um navegador. Obviamente, o desenvolvedor da interface WEB precisa levar em consideração os recursos de vários dispositivos, mas isso diz respeito principalmente ao tamanho e à resolução. O acesso à interface WEB de um dispositivo inteligente em uma casa / apartamento / chalé é facilmente fornecido de fora pela Internet - agora é difícil imaginar uma casa / apartamento em que haja dispositivos inteligentes e nenhum roteador e a Internet, e no roteador esse acesso é configurado em alguns cliques (para aqueles que completamente fora do tópico, as palavras-chave ajudarão - “encaminhamento de porta” e “DNS dinâmico”). No caso de uma residência de verão, o acesso pode ser fornecido usando um roteador 3G.
Para implementar a interface WEB, é necessário um servidor WEB. Estou usando a biblioteca me-no-dev / ESPAsyncWebServer. Esta biblioteca fornece a seguinte funcionalidade:
- Retorno de conteúdo estático, incluindo com suporte à compressão gzip. Os diretórios virtuais são suportados, com a capacidade de especificar um arquivo principal (que geralmente é index.htm) para cada diretório.
- Atribuindo funções de retorno de chamada a diferentes URLs com base no tipo de solicitação (GET, POST, ...)
- Suporte para soquetes WEB na mesma porta (isso é importante no encaminhamento de porta).
- Autorização básica. Além disso, a autorização é definida individualmente para cada URL. Isso é importante porque por exemplo, o Google Chrome, ao criar um atalho de página na tela principal, solicita um ícone e um arquivo de manifesto e não transfere dados de autorização. Portanto, alguns arquivos são colocados em um diretório virtual e a autorização é desabilitada para este diretório.
Sistema operacional de serviços HTTP
No projeto atual, todas as configurações do sistema operacional são executadas usando serviços HTTP. O serviço HTTP é uma pequena funcionalidade independente de recuperação / modificação de dados disponível via HTTP. Em seguida, considere uma lista desses serviços.
Ajuda
A implementação de qualquer lista de comandos, acho certo começar com a implementação da equipe HELP. Abaixo está o bloco de inicialização do servidor WEB:
void HTTPserverClass::init() {
Por que preciso de um sistema de ajuda, acho que não vale a pena dizer. Alguns desenvolvedores adiam a implementação do sistema de ajuda para mais tarde, mas esse "mais tarde" geralmente não ocorre. É muito mais fácil implementá-lo no início do projeto e complementá-lo durante o desenvolvimento do projeto.
No meu projeto, uma lista de serviços possíveis também é exibida com um erro 404 - a página não foi encontrada. Atualmente implementamos os seguintes serviços:
http://tc-demo.vehs.ru/help /'doc_hame.ext': load file from server. Allow methods: HTTP_GET /info: get system info. Allow methods: HTTP_GET /time: get time as string (eg: 20140527T123456). Allow methods: HTTP_GET /uptime: get uptime as string (eg: 123D123456). Allow methods: HTTP_GET /rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST /list ? [dir=...] & [format=json]: get file list as text or json. Allow methods: HTTP_GET /edit: edit files. Allow methods: HTTP_GET, HTTP_PUT, HTTP_DELETE, HTTP_POST /wifi: edit wifi settings. Allow methods: HTTP_GET, HTTP_POST /wifi-scan ? [format=json]: get wifi list as text or json. Allow methods: HTTP_GET /wifi-info ? [format=json]: get wifi info as text or json. Allow methods: HTTP_GET /ap: edit soft ap settings. Allow methods: HTTP_GET, HTTP_POST /user: edit user settings. Allow methods: HTTP_GET, HTTP_POST /user-info ? [format=json]: get user info as text or json. Allow methods: HTTP_GET /update: update flash. Allow methods: HTTP_GET, HTTP_POST /restart: restart system. Allow methods: HTTP_GET, HTTP_POST /ws: web socket url. Allow methods: HTTP_GET /help: list allow URLs. Allow methods: HTTP_GET
Como você pode ver, na lista de serviços, não há serviços de aplicativos. Todos os serviços HTTP estão relacionados ao sistema operacional. Cada serviço executa uma pequena tarefa. Além disso, se o serviço exigir a entrada de qualquer dado, GET, mediante solicitação, retornará um formulário de entrada minimalista:
#ifdef USE_RTC_CLOCK help_info.concat(F("/rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST\n")); const char* urlNTP = "/rtc"; server.on(urlNTP, HTTP_GET, [](AsyncWebServerRequest *request) { DEBUG_PRINT(F("/rtc")); request->send(200, ContentTypesStrings[ContentTypes::text_html], String(F("<head><title>RTC time</title></head><body><form method=\"post\" action=\"rtc\"><input name=\"newtime\" length=\"15\" placeholder=\"yyyyMMddThhmmss\"><button type=\"submit\">set</button></form></body></html>"))); }); server.on(urlNTP, HTTP_POST, handleSetRTC_time); #endif

Posteriormente, este serviço é usado em uma interface mais bonita:

Software de aplicação
Finalmente chegamos ao ponto em que o sistema foi criado. Ou seja - para a implementação da tarefa aplicada.
Qualquer aplicativo deve receber os dados de origem, processá-los e produzir o resultado. Também é possível que o sistema relate o estado atual.
Os dados da fonte do controlador de piso radiante são:
- Dados do sensor - o sistema não está vinculado a sensores específicos. Um identificador exclusivo é gerado para cada sensor. Para sensores de rádio, seu identificador é preenchido com zeros a 16 bits; para sensores 1Wire, o CRC16 é calculado com base em seu identificador interno e usado como identificador de sensor. Assim, todos os sensores têm identificadores com um comprimento de 2 bytes.
- Dados sobre zonas de aquecimento - o número de zonas não é fixo, o número máximo é limitado pelo módulo de relé usado. Dada essa limitação, a interface WEB também foi desenvolvida.
- Temperatura e cronograma alvo - tentei fazer as configurações mais flexíveis, você pode criar vários esquemas de aquecimento e até atribuir seu próprio esquema de configurações a cada zona.
Portanto, há várias configurações que precisam ser definidas de alguma forma e há vários parâmetros que o sistema relata como o estado atual.
Para comunicação entre o controlador e o mundo externo, implementei um interpretador de comando que me permitiu implementar o controle do controlador e o recebimento de dados de status. Os comandos são transmitidos ao controlador em formato legível por humanos e podem ser transmitidos por um soquete UART ou WEB (se desejar, você pode implementar suporte para outros protocolos, por exemplo, telnet).
A linha de comando começa com o caractere '#' e termina com um caractere nulo ou um caractere de nova linha. Todos os comandos consistem em um nome de comando e um operando, separados por dois pontos. Para alguns comandos, o operando é opcional; nesse caso, os dois pontos e o operando não são especificados. Os comandos em uma linha são separados por vírgula. Por exemplo:
#ZonesInfo:1,SensorsInfo
E, é claro, a lista de comandos começa com o comando Ajuda, que exibe uma lista de todos os comandos válidos (por conveniência, os comandos transmitidos começam com '>' em vez de '#'):
>help Help SetZonesCount Zone SetName SetSensor ... LoadCfg SaveCfg #Cmd:Help,CmdRes:Ok
Um recurso da implementação do interpretador de comandos é que as informações sobre o resultado da execução do comando também são emitidas na forma de um comando ou um conjunto de comandos:
>help … #Cmd:Help,CmdRes:Ok >zone:123 #Cmd:Zone,Value:123,CmdRes:Error,Error:Zone 123 not in range 1-5 >SchemasInfo #SchemasCount:2 #Schema:1,Name:,DOWs:0b0000000 #Schema:2,Name:,DOWs:0b0000000 #Cmd:SchemasInfo,CmdRes:Ok
No lado do cliente WEB, também é implementado um shell que aceita esses comandos e os converte em uma visualização gráfica. Por exemplo:
>zonesInfo:3 #Zone:3,Name:,Sensor:0x5680,Schema:1,DeltaT:-20 #Cmd:ZonesInfo,CmdRes:Ok
A interface WEB enviou uma solicitação ao controlador sobre o número de zona 3 e, em resposta, recebeu o nome da zona, o identificador do sensor associado à zona, o identificador do circuito atribuído à zona e a correção de temperatura para a zona. A concha não entende números fracionários; portanto, a temperatura é transmitida em décimos de grau, ou seja, 12,3 graus é 123 décimos.
A principal característica é que o controlador responde a todos os clientes de uma só vez para qualquer comando, independentemente do método de inserção do comando. Isso permite exibir a alteração de estado imediatamente em todas as sessões da interface WEB. Porque O principal transporte de troca entre o controlador e a interface WEB são os soquetes WEB, então o controlador pode transmitir dados sem uma solicitação, por exemplo, quando novos dados vierem dos sensores:
#sensor:0x5A20,type:w433th,battery:1,button_tx:0,channel:0,temperature:228,humidity:34,uptime_label:130308243,time_label:20180521T235126
Ou, por exemplo, que essas zonas precisam ser atualizadas:
#Zone:2,TargetTemp:220,CurrentTemp:228,Error:Ok
A interface WEB do controlador é baseada no uso de comandos de texto. Em uma das guias da interface, há um terminal com o qual você pode inserir comandos em forma de texto. Além disso, essa guia, para fins de depuração, permite descobrir quais comandos a interface WEB envia e recebe com várias ações do usuário.
O interpretador de comandos facilita a alteração e o aumento da funcionalidade do dispositivo, alterando os comandos existentes e adicionando novos. Ao mesmo tempo, a depuração de um sistema desse tipo é bastante simplificada, porque a comunicação com o controlador ocorre exclusivamente em uma linguagem legível por humanos.
Conclusão
Usando uma abordagem semelhante, a saber:
- Separação de software em sistema operacional e programa de aplicação
- Implementando configurações do sistema operacional na forma de serviços HTTP minimalistas
- Separando a lógica do sistema da apresentação de dados
- Usando protocolos de comunicação legíveis por humanos
permite criar soluções compreensíveis para usuários e desenvolvedores. Tais soluções são fáceis de modificar. Com base nessas soluções, é fácil criar novos dispositivos com uma lógica completamente diferente, mas que funcionará com os mesmos princípios. Você pode criar uma linha de dispositivos com o mesmo tipo de interface:

Como você pode ver, neste projeto, apenas as três primeiras páginas da interface estão diretamente relacionadas ao aplicativo, e as demais são quase universais.
Nesta publicação, descrevo apenas minha opinião sobre a construção de dispositivos inteligentes e, em nenhum caso, afirmo ser a verdade suprema.
Para quem esse tópico é interessante - escreva, talvez eu esteja errado sobre algo, mas talvez alguns detalhes façam sentido para descrevê-lo em mais detalhes.
O que aconteceu no final:
Fiasco. A história de uma IoT caseira