我们将水表连接到智能家居

曾经的家庭自动化系统或通常被称为“智能家居”的系统非常昂贵,只有富人才能负担得起。 如今,在市场上,您可以找到非常便宜的套件,其中包含用于控制照明,插座,通风,供水和其他消费者的传感器,按钮/开关和执行器。 甚至最krivoruky DIY-shnik都能以低廉的价格加入漂亮的收藏家并为智能家居收集设备。



通常,建议的设备是传感器或执行器。 它们可以轻松实现“触发运动传感器时打开灯”或“出口处的开关使整个公寓的灯都熄灭”等场景。 但是遥测无法解决。 充其量,这是一个温度和湿度或特定插座中瞬时功率的图表。

最近,我安装了带脉冲输出的水表。 通过仪表的每升水都会触发一个簧片开关,并闭合触点。 剩下的唯一一件事就是紧紧抓住电线,并尝试从中获利。 例如,按小时和星期几分析耗水量。 好吧,如果公寓中有几个水上升管,那么在一个屏幕上查看所有当前指示器比用手电筒爬上难以触及的壁more更为方便。

根据削减,我的设备版本基于ESP8266,它可以对水表中的脉冲进行计数,并通过MQTT将读数发送到智能家居服务器。 我们将使用uasyncio库在micropython中进行编程。 在创建固件时,我遇到了一些有趣的困难,我还将在本文中进行讨论。 走吧

方案




整个电路的核心是ESP8266微控制器上的模块。 ESP-12原先计划中,但事实证明它是有缺陷的。 我必须对可用的ESP-07模块感到满意。 幸运的是,它们在结论和功能上都是相同的,区别仅在于天线-ESP-12内置,而ESP-07内置。 但是,即使没有WiFi天线,我浴室中的信号也能正常捕获。

模块绑定是标准的:

  • 带有悬吊器和电容器的复位按钮(尽管两者已经在模块内部)
  • 使能信号(CH_PD)上电
  • GPIO15接地。 这只是一开始就必须的,但是我仍然没有必要坚持这一条

要将模块置于固件模式,您需要将GPIO2接地,为了使其更加方便,我提供了Boot按钮。 在正常情况下,该引脚被上电。

GPIO2线的状态仅在工作开始时检查-通电时或复位后立即检查。 因此,该模块要么照常加载,要么进入固件模式。 加载后,此输出可用作常规GPIO。 好吧,由于那里已经有一个按钮,您可以在上面放一些有用的功能。

对于编程和调试,我将使用带到梳子的UART。 必要时-我只需插入USB-UART适配器即可。 您只需要记住该模块由3.3V供电。 如果忘记将适配器切换到该电压并施加5V电压,则模块很可能会烧坏。

我的浴室用电没有问题-插座位于距离仪表约1米的位置,因此我将使用220V的电源为其供电。 作为电源,我将使用Tenstar Robot的一小部分HLK-PM03 。 就个人而言,我对模拟和电力电子设备非常关注,但这里有一个小巧的成品电源。

为了指示操作模式,我提供了一个连接到GPIO2的LED。 但是,我没有开始焊接它,因为 此外,ESP-07模块已经有一个LED连接到相同的GPIO2。 但是,顺带一提-我突然想把这个LED带到外壳上。

我们传递给最有趣的。 水表没有逻辑;不能要求它们提供当前读数。 对我们来说唯一可用的是脉冲-每升关闭簧片开关的触点。 舌簧开关的结论在GPIO12 / GPIO13中设置。 我将以编程方式打开模块内部的上拉电阻。

最初,我忘记提供电阻R8和R9,但在我的电路板版本中却没有。 但是,由于我已经将该计划公开展示,因此有必要纠正这种疏忽。 如果固件有故障,则需要电阻器以免烧毁端口,并将其放在引脚上,并且簧片开关将此线接地短路(电阻器将流经最大3.3V / 1000Ohm = 3.3mA)。

现在是时候考虑停电了怎么办。 第一种选择是在开始时要求服务器提供初始计数器。 但这将需要交换协议的极大复杂化。 此外,在这种情况下,设备的可操作性取决于服务器的状态。 如果在关闭灯后服务器没有启动(或稍后启动),则水表将无法请求初始值,并且将无法正常工作。

因此,我决定将计数器值存储在通过I2C连接的存储芯片中。 我对闪存的大小没有特殊要求-我只需要保存2个数字(冷热水表的升数)。 即使最小的模块也可以。 但是在记录周期数上您需要注意。 对于大多数模块而言,这是10万个周期,对于某些模块而言,多达一百万个周期。

看来一百万是很多。 但是,在我的公寓居住了4年后,我消耗了500多立方米的水,也就是50万升! 以及50万个条目在闪存中。 这只是冷水。 当然,您可以每两年焊接一次芯片,但是事实证明有FRAM芯片。 从编程的角度来看,这是相同的I2C EEPROM,但是重写周期非常多(数亿)。 直到所有东西都以这种方式以这种芯片到达商店为止,所以现在通常的24LC512可以使用了。

电路板


最初,我计划在家中收费。 因此,该板被设计为单面。 但是,在用激光熨斗和阻焊膜(经过某种程度的准备后再长时间使用)长时间工作后,我仍然决定从中国人那里订购木板。



在订购电路板之前,我几乎意识到,除了I2C总线上的闪存芯片之外,您还可以购买其他有用的东西,例如显示器。 到底要输出什么仍然是一个问题,但您需要在董事会上进行培育。 好吧,由于我要在工厂订购电路板,所以将自己限制为单面电路板是没有意义的,因此I2C上的线路是电路板背面的唯一线路。

一个大的门框也与单面布线相关联。 因为 电路板的一侧被绘制,轨道和SMD组件计划被放置在一侧,而输出组件,连接器和电源则被放置在另一侧。 一个月后收到板子时,我忘了最初的计划,解开了正面的所有组件。 而且只有在焊接电源时,事实证明,正负离婚了。 我不得不和跳线一起去集体农场。 在上面的图片中,我已经更改了布线,但是通过“启动”按钮的输出将地从板的一个部分扔到了另一部分(尽管可以在第二层上绘制一条轨道)。

原来是这样的



房屋


下一步是外壳。 使用3D打印机,这不是问题。 我并没有太在意-我只是画了一个合适大小的盒子,并在合适的位置剪裁了一部分。 盖子通过小螺丝固定在车身上。



我已经提到过Boot按钮可以用作通用按钮-在这里我们将其带到前面板。 为此,我在按钮所在的位置绘制了一个特殊的“井”。



机壳内部还有用于安装板并用一个M3螺钉固定的树桩(板上没有更多空间)。

当我打印第一个合适的表壳时,选择了显示器。 标准的两行不适合这种情况,但是在云台中发现了OLED显示器SSD1306 128x32。 它很小,但我不必每天看着他-它会滚动。

估计从他身上铺设电线的方式以及方式,我决定将显示器贴在箱子中间。 当然,人体工学在底板下方-顶部是按钮,底部是显示器。 但是我已经说过,拧紧显示器的想法来得太晚了,太懒了,无法重新布置面板来移动按钮。

设备完成。 显示模块粘在热熔喷嘴上





最终结果可以在KDPV上看到

韧体


让我们继续进行软件部分。 对于这样的小型工艺品,我真的很喜欢使用Python语言( micropython )-代码非常紧凑且易于理解。 幸运的是,不必为了降低微秒而降低寄存器的级别-一切都可以通过python完成。

看起来很简单,但不是很简单-设备概述了几个独立的功能:

  • 用户戳一个按钮并查看显示
  • 升滴答并更新闪存中的值
  • 模块监视WiFi信号并在必要时重新连接
  • 好吧,没有指示灯闪烁,您根本无法做

如果另一个功能由于某种原因而愚蠢,则不可能假设一个功能不起作用。 我已经在其他项目中吃过仙人掌,现在我看到了“缺少另一升,因为当时显示屏已更新”或“模块连接到WiFi时用户无能为力”的样式问题。 当然,有些事情可以通过中断来完成,但是您会遇到持续时间,调用嵌套或变量的非原子更改的限制。 好吧,执行所有操作并立即迅速变成混乱的代码。

在该项目中,我更认真地使用了经典的抢先式多任务处理和FreeRTOS,但是在这种情况下, 协程模型和uasync库变得更加合适。 而且,Corutin的Pitonovskiy实现只是炸弹-对程序员而言,一切都简单而方便地完成了。 只需编写您自己的逻辑,告诉我您可以在线程之间切换的位置即可。

我建议选择探索排挤与竞争性多任务处理之间的区别。 现在,让我们最后继续进行代码。

##################################### # Counter class - implements a single water counter on specified pin ##################################### class Counter(): debounce_ms = const(25) def __init__(self, pin_num, value_storage): self._value_storage = value_storage self._value = self._value_storage.read() self._value_changed = False self._pin = Pin(pin_num, Pin.IN, Pin.PULL_UP) loop = asyncio.get_event_loop() loop.create_task(self._switchcheck()) # Thread runs forever 

每个计数器都由Counter类的实例处理。 首先,从EEPROM(value_storage)中减去计数器的初始值-这就是从电源故障中恢复的方式。

该引脚通过电源的内置上拉电阻进行初始化:如果舌簧开关闭合,则线路上的该引脚为零;如果该线路断开,则该线路被拉至电源,并且控制器读取一。

另外,此处启动了一个单独的任务,它将轮询该引脚。 每个计数器将运行其自己的任务。 这是她的密码

  """ Poll pin and advance value when another litre passed """ async def _switchcheck(self): last_checked_pin_state = self._pin.value() # Get initial state # Poll for a pin change while True: state = self._pin.value() if state != last_checked_pin_state: # State has changed: act on it now. last_checked_pin_state = state if state == 0: self._another_litre_passed() # Ignore further state changes until switch has settled await asyncio.sleep_ms(Counter.debounce_ms) 

需要25ms的延迟来过滤接触反弹,并同时调节任务唤醒的频率(此任务处于睡眠状态时,其他任务可以工作)。 该功能每隔25ms唤醒一次,检查引脚,如果舌簧开关的触点闭合,则说明计数器又经过了另一升,需要对此进行处理。

  def _another_litre_passed(self): self._value += 1 self._value_changed = True self._value_storage.write(self._value) 

处理下一升很简单-计数器只会增加。 好吧,将新值写入闪存驱动器会很好。

为了易于使用,提供了“访问器”。

  def value(self): self._value_changed = False return self._value def set_value(self, value): self._value = value self._value_changed = False 

好吧,现在我们将利用python和uasync库的乐趣,并使计数器对象可以等待(如何将其翻译成俄语?可以预料到吗?)

  def __await__(self): while not self._value_changed: yield from asyncio.sleep(0) return self.value() __iter__ = __await__ 

这是一个方便的功能,可以等待直到计数器值被更新为止-该功能会不时唤醒并检查_value_changed标志。 这个函数的笑话是,调用此函数的代码可以在进入此函数后进入睡眠状态,然后休眠直到收到新值为止。

但是中断呢?
是的,您可以在这里骗我,说他自己说过打扰,但实际上他安排了一个愚蠢的针脚调查。 实际上,中断是我尝试的第一件事。 在ESP8266中,您可以在边缘组织中断,甚至可以在python中为该中断编写处理程序。 在此中断中,您可以更新变量的值。 如果计数器是一个从属设备,则可能就足够了,该设备将一直等待直到被要求提供该值为止。

不幸的是(或幸运的是?)我的设备处于活动状态,它必须使用MQTT协议发送消息并将数据写入EEPROM。 而且这里已经出现了限制-您无法分配内存并在中断中使用大堆栈,这意味着您可以忘记通过网络发送消息。 有像micropython.schedule()这样的包子,它使您可以“立即”运行某种功能,但问题是“有什么意义?”。 突然,我们现在正在发送某种消息,在这里,中断使变量变差了。 或者,例如,一个新的计数器值从服务器到达,而我们仍未记下旧的计数器值。 通常,您需要隔离同步或以其他方式退出。

有时,RuntimeError崩溃:计划堆栈已满,谁知道原因?

通过显式的轮询和uasync,在这种情况下它会更漂亮,更可靠。

我上了一堂小班的EEPROM

 class EEPROM(): i2c_addr = const(80) def __init__(self, i2c): self.i2c = i2c self.i2c_buf = bytearray(4) # Avoid creation/destruction of the buffer on each call def read(self, eeprom_addr): self.i2c.readfrom_mem_into(self.i2c_addr, eeprom_addr, self.i2c_buf, addrsize=16) return ustruct.unpack_from("<I", self.i2c_buf)[0] def write(self, eeprom_addr, value): ustruct.pack_into("<I", self.i2c_buf, 0, value) self.i2c.writeto_mem(self.i2c_addr, eeprom_addr, self.i2c_buf, addrsize=16) 

在python中,直接处理字节很困难,但是字节是写入内存的。 我必须使用ustruct库修复整数和字节之间的转换。

为了不每次都传输I2C对象和存储单元地址,我将其全部封装在一个小巧方便的经典中

 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) 

I2C对象本身就是用这样的参数创建的

 i2c = I2C(freq=400000, scl=Pin(5), sda=Pin(4)) 

我们处理最有趣的事情-通过MQTT与服务器进行通信。 嗯,协议本身不需要实现-在Internet上找到了现成的异步实现 。 在这里我们将使用它。

所有最有趣的信息收集在基于库MQTTClient的类CounterMQTTClient中。 让我们从外围开始

 ##################################### # Class handles both counters and sends their status to MQTT ##################################### class CounterMQTTClient(MQTTClient): blue_led = Pin(2, Pin.OUT, value = 1) button = Pin(0, Pin.IN) hot_counter = Counter(12, EEPROMValue(i2c, EEPROM_ADDR_HOT_VALUE)) cold_counter = Counter(13, EEPROMValue(i2c, EEPROM_ADDR_COLD_VALUE)) 

在这里,创建并配置了灯泡和按钮的引脚以及冷热水表的对象。

使用初始化,并非一切都那么琐碎

  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()) 

要设置mqtt_as库的参数,使用了一个具有不同设置的大型词典-config。 大多数默认设置适合我们,但是许多设置需要显式设置。 为了不直接在代码中注册设置,我将它们存储在文本文件config.txt中。 这样,无论设置如何,都可以更改代码,也可以铆钉几个具有不同参数的相同设备。

最后的代码块运行多个协程以服务各种系统功能。 这是为柜台服务的协程示例

  async def _counter_coro(self, counter, topic): # Publish initial value value = counter.value() await self.publish(topic, str(value)) # Publish each new value while True: value = await counter await self.publish_msg(topic, str(value)) 

在一个循环中,Corutin等待一个新的计数器值,并在其出现后立即使用MQTT协议发送消息。 即使没有水流过计数器,第一段代码也会发送初始值。

MQTTClient基类为其自身提供服务,启动WiFi连接,并在连接丢失时重新连接。 当WiFi连接状态更改时,图书馆通过调用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) 

从示例中确实可以看出该功能。 在这种情况下,它将考虑断开连接的数量(internet_outages)及其持续时间。 恢复连接后,停机时间将发送到服务器。

顺便说一句,最后一个睡眠只需要函数变得异步-在库中通过await调用,并且只能调用主体中具有另一个await的函数。

除了连接到WiFi,还需要与MQTT代理(服务器)建立连接。 库也可以这样做,但是建立连接后,我们就有机会做一些有用的事情。

  async def mqtt_connection_handler(self, client): await client.subscribe(self._mqtt_cold_water_theme) await client.subscribe(self._mqtt_hot_water_theme) 

在这里,我们订阅了几条消息-服务器现在可以通过发送适当的消息来设置当前计数器值。

  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)) 

此函数处理传入的消息,并根据主题(消息名称),更新其中一个计数器的值

几个辅助功能

  # Publish a message if WiFi and broker is up, else discard async def publish_msg(self, topic, msg): self.dprint("Publishing message on topic {}: {}".format(topic, msg)) if not self.internet_outage: await self.publish(topic, msg) else: self.dprint("Message was not published - no internet connection") 

如果建立连接,则此功能发送消息。 如果没有连接,则忽略该消息。

这只是生成和发送调试消息的便捷功能。

  async def publish_debug_msg(self, subtopic, msg): await self.publish_msg("{}/{}".format(self._mqtt_debug_water_theme, subtopic), str(msg)) 

文字太多,但我们没有使LED闪烁。 在这里

  # Blink flash LED if WiFi down async def _heartbeat(self): while True: if self.internet_outage: self.blue_led(not self.blue_led()) # Fast blinking if no connection await asyncio.sleep_ms(200) else: self.blue_led(0) # Rare blinking when connected await asyncio.sleep_ms(50) self.blue_led(1) await asyncio.sleep_ms(5000) 

我提供了2种眨眼模式。 如果连接丢失(或正在建立),则设备将快速闪烁。 如果建立连接,则设备每5秒闪烁一次。 如有必要,您可以在此处实施其他闪烁模式。

但是LED如此令人纵容。 我们仍然向显示器挥手。

  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) 

这就是我所说的-协程如何简单和方便。 此小功能描述所有用户交互。 Corutin只是等待按钮按下,然后打开显示器3秒钟。 显示屏显示当前的仪表读数。

还有一些小事情。 这是运行整个服务器场的功能(重新)。 主循环仅处理每分钟发送一次各种调试信息的问题。 总的来说,我照原样引用-我认为特别是不必评论

  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): # Loop forever mins = 0 while True: gc.collect() # For RAM stats. mem_free = gc.mem_free() mem_alloc = gc.mem_alloc() try: await self.publish_debug_msg("Uptime", mins) await self.publish_debug_msg("Repubs", self.REPUB_COUNT) await self.publish_debug_msg("Outages", self.internet_outages) await self.publish_debug_msg("MemFree", mem_free) await self.publish_debug_msg("MemAlloc", mem_alloc) except Exception as e: self.dprint("Exception occurred: ", e) mins += 1 await asyncio.sleep(60) 

好了,还有更多设置和常量可以完成描述

 ##################################### # Constants and configuration ##################################### config['keepalive'] = 60 config['clean'] = False config['will'] = ('/ESP/Wemos/Water/LastWill', 'Goodbye cruel world!', False, 0) MQTTClient.DEBUG = True EEPROM_ADDR_HOT_VALUE = const(0) EEPROM_ADDR_COLD_VALUE = const(4) 

这一切开始

 client = CounterMQTTClient() loop = asyncio.get_event_loop() loop.run_until_complete(client.main()) 


我记忆中的东西变成了


因此,所有代码都在那里。 我使用ampy实用程序上传了文件-它允许将它们上传到内部闪存驱动器(ESP-07本身中的一个),然后从程序中以普通文件的形式进行访问。 我在那里上传了我使用的mqtt_as,uasyncio,ssd1306和集合库(在mqtt_as中使用)。

我们开始并...我们收到MemoryError。 而且,我越想确切地了解内存在哪里泄漏,就越安排打印调试,越早发生此错误。 一小段gugelezh使我了解到,原则上,微控制器只有30kb的内存,其中65kb的代码(以及库)根本无法容纳。

但是有办法。 事实证明,micropython不会直接从.py文件执行代码-该文件会先编译。 然后直接在微控制器上编译,转换为字节码,然后存储在内存中。 好了,要使编译器正常工作,您还需要一定数量的RAM。

诀窍是从资源密集型编译中节省微控制器。 您可以在大型计算机上编译文件,然后将完成的字节码填写到微控制器中。 为此,请下载micropython固件并构建mpy-cross实用程序

我没有编写Makefile,而是手动检查并编译了所有必要的文件(包括库),如下所示

 mpy-cross water_counter.py 

它仅保留上传扩展名为.mpy的文件,而不会忘记首先从设备的文件系统中删除相应的.py。

我在程序(IDE?)ESPlorer中进行了所有开发。 它允许您将脚本上传到微控制器并立即执行它们。 就我而言,所有对象的所有逻辑和创建都位于文件water_counter.py(.mpy)中。 但是,要使所有这些自动开始,首先必须有一个名为main.py的文件。 它应该完全是.py,而不是预编译的.mpy。 这是其琐碎的内容

 import water_counter 

我们开始-一切正常。 但是危险地是很少有可用内存-大约1kb。 我仍然有计划扩展设备的功能,而这千字节显然对我来说还不够。 但是事实证明,有一个出路。

就是这个 即使文件被编译为字节码并位于内部文件系统上,实际上它们仍然被加载到RAM中并从那里执行。 但是事实证明,micropython能够直接从闪存执行字节码,但是为此,您需要将其直接嵌入到固件中。 这并不困难,尽管在我的上网本上花费了相当多的时间(只有在那里我才有了Linux)。

算法如下:

  • 下载并安装ESP Open SDK 。 这个东西为ESP8266下的程序构建了一个编译器和库。 它是根据项目主页上的说明进行组装的(我选择设置STANDALONE = yes)
  • 下载Micropython排序
  • 将必要的库拖放到micropython树内的端口/ esp8266 /模块中
  • 我们按照ports / esp8266 / README.md文件中的说明组装固件。
  • 将固件倒入微控制器(我在Windows上使用ESP8266Flasher程序或Python esptool进行此操作)

就是这样,现在“导入ssd1306”将直接从固件中引发代码,而RAM不会花在此上。 使用此技巧,我仅将库代码下载到固件中,而主程序代码是从文件系统执行的。 这使您无需重新编译固件即可轻松修改程序。 目前,我大约有8.5kb的可用RAM。 将来,这将允许实现许多不同的有用功能。 好吧,如果根本没有足够的内存,那么您也可以将主程序推送到固件中。

现在该怎么办?


好的,焊接好硬件,写入固件,打印盒子,将设备固定在墙上,并用灯泡快乐地闪烁。 但是,尽管这全是一个黑匣子(从字面意义和象征意义上来说),但它的意义仍然不够。 现在该处理发送到服务器的MQTT消息了。

我的“智能家居” 在Majordomo系统运转 。 MQTT模块可以直接使用,也可以从附加组件市场轻松安装-我不再记得它来自哪里。 MQTT的事情不是自给自足的-所谓的 代理-接收,排序MQTT消息并将其重定向到客户端的服务器。 我使用mosquitto,它(与majordomo一样)都在同一台上网本上运行。

设备至少发送一次消息后,该值将立即显示在列表中。



这些值现在可以与系统对象相关联,可以在自动化脚本中使用它们并进行各种分析-所有这些都不在本文的讨论范围之内。 谁对majordomo系统感兴趣,我可以推荐“镜头电子产品”频道 -一个朋友还建了一座智能房屋,并就如何安装该系统进行了清晰的讨论。

我将只显示几个图表。 这是每日价值的简单图表。


可以看出,几乎没有人在晚上用水。 几次有人去洗手间,看起来反渗透过滤器每晚要吸几升水。 早晨,消费量显着增加。 通常,我使用锅炉中的水,但后来我想洗澡并暂时改用城市热水-这在底部图表中也清晰可见。

从这个时间表中,我了解到上厕所需要6到7升水,淋浴-20到30升,洗碗大约需要20升,洗澡需要160升。 一天,我一家人的消费量约为500-600升。

最好奇的是,您可以查看每个值的条目



从这里我得知,打开水龙头,水在5秒钟内以大约1升的速度流动。

但是以这种形式,统计数据可能不太方便查看。 在majordomo,仍然有机会按日,周和月查看消费图表。 例如,列中的消耗量图



到目前为止,我只有一个星期的数据。 在一个月内,此图表将更具指示性-每天将有一个单独的列。 手动输入的值的调整会稍微破坏图片(最大列)。 目前尚不清楚我是否错误地将第一个值设置得少了近一个立方,或者这是否是固件中的错误,并非所有公升都被抵消了。 这需要更多时间。

仍然有必要使图形本身变白,变白,变色。 也许我还会为调试目的建立一个内存消耗的图表-突然那里泄漏了一些东西。 也许我会以某种方式显示互联网不可用的时期。 尽管所有这些都在思想层面上旋转。

结论


今天,我的公寓变得更聪明了。 有了这么小的设备,对我来说监控房屋的用水量将更加方便。 如果早些时候我很生气“再次发现他们一个月内消耗了很多水”,现在我可以找到这种消耗的来源。

如果距离仪表本身一米,那么在屏幕上观看读数对于某人来说似乎很奇怪。 但是在不久的将来,我计划搬到另一间公寓,那里将有几个立管,而电表本身很可能位于平台上。 因此,远程阅读设备将非常有帮助。

我还计划扩展设备的功能。 我已经在寻找电动阀了。 现在,要切换锅炉用水,我需要在一个无法接近的利基市场中转动3个水龙头。 使用带有适当指示的一个按钮来执行此操作将更加方便。 好吧,当然,值得采取防止泄漏的保护措施。

在本文中,我告诉了我基于ESP8266的设备版本。 我认为,我使用协程获得了一个非常有趣的micropython固件版本-简单漂亮。 , . , , , .

.



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


All Articles