使用ESP8266上的加热地板控制器示例开发智能设备

我想分享我在开发智能设备方面的经验。 在本出版物中,我将简要介绍硬件和软件(更详细地介绍)软件。

该控制器旨在分析传感器的读数(有线和无线)并通过打开/关闭锅炉并使用集热器上的热敏头控制水底加热回路来维持每个单独区域中的设定温度(包括时间表,包括一周中的几天)。

硬体


作为控制器,选择了ESP8266(WeMos D1 mini),作为 船上有wifi。 除了ESP8266外,其他任何控制器或微型计算机都可以使用-总体思路不变,主要的事情是可以在所选系统上部署支持WEB套接字的WEB服务器。 项目中还使用了以下内容:

  • RTC:DS3231-必须确定星期几和当前时间。 该项目被认为是可以在没有Internet的情况下工作的独立设备,因此NTP不适合。
  • 无线温度传感器:NoName,433MHz,来自中国气象站-一种交钥匙解决方案,它们使用电池供电。 您还需要什么? 但是有必要数据传输周期不固定。 问题在于传输周期为35秒,并且游泳不多。 并且在某些情况下,两个传感器的信号会重叠。 在这种情况下,一两个传感器会暂时退出系统。 使用类似的传感器可以解决该问题,其中通道切换也会改变数据传输周期。
  • 433MHz接收器:Rxb6-互联网评论和个人经验都不错。
  • 有线温度传感器:DS18B20-非常方便,因为您无需事先知道传感器的数量。
  • 1Wire总线主机:DS2482-100-1Wire协议对时序非常敏感,因此程序总线主机的所有实现都使用延迟,这对于多任务处理不是很好。 使用该芯片,您可以利用1Wire总线,并通过广播1Wire <-> i2c消除其缺点。 i2c协议具有一条同步线,因此它对时序并不重要,通常在控制器的硬件中实现。
  • 看门狗定时器:TPL5000DGST-对于该项目,连续的正常运行时间并不重要,但是可访问性非常重要。 ESP8266内置了看门狗定时器。 但是,正如实践所表明的那样,有时仍然存在无法应对且系统死机的情况。 外部硬件看门狗定时器旨在处理紧急情况。 配置为64秒的延迟。 连接到支腿TX-在操作期间,系统不断将调试信息写入Serial,并且如果超过一分钟没有活动,则表明系统已挂起。
  • 端口扩展器:74HC595-使用此扩展器需要控制器的4条支线-3条支线传输状态,1条支线以便在通电时继电器不会发出喀哒声。 下次我将使用PCF8574-i2c总线仍在使用,即 无需额外的MCU支路,并且在通电时将设置输出1。
  • 继电器模块:NoName,8通道,5V-没什么好说的,除了继电器在模块输入端以低电平打开时。 该项目中不允许使用固态继电器,因为 锅炉触点必须通过干触点进行切换-通常,我不知道触点上的电压以及直流或交流电。

作业系统


操作系统是确保应用程序可操作性的所有软件。 操作系统也是硬件和应用程序之间的一层,提供用于访问硬件资源的高级接口。 对于ESP,操作系统的组件为:

文件系统


该项目使用SPIFFS,在这里一切似乎都很清楚,这是最简单的方法。 为了从外部轻松访问设备上的文件,我使用nailbuster / esp8266FTPServer库。

CPU时间分配系统


这是操作系统的主要功能之一,ESP也不例外。 对于并行执行算法的各种流程,全局对象(单例)计时器负责。 该类非常简单,并提供以下功能:

  • 以指定的时间间隔定期执行功能。 计时器初始化示例:

    Timers.add(doLoop, 6000, F("OneWireSensorsClass::doLoop")); //   –   
  • 在指定的时间段后一次执行功能。 例如,以这种方式执行WiFi网络的延迟扫描:

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

因此,循环函数如下所示:

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

实际上,循环功能包含更多行,下面将对其进行描述。
附带了Timers类的清单。

CPU时间核算


没有实际应用的服务功能。 不过,她是。 由单例CPULoadInfo实现。 初始化对象后,将在很短的时间内测量空循环的迭代次数:

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

然后,我们计算每秒循环过程调用的次数,我们以百分比计算处理器负载,并将数据保存到缓冲区:

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

使用这种方法可以使每个线程获得相同的处理器使用率(如果将此子系统与Timers类连接在一起),但是正如我所说-我没有看到任何实际的应用程序。

输入输出系统


为了与用户通信,使用了UART-USB和WEB接口。 我认为关于UART,我不需要详细讨论。 唯一需要注意的是,为了方便和与非ESP兼容,实现了serialEvent()函数:

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

使用WEB界面,一切都变得更加有趣。 我们在其中专门介绍了一个部分。

WEB界面


在我看来,对于智能设备,WEB界面是最人性化的解决方案。

我认为使用连接到设备的屏幕是过时的做法-当使用小屏幕和有限的按钮集时,不可能创建简单,方便,美观的界面。

使用特定程序来控制设备会给用户带来限制,增加了开发和支持这些程序的需要,并且还要求开发人员注意将这些程序交付给用户的终端设备。 以良好的方式,该应用程序应该以deb和rpm软件包的形式在Google,Apple,Windows应用程序商店中发布,并在Linux系统信息库中可用-否则,某些受众可能难以访问设备的界面。

只需安装浏览器,即可从任何操作系统(Linux,Windows,Android,MacOS,台式机,笔记本电脑,平板电脑,智能手机上)访问设备的WEB界面。 当然,WEB界面的开发人员必须考虑各种设备的功能,但这主要涉及尺寸和分辨率。 可以很容易地通过互联网从外部访问房屋/公寓/平房中智能设备的WEB界面-现在很难想象房屋/公寓中没有智能设备并且没有路由器和Internet,并且在路由器中单击几次即可配置此访问权限(对于那些完全不在主题范围内的关键字将对您有所帮助-“端口转发”和“动态DNS”)。 如果是夏季住宅,则可以使用3G路由器进行访问。

要实现WEB界面,需要一个WEB服务器。 我正在使用me-no-dev / ESPAsyncWebServer库。 该库提供以下功能:

  • 返回静态内容,包括 带有gzip压缩支持。 支持虚拟目录,并能够为每个目录指定主文件(通常为index.htm)。
  • 根据请求的类型(GET,POST等)将回调函数分配给不同的URL
  • 支持同一端口上的WEB套接字(在端口转发时很重要)。
  • 基本授权。 此外,为每个URL分别设置授权。 这很重要,因为 例如,谷歌浏览器在主屏幕上创建页面快捷方式时,会请求图标和清单文件,并且不会传输授权数据。 因此,某些文件被放置在虚拟目录中,并且对该目录禁用了授权。

HTTP服务操作系统


在当前项目中,所有操作系统设置都是使用HTTP服务执行的。 HTTP服务是可通过HTTP使用的小型独立数据检索/修改功能。 接下来,考虑这些服务的列表。

帮忙


执行任何命令列表,我认为从实施HELP团队开始是正确的。 以下是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")); } 

为什么我需要一个帮助系统,我认为这不值一提。 一些开发人员将帮助系统的实施推迟到以后,但是这种“后期”通常不会发生。 在项目开始时实施它并在项目开发过程中对其进行补充要容易得多。
在我的项目中,还会显示可能的服务列表,并显示404错误-未找到该页面。 当前实施了以下服务:

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 

如您所见,服务列表中没有应用程序服务。 所有HTTP服务都与操作系统有关。 每个服务执行一个小任务。 此外,如果服务需要输入任何数据,则应要求,GET返回一个简约的输入形式:

 #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 



后来,该服务在更漂亮的界面中使用:



应用软件


最后,我们来到创建系统的地步。 即-到应用任务的执行。

任何应用程序都必须接收源数据,对其进行处理并产生结果。 系统也应该报告当前状态。

地板采暖控制器的源数据为:

  • 传感器数据-系统未绑定到特定传感器。 为每个传感器生成一个唯一的标识符。 对于无线传感器,其标识符用0到16位填充;对于1Wire传感器,根据其内部标识符计算CRC16,并将其用作传感器标识符。 因此,所有传感器都具有长度为2个字节的标识符。
  • 加热区的数据-加热区的数量不固定,最大数量受所用继电器模块的限制。 鉴于此限制,还开发了WEB界面。
  • 目标温度和时间表-我尝试进行最灵活的设置,可以创建多个加热方案,甚至可以为每个区域分配自己的设置方案。

因此,有许多设置需要以某种方式进行设置,并且有许多参数需要系统报告为当前状态。
为了实现控制器与外界之间的通信,我实现了命令解释器,该命令解释器使我既可以实现对控制器的控制又可以接收状态数据。 命令以人类可读的形式传输到控制器,并且可以通过UART或WEB套接字传输(如果需要,您可以实现对其他协议的支持,例如telnet)。
命令行以“#”字符开头,以空字符或换行符结尾。 所有命令均由命令名称和操作数组成,并用冒号分隔。 对于某些命令,操作数是可选的;在这种情况下,未指定冒号和操作数。 一行中的命令用逗号分隔。 例如:

 #ZonesInfo:1,SensorsInfo 

当然,命令列表以“帮助”命令开头,该命令显示所有有效命令的列表(为方便起见,传输的命令以“>”而不是“#”开头):

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

命令解释器的实现的一个特征是,有关命令执行结果的信息也以命令或命令集的形式发布:

 >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 

在WEB客户端,还实现了一个外壳程序,该外壳程序接受这些命令并将其转换为图形视图。 例如:

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

WEB接口向控制器发送了有关区域编号3的请求,并作为响应接收了区域名称,与该区域关联的传感器的标识符,分配给该区域的电路的标识符以及该区域的温度校正。 壳不了解分数,因此温度以十分之一度(即1摄氏度)传递。 12.3度是123的十分之一。

关键功能是,无论输入命令的方法如何,控制器都会对所有命令立即响应所有客户端。 这使您可以立即在WEB界面的所有会话中显示状态更改。 因为 控制器和WEB接口之间的主要交换传输是WEB套接字,然后控制器可以在没有请求的情况下传输数据,例如,当新数据来自传感器时:

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

或者,例如,这些区域需要更新:

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

控制器的WEB界面基于文本命令的使用。 在界面的一个选项卡上,有一个终端,您可以使用该终端以文本形式输入命令。 另外,此选项卡(用于调试)可让您找出WEB界面通过各种用户操作发送和接收的命令。

通过更改现有命令并添加新命令,命令解释器使更改和增加设备功能变得容易。 同时,这样的系统的调试大大简化了,因为 与控制器的通信仅以人类可读的语言进行。

结论


使用类似的方法,即:

  • 将软件分为操作系统和应用程序
  • 以简约的HTTP服务形式实现操作系统设置
  • 将系统逻辑与数据表示分离
  • 使用人类可读的通信协议

使您可以创建用户和开发人员都可以理解的解决方案。 这样的解决方案很容易修改。 基于这样的解决方案,很容易用完全不同的逻辑来构建新设备,但是它们将以相同的原理工作。 您可以创建一系列具有相同接口类型的设备:



如您所见,在该项目中,仅界面的前三页与应用程序直接相关,其余几乎都是通用的。

在本出版物中,我仅描述我对智能设备的构造的看法,在任何情况下我都不会声称这是最终的真理。

这个主题对谁有意思-写,也许我在某件事上是错的,但也许有一些细节可以更详细地描述。

最终发生了什么: 惨败。 一种自制物联网的故事

Source: https://habr.com/ru/post/zh-CN412889/


All Articles