Desarrollo de dispositivos inteligentes utilizando el ejemplo de un controlador de piso calentado en ESP8266

Quiero compartir mi experiencia en el desarrollo de un dispositivo inteligente. En esta publicación, describiré el hardware (brevemente) y el software (con más detalle).

El controlador está diseñado para analizar las lecturas de los sensores (cableados e inalámbricos) y mantener la temperatura establecida (incluido el horario, incluidos los días de la semana) en cada zona individual encendiendo / apagando la caldera y controlando los circuitos de calefacción del piso de agua usando los cabezales térmicos en el colector.

Hardware


Como controlador, se seleccionó ESP8266 (WeMos D1 mini), como Tiene wifi a bordo. En lugar del ESP8266, se puede utilizar cualquier otro controlador o microordenador: las ideas generales permanecerán sin cambios, lo principal es que un servidor WEB con soporte para sockets WEB podría implementarse en el sistema seleccionado. Los siguientes también fueron utilizados en el proyecto:

  • RTC: DS3231: es necesario determinar el día de la semana y la hora actual. El proyecto fue concebido como un dispositivo independiente que puede funcionar sin Internet, por lo que NTP no es adecuado.
  • Sensores de temperatura inalámbricos: NoName, 433MHz, de la estación meteorológica china, una solución llave en mano, funcionan con baterías. ¿Qué más necesitas? Pero es necesario que el período de transferencia de datos no sea fijo. El problema es que el período de transmisión es de 35 segundos y no nada mucho. Y hay situaciones en las que las señales de dos sensores se superponen. En este caso, uno o dos sensores abandonan el sistema por un tiempo. El problema se puede resolver utilizando sensores similares, en los cuales la conmutación de canales también cambia el período de transmisión de datos.
  • Receptor de 433MHz: Rxb6: las revisiones de Internet y la experiencia personal no son un mal receptor.
  • Sensores de temperatura con cable: DS18B20: muy conveniente ya que no necesita saber la cantidad de sensores por adelantado.
  • 1Wire bus master: DS2482-100: el protocolo 1Wire es muy sensible a los tiempos, por lo tanto, todas las implementaciones del programa bus master usan delay, lo que no es muy bueno para la multitarea. Con este chip, puede aprovechar el bus 1Wire y deshacerse de sus defectos transmitiendo 1Wire <-> i2c. El protocolo i2c tiene una línea de sincronización, por lo que no es crítico para los tiempos y a menudo se implementa en el hardware de los controladores.
  • Temporizador de vigilancia: TPL5000DGST: el tiempo de actividad continuo no es tan importante para este proyecto, sin embargo, la accesibilidad es muy importante. El ESP8266 tiene un temporizador de vigilancia incorporado. Pero como ha demostrado la práctica, a veces todavía hay situaciones en las que no puede hacer frente y el sistema se congela. Un temporizador de vigilancia de hardware externo está diseñado para hacer frente a situaciones de emergencia. Configurado para un retraso de 64 segundos. Conectado a la TX TX: durante la operación, el sistema escribe constantemente información de depuración en Serial y la falta de actividad durante más de un minuto indica un bloqueo del sistema.
  • Expansor de puerto: 74HC595: el uso de este expansor requiere 4 patas del controlador, tres para transmitir el estado y una para que los relés no hagan clic cuando se aplica energía. La próxima vez usaré PCF8574: el bus i2c todavía se usa, es decir no se requieren patas MCU adicionales y las salidas 1 se configuran cuando se aplica energía.
  • Módulo de relé: Sin nombre, 8 canales, 5 V: no hay nada que decir, excepto que el relé se enciende a un nivel bajo en las entradas del módulo. Los relés de estado sólido no están permitidos en este proyecto, ya que Los contactos de la caldera deben cambiarse mediante un contacto seco; en general, no conozco el voltaje y la corriente continua o alterna en los contactos.

Sistema operativo


El sistema operativo es todo el software que garantiza la operatividad del programa de aplicación. El sistema operativo también es una capa entre el hardware y el programa de aplicación, que proporciona una interfaz de alto nivel para acceder a los recursos de hardware. En el caso de ESP, los componentes del sistema operativo pueden considerarse:

Sistema de archivos


El proyecto usa SPIFFS, todo parece estar claro aquí, esta es la forma más fácil. Para acceder fácilmente a los archivos en el dispositivo desde el exterior, utilizo la biblioteca nailbuster / esp8266FTPServer.

Sistema de asignación de tiempo de CPU


Esta es una de las funciones principales del sistema operativo y ESP no será una excepción. Para la ejecución paralela de varios flujos del algoritmo, el objeto global (singleton) Timers es responsable. La clase es bastante simple y proporciona la siguiente funcionalidad:

  • Ejecución periódica de la función, con un intervalo especificado. Ejemplo de inicialización del temporizador:

    Timers.add(doLoop, 6000, F("OneWireSensorsClass::doLoop")); //   –   
  • Una sola ejecución de una función después de un período de tiempo especificado. Por ejemplo, un escaneo retrasado de redes WiFi se realiza de esta manera:

     Timers.once([]() { WiFi.scanNetworks(true);}, 1); 

Por lo tanto, la función de bucle se ve así:

 void loop(void) { ESP.wdtFeed(); Timers.doLoop(); CPULoadInfo.doLoop(); } 

En la práctica, la función de bucle contiene algunas líneas más, que se describirán a continuación.
Se adjunta una lista de la clase Timers.

Contabilidad de tiempo de CPU


Función de servicio que no tiene aplicación práctica. Sin embargo, ella es. Implementado por el singleton CPULoadInfo. Cuando se inicializa el objeto, se mide el número de iteraciones del bucle vacío durante un corto período de tiempo:

 void CPULoadInfoClass::init() { uint32_t currTime = millis(); //      1 while ((millis() - currTime) < 10) { delay(0); MaxLoopsInSecond++; } MaxLoopsInSecond *= 100; } 

Luego, contamos el número de llamadas de procedimiento de bucle por segundo, calculamos la carga del procesador en porcentaje y guardamos los datos en el búfer:

 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; } } 

El uso de este enfoque le permite obtener el mismo uso de procesador por cada subproceso individual (si conecta este subsistema con la clase Timers), pero como dije, no veo ninguna aplicación práctica para esto.

Sistema de entrada-salida


Para comunicarse con el usuario, se utilizan la interfaz UART-USB y WEB. Pienso en UART, no necesito hablar en detalle. Lo único a lo que vale la pena prestar atención es por conveniencia y compatibilidad con no ESP, se implementa la función serialEvent ():

 void loop(void) { // … if (Serial.available()) serialEvent(); // … } 

Con la interfaz WEB, todo es mucho más interesante. Le dedicamos una sección separada.

Interfaz WEB


En el caso de los dispositivos inteligentes, en mi opinión, la interfaz WEB es la solución más fácil de usar.

Considero que el uso de una pantalla conectada al dispositivo es una práctica desactualizada: es imposible crear una interfaz simple, conveniente y hermosa cuando se usa una pantalla pequeña y un conjunto limitado de botones.

El uso de programas específicos para controlar el dispositivo impone restricciones al usuario, agrega la necesidad de desarrollar y soportar estos programas, y también requiere que el desarrollador se encargue de la entrega de estos programas a los dispositivos terminales del usuario. En el buen sentido, la aplicación debe publicarse en las tiendas de aplicaciones de Google, Apple y Windows, y también debe estar disponible en repositorios de Linux en forma de paquetes deb y rpm; de lo contrario, el acceso a la interfaz del dispositivo puede ser difícil para alguna parte de la audiencia.

El acceso a la interfaz WEB del dispositivo está disponible desde cualquier sistema operativo: Linux, Windows, Android, MacOS, en el escritorio, computadora portátil, tableta, teléfono inteligente, solo para tener un navegador. Por supuesto, el desarrollador de la interfaz WEB debe tener en cuenta las características de varios dispositivos, pero esto se refiere principalmente al tamaño y la resolución. El acceso a la interfaz WEB de un dispositivo inteligente en una casa / apartamento / cabaña se proporciona fácilmente desde el exterior a través de Internet; ahora es difícil imaginar una casa / apartamento en el que haya dispositivos inteligentes y sin enrutador e Internet, y en el enrutador este acceso se configura en unos pocos clics (para aquellos que completamente fuera de tema, las palabras clave ayudarán: "reenvío de puertos" y "DNS dinámico"). En el caso de una residencia de verano, se puede proporcionar acceso mediante un enrutador 3G.

Para implementar la interfaz WEB, se requiere un servidor WEB. Estoy usando la biblioteca me-no-dev / ESPAsyncWebServer. Esta biblioteca proporciona la siguiente funcionalidad:

  • Devolución de contenido estático, incluido con soporte de compresión gzip. Los directorios virtuales son compatibles, con la capacidad de especificar un archivo principal (que generalmente es index.htm) para cada directorio.
  • Asignación de funciones de devolución de llamada a diferentes URL en función del tipo de solicitud (GET, POST, ...)
  • Soporte para sockets WEB en el mismo puerto (es importante cuando se reenvía el puerto).
  • Autorización BÁSICA. Además, la autorización se establece individualmente para cada URL. Esto es importante porque por ejemplo, Google Chrome, cuando crea un acceso directo de página en la pantalla principal, solicita un icono y un archivo de manifiesto y no transfiere datos de autorización. Por lo tanto, algunos archivos se colocan en un directorio virtual y la autorización está deshabilitada para este directorio.

Sistema operativo de servicios HTTP


En el proyecto actual, todas las configuraciones del sistema operativo se realizan mediante servicios HTTP. El servicio HTTP es una pequeña funcionalidad independiente de recuperación / modificación de datos disponible a través de HTTP. Luego, considere una lista de estos servicios.

Ayuda


La implementación de cualquier lista de comandos, creo que es correcto comenzar con la implementación del equipo HELP. A continuación se muestra el bloque de inicialización del servidor WEB:

 void HTTPserverClass::init() { //SERVER INIT help_info.concat(F("/'doc_hame.ext': load file from server. Allow methods: HTTP_GET\n")); AsyncStaticWebHandler& handler = server.serveStatic("na/", SPIFFS, "na/"); serveStaticHandlerNA = &handler; //       server.serveStatic("/", SPIFFS, "/"); //    //info //       help_info.concat(F("/info: get system info. Allow methods: HTTP_GET\n")); server.on("/info", HTTP_GET, handleInfo); … server.on("/help", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, ContentTypesStrings[ContentTypes::text_plain], help_info.c_str()); }); //    setAuthentication(ConfigStore.getAdminName(), ConfigStore.getAdminPassword()); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); //  -  //   server.begin(); //       DEBUG_PRINT(F("HTTP server started")); } 

¿Por qué necesito un sistema de ayuda? Creo que no vale la pena contarlo. Algunos desarrolladores posponen la implementación del sistema de ayuda para más adelante, pero este "más tarde" generalmente no ocurre. Es mucho más fácil implementarlo al comienzo del proyecto y complementarlo durante el desarrollo del proyecto.
En mi proyecto, también se muestra una lista de posibles servicios con un error 404: no se encontró la página. Actualmente implementa los siguientes servicios:

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 puede ver, en la lista de servicios no hay servicios de aplicación. Todos los servicios HTTP están relacionados con el sistema operativo. Cada servicio realiza una pequeña tarea. Además, si el servicio requiere la entrada de cualquier dato, entonces, previa solicitud, GET devuelve un formulario 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 // USE_RTC_CLOCK 



Más tarde, este servicio se utiliza en una interfaz más bonita:



Software de aplicación


Finalmente llegamos al punto para el cual se creó el sistema. A saber: a la implementación de la tarea aplicada.

Cualquier aplicación debe recibir los datos de origen, procesarlos y producir el resultado. También es posible que el sistema informe el estado actual.

Los datos de origen para el controlador de calefacción por suelo radiante son:

  • Datos del sensor: el sistema no está vinculado a sensores específicos. Se genera un identificador único para cada sensor. Para los sensores de radio, su identificador se rellena con ceros a 16 bits; para los sensores de 1 cable, CRC16 se calcula en función de su identificador interno y se utiliza como el identificador del sensor. Por lo tanto, todos los sensores tienen identificadores con una longitud de 2 bytes.
  • Datos sobre zonas de calentamiento: el número de zonas no es fijo, el número máximo está limitado por el módulo de relé utilizado. Dada esta limitación, también se desarrolló la interfaz WEB.
  • Temperatura objetivo y programa: intenté hacer las configuraciones más flexibles, puede crear varios esquemas de calefacción e incluso puede asignar su propio esquema de configuraciones a cada zona.

Por lo tanto, hay una serie de configuraciones que deben establecerse de alguna manera, y hay una serie de parámetros que el sistema informa como el estado actual.
Para la comunicación entre el controlador y el mundo exterior, implementé un intérprete de comandos que me permitió implementar tanto el control del controlador como la recepción de datos de estado. Los comandos se transmiten al controlador en forma legible y pueden transmitirse a través de un conector UART o WEB (si lo desea, puede implementar soporte para otros protocolos, por ejemplo, telnet).
La línea de comando comienza con el carácter '#' y termina con un carácter nulo o un carácter de nueva línea. Todos los comandos consisten en un nombre de comando y un operando, separados por dos puntos. Para algunos comandos, el operando es opcional; en este caso, los dos puntos y el operando no están especificados. Los comandos en una línea están separados por una coma. Por ejemplo:

 #ZonesInfo:1,SensorsInfo 

Y, por supuesto, la lista de comandos comienza con el comando Ayuda, que muestra una lista de todos los comandos válidos (por conveniencia, los comandos transmitidos comienzan con '>' en lugar de '#'):

 >help Help SetZonesCount Zone SetName SetSensor ... LoadCfg SaveCfg #Cmd:Help,CmdRes:Ok 

Una característica de la implementación del intérprete de comandos es que la información sobre el resultado de la ejecución del comando también se emite en forma de un comando o un 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 

En el lado del cliente WEB, también se implementa un shell que acepta estos comandos y los convierte en una vista gráfica. Por ejemplo:

 >zonesInfo:3 #Zone:3,Name:,Sensor:0x5680,Schema:1,DeltaT:-20 #Cmd:ZonesInfo,CmdRes:Ok 

La interfaz WEB envió una solicitud al controlador sobre la zona número 3, y en respuesta recibió el nombre de la zona, el identificador del sensor asociado con la zona, el identificador del circuito asignado a la zona y la corrección de temperatura para la zona. El caparazón no comprende números fraccionarios, por lo que la temperatura se transmite en décimas de grado, es decir. 12.3 grados son 123 décimas.

La característica clave es que el controlador responde a todos los clientes a la vez para cualquier comando, independientemente del método para ingresar el comando. Esto le permite mostrar el cambio de estado inmediatamente en todas las sesiones de la interfaz WEB. Porque El transporte de intercambio principal entre el controlador y la interfaz WEB son los enchufes WEB, luego el controlador puede transmitir datos sin una solicitud, por ejemplo, cuando los datos nuevos provienen de sensores:

 #sensor:0x5A20,type:w433th,battery:1,button_tx:0,channel:0,temperature:228,humidity:34,uptime_label:130308243,time_label:20180521T235126 

O, por ejemplo, que estas zonas deben actualizarse:

 #Zone:2,TargetTemp:220,CurrentTemp:228,Error:Ok 

La interfaz WEB del controlador se basa en el uso de comandos de texto. En una de las pestañas de la interfaz hay un terminal con el que puede ingresar comandos en forma de texto. Además, esta pestaña, para fines de depuración, le permite averiguar qué comandos envía y recibe la interfaz WEB con diversas acciones del usuario.

El intérprete de comandos facilita el cambio y aumenta la funcionalidad del dispositivo al cambiar los comandos existentes y agregar otros nuevos. Al mismo tiempo, la depuración de dicho sistema se simplifica enormemente, porque La comunicación con el controlador se realiza exclusivamente en un lenguaje legible por humanos.

Conclusión


Usando un enfoque similar, a saber:

  • Separación de software en sistema operativo y programa de aplicación
  • Implementación de la configuración del sistema operativo en forma de servicios HTTP minimalistas
  • Separar la lógica del sistema de la presentación de datos
  • Usar protocolos de comunicación legibles por humanos

le permite crear soluciones que sean comprensibles tanto para los usuarios como para los desarrolladores. Dichas soluciones son fáciles de modificar. Basado en tales soluciones, es fácil construir nuevos dispositivos con una lógica completamente diferente, pero que funcionará con los mismos principios. Puede crear una línea de dispositivos con el mismo tipo de interfaz:



Como puede ver, en este proyecto solo las primeras tres páginas de la interfaz están directamente relacionadas con la aplicación, y el resto son casi universales.

En esta publicación, describo solo mi opinión sobre la construcción de dispositivos inteligentes, y en ningún caso pretendo ser la verdad definitiva.

Para quién es interesante este tema: escriba, tal vez me equivoque sobre algo, pero tal vez algunos detalles tengan sentido para describir con más detalle.

Lo que sucedió al final: Fiasco. La historia de un IoT casero

Source: https://habr.com/ru/post/es412889/


All Articles