Когда-то системы домашней автоматизации, или как их часто называют “умный дом”, были жутко дорогими и их могли позволить себе лишь богачи. Сегодня на рынке можно найти достаточно бюджетные комплекты с датчиками, кнопками/выключателями и исполнительными устройствами для управлением освещением, розетками, вентиляцией, водоснабжением и другими потребителями. И даже самый криворукий DIY-шник может приобщиться к прекрасному и за недорого собирать устройства для умного дома.



Как правило предлагаемые устройства это либо датчики, либо исполнительные механизмы. Они позволяют легко реализовать сценарии вроде “при срабатывании датчика движения включить свет” или “выключатель возле выхода тушит свет во всей квартире”. Но вот с телеметрией как-то не сложилось. В лучшем случае это график температуры и влажности, или мгновенная мощность в конкретной розетке.

Недавно я себе поставил счетчики воды с импульсным выходом. Через каждый литр пробежавший через счетчик срабатывает геркон и замыкает контакт. Осталось дело за малым — прицепиться к проводам и попробовать из этого получить пользу. Например, анализировать потребление воды по часам и дням недели. Ну а если стояков для воды в квартире несколько, то удобнее видеть все текущие показатели на одном экране, чем лазить по труднодоступным нишам с фонариком.

Под катом мой вариант устройства на базе ESP8266, которое считает импульсы со счетчиков воды и по MQTT отправляет показания на сервер умного дома. Программировать будем на micropython с использованием библиотеки uasyncio. При создании прошивки я наткнулся на несколько интересных сложностей, о которых также расскажу в этой статье. Поехали!

Схема




Сердцем всей схемы является модуль на микроконтроллере ESP8266. Изначально планировался ESP-12, но мой оказался бракованный. Пришлось довольствоваться модулем ESP-07, который был в наличии. Благо они одинаковые и по выводам, и по функционалу, разница только в антенне — у ESP-12 она встроенная, а у ESP-07 — внешняя. Впрочем, даже без антенны WiFi сигнал в моей ванной ловится нормально.

Обвязка модуля стандартная:

  • кнопка ресет с подтяжкой и конденсатором (хотя и то и другое уже есть внутри модуля)
  • Сигнал enable (CH_PD) подтянут к питанию
  • GPIO15 подтянут к земле. Это нужно только на старте, но мне все равно нечего на эту ногу цеплять больше не нужно

Для перевода модуля в режим прошивки нужно замкнуть GPIO2 на землю, а чтобы было удобнее я предусмотрел кнопку Boot. В нормальном состоянии этот пин подтягивается к питанию.

Состояние линии GPIO2 проверяется только вначале работы — при подаче питания или сразу после ресета. Так модуль либо загружается как обычно, либо переходит в режим прошивки. После загрузки этот вывод можно использовать как обычный GPIO. Ну а раз там уже есть кнопка, то можно повесить на нее какую нибудь полезную функцию.

Для программирования и отладки я буду использовать UART, который вывел на гребенку. Когда нужно — я просто подключаю туда USB-UART переходник. Нужно только не забывать, что питается модуль от 3.3В. Если забыть переключить переходник на это напряжение и подать 5В, то модуль скорее всего сгорит.

С электричеством в ванной у меня проблем нет — розетка расположена примерно в метре от счетчиков, так что запитывать буду от 220В. В качестве источника питания у меня будет трудится небольшой блочок HLK-PM03 от Tenstar Robot. Лично у меня туго с аналоговой и силовой электроникой, а тут готовый блок питания в маленьком корпусе.

Для сигнализации режимов работы я предусмотрел светодиод, подключенный к GPIO2. Впрочем распаивать я его не стал, т.к. в модуле ESP-07 уже есть светодиод, причем подключенный к тому же GPIO2. Но на плате пускай будет — вдруг я захочу вывести этот светодиод на корпус.

Переходим к самому интересному. У счетчиков воды нет никакой логики, у них нельзя спросить текущие показания. Единственное что нам доступно это импульсы — замыкание контактов геркона каждый литр. Выводы герконов у меня заведены в GPIO12/GPIO13. Подтягивающий резистор я буду включать программно внутри модуля.

Изначально я забыл предусмотреть резисторы R8 и R9 и в моем варианте платы их нет. Но раз я уже выкладываю схему на всеобщее обозрение, то стОит исправить эту оплошность. Резисторы нужны, чтобы не спалить порт в случае если прошивка глюканет и выставит единицу на пине, а геркон закоротит эту линию на землю (с резистором потечет максимум 3.3В/1000Ом = 3.3мА).

Пора подумать что делать если пропадет электричество. Первый вариант — на старте запрашивать у сервера начальные значения счетчиков. Но это потребовало бы существенного усложнения протокола обмена. Более того, работоспособность устройства в таком случае зависит от состояния сервера. Если бы после отключения света сервер не завелся (или завелся позже), то счетчик воды не смог бы запросить начальные значения и работал бы неверно.

Поэтому я решил реализовать сохранение значений счетчиков в микросхеме памяти, подключенной по I2C. Особых требований по размеру флеш памяти у меня нет — нужно сохранять всего 2 числа (количество литров по счетчикам горячей и холодной воды). Даже самый маленький модуль подойдет. А вот на количество циклов записи нужно обратить внимание. У большинства модулей это 100 тыс циклов, у некоторых до миллиона.

Казалось бы миллион это много. Но я за 4 года проживания в своей квартире потребил чуть более 500 кубов воды, это 500 тыс литров! И 500 тыс записей во флеш. И это только холодная вода. Можно, конечно, перепаивать микросхему каждые пару лет, но оказалось есть микросхемы FRAM. С точки зрения программирования это тот же самый I2C EEPROM, только с ооооочень большим количеством циклов перезаписи (сотни миллионов). Вот только пока все никак не доеду до магазина с такими микросхемами, поэтому пока постоит обычная 24LC512.

Печатная плата


Изначально я планировал делать плату в домашних условиях. Потому плата проектировалась как односторонняя. Но продолбавшись битый час с c лазерным утюгом и паяльной маской (без нее как-то не comme il faut), я все же решил заказать платы у китайцев.



Уже практически перед заказом платы я сообразил, что помимо микросхемы флеш памяти на шину I2C можно подцепить что нибудь еще полезное, например дисплей. Что именно на него выводить — пока еще вопрос, но развести на плате нужно. Ну а раз я собрался платы заказывать на фабрике, то ограничивать себя односторонней платой уже не было смысла, поэтому линии на I2C — единственные на задней стороне платы.

С односторонней разводкой также связан был один большой косяк. Т.к. плата рисовалась односторонняя, то дорожки и SMD компоненты планировалось размещать с одной стороны, а выводные компоненты, разъемы и блок питания с другой. Когда через месяц я получил платы, то забыл про изначальный план и распаял все компоненты на лицевой стороне. И только когда дело дошло до припаивания блока питания выяснилось, что плюс и минус разведены наоборот. Пришлось колхозить перемычками. На картинке выше я уже поменял разводку, но земля перекидывается из одной части платы в другую через выводы кнопки Boot (хотя можно было бы и на втором слое дорожку провести).

Получилось вот так



Корпус


Следущий шаг — корпус. При наличии 3D принтера это не проблема. Особо не заморачивался — просто нарисовал коробку нужного размера и сделал вырезы в нужных местах. Крышка крепится к корпусу на маленьких саморезах.



Я уже упоминал, что кнопка Boot может быть использована как кнопка общего назначения — вот ее и выведем на переднюю панель. Для этого я нарисовал специальный “колодец” где живет кнопка.



Внутри корпуса также располагаются пеньки, на которые устанавливается плата и фиксируется единственным винтом М3 (на плате больше места не оказалось)

Дисплей подбирал уже когда напечатал первый примерочный вариант корпуса. Стандартный двухстрочник в этот корпус не влазил, зато в сусеках обнаружился OLED дисплей SSD1306 128x32. Маловат, но мне на него не каждый день глазеть — покатит.

Прикидывая и так и эдак как от него будут проложены провода решил прилепить дисплей посреди корпуса. Эргономика, конечно, ниже плинтуса — кнопка сверху, дисплей снизу. Но я уже говорил, что идея прикрутить дисплей пришла слишком поздно и лень было переразводить плату, чтобы переместить кнопку.

Устройство в сборе. Дисплейный модуль приклеен на сопли термоклей





Конечный результат можно увидеть на КДПВ

Прошивка


Перейдем к программной части. Для вот таких небольших поделок мне очень нравится использовать язык Python (micropython)- код получается очень компактный и понятный. Благо тут нет необходимости спускаться на уровень регистров с целью выжимать микросекунды — все можно сделать из питона.

Вроде бы все просто, да не очень — в устройстве намечается несколько независимых функций:

  • Пользователь тыкает в кнопку и смотрит на дисплей
  • Литры тикают и обновляют значения во флеш памяти
  • Модуль следит за сигналом WiFi и переконнекчивается если нужно
  • Ну а без моргающей лампочки вообще нельзя

Нельзя допустить что одна функция не работала, если другая по какой-то причине тупит. Я уже наелся кактусов в других проектах и теперь так и вижу глюки в стиле “пропустили очередной литр, потому, что в этот момент обновлялся дисплей” или “пользователь ничего не может сделать пока модуль коннектится к WiFi”. Конечно, некоторые вещи можно делать через прерывания, но можно упереться в ограничение по длительности, вложенности вызовов или неатомарного изменения переменных. Ну и код который занимается всем и сразу быстро превращается в кашу.

В проекте посерьезнее я использовал классическую вытесняющую многозадачность и FreeRTOS, но в данном случае гораздо более подходящей оказалась модель сопрограмм (coroutines) и библиотеки uasync . Причем питоновская реализация корутин просто бомбовая — для программиста все сделано просто и удобно. Просто пиши себе логику, только скажи в каких местах между потоками переключаться можно.

Различия между вытесняющей и конкурентной многозадачностью предлагаю изучить факультативно. А сейчас давайте, наконец, перейдем к коду.

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

Задержка в 25мс нужна для фильтрации дребезга контактов, а заодно она регулирует как часто просыпается задача (пока эта задача спит — работают другие задачи). Каждые 25мс функция просыпается, проверяет пин и если контакты геркона замкнулись, то значит через счетчик прошел очередной литр и это нужно обработать.

    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

Ну а теперь воспользуемся прелестями питона и библиотеки uasync и сделаем объект счетчика waitable (как это на русский перевести-то? Тот, которой можно ожидать?)

    def __await__(self):
        while not self._value_changed:
            yield from asyncio.sleep(0)

        return self.value()

    __iter__ = __await__  

Это такая удобная функция, которая ждет пока значение счетчика не обновится — функция время от времени просыпается и проверяет флажок _value_changed. Прикол этой функции в том, что вызывающий код может уснуть на вызове этой функции и спать до получения нового значения.

А как же прерывания?
Да, в этом месте вы меня можете потроллить, мол сам же сказал про прерывания, а на деле устроил тупой опрос пина. На самом деле прерывания это первое, что я попробовал. В ESP8266 можно организовать прерывание по фронту, и даже написать обработчик этого прерывания на питоне. В этом прерывании можно обновлять значение переменной. Наверное, этого бы хватило будь счетчик ведомым устройством — таким, которое ждет, пока у него не спросят это значение.

К сожалению (или к счастью?) мое устройство активное, оно должно само слать сообщения по протоколу MQTT и записывать данные в EEPROM. И тут уже вступают ограничения — в прерываниях нельзя выделять память и использовать большой стек, а значит об отправке сообщений по сети можно забыть. Есть плюшки типа micropython.schedule(), которые позволяют запустить какую нибудь функцию “как только так и сразу”, но возникает вопрос “а толку-то?”. Вдруг мы прямо сейчас отправляем какое нибудь сообщение, а тут вклинивается прерывание и портит значения переменных. Или, например, с сервера приехало новое значение счетчика пока мы еще старое недозаписали. В общем, нужно городить синхронизацию или выкручиваться как-то по другому.

А еще время от времени вылетает RuntimeError: schedule stack full и кто его знает почему?

С явным опросом и 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)

В питоне напрямую с байтами работать сложновато, а ведь именно байты записываются в память. Пришлось городить конвертацию между целым числом и байтами с использованием библиотеки 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. Ну сам протокол реализовывать не нужно — на просторах интернета нашлась готовая асинхронная реализация. Вот ее и будем использовать.

Все самое интересное собрано в классе CounterMQTTClient, который базируется на библиотечном MQTTClient. Начнем с периферии

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

Корутина в цикле ждет нового значения счетчика и как только оно появилось — отправляет сообщение по протоколу 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) и их длительность. При восстановлении соединения на сервер отправляется время простоя.

Кстати говоря, последний sleep нужен только для того, чтобы функция стала асинхронной — в библиотеке она вызывается через 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))

Так много текста, а мы еще не моргали светодиодом. Вот

    # 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 секунд. При необходимости тут можно реализовать и другие режимы моргания.

Но светодиод это так, баловство. Мы же еще на дисплей замахнулись.

    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)

Вот это то, о чем я говорил — как просто и удобно с корутинами. Эта маленькая функция описывает ВСЁ взаимодействие с пользователем. Корутина просто ждет нажатия кнопки и включает дисплей на 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 и collections (используется внутри mqtt_as).

Запускаем и… Получаем MemoryError. Причем чем больше я пытался понять где именно утекает память, чем больше я расставлял дебаг принтов, тем раньше возникала эта ошибка. Короткий гуглеж привел меня к пониманию, что в микроконтроллере в принципе всего 30кб памяти в которые 65 кб кода (вместе с библиотеками) ну никак не помещаются.

Но выход есть. Оказывается micropython не исполняет код напрямую из .py файла — этот файл сначала компилируется. Причем компилируется он прямо на микроконтроллере, превращается в байткод, который потом хранится в памяти. Ну и для работы компилятора тоже нужен определенный объем оперативки.

Трюк заключается в том, чтобы избавить микроконтроллер от ресурсоемкой компиляции. Можно скомпилировать файлы на большом компьютере, а в микроконтроллер залить уже готовый байткод. Для этого нужно скачать прошивку micropython и собрать утилиту mpy-cross.

Я не стал писать Makefile, а вручную прошелся и скомпилировал все нужные файлики (включая библиотеки) примерно так

mpy-cross water_counter.py

Осталось только залить файлики с расширением .mpy, не забыв предварительно удалить соответствующие .py с файловой системы устройства.

Все разработку я вел в программе (IDE?) ESPlorer. Она позволяет заливать скрипты в микроконтроллер и тут же их выполнять. В моем случае вся логика и создание всех объектов находятся находится в файле water_counter.py (.mpy). Но чтобы все это запускалось автоматически на старте должен быть еще файл с именем main.py. Причем это должен быть именно .py, а не пред-компилированный .mpy. Вот его тривиальное содержимое

import water_counter

Запускаем — все работает. Но свободной памяти угрожающе мало — порядка 1кб. У меня еще есть планы по расширению функциональности устройства, и этого килобайта мне явно будет маловато. Но оказалось и на этот случай есть выход.

Дело вот в чем. Даже при том, что файлы скомпилированы в байткод и находятся на внутренней файловой системе, на деле они все равно загружаются в оперативную память и выполняются оттуда. Но оказывается micropython умеет выполнять байткод прямо из флеш памяти, но для этого нужно встроить его непосредственно в прошивку. Это не сложно, хотя на моем нетбуке это заняло прилично времени (только там у меня оказался линукс).

Алгоритм такой:

  • Скачать и установить ESP Open SDK. Эта штука собирает компилятор и библиотеки для программ под ESP8266. Собирается по инструкции на главной страничке проекта (я выбирал установку STANDALONE=yes)
  • Скачать сорцы micropython
  • Нужные библиотеки закинуть в ports/esp8266/modules внутри дерева micropython
  • Собираем прошивку согласно инструкции в файле ports/esp8266/README.md
  • Заливаем прошивку в микроконтроллер (я это делаю на винде программами ESP8266Flasher или питоновским esptool’ом)

Все, теперь 'import ssd1306' будет поднимать код напрямую из прошивки и оперативная память под это расходоваться не будет. Таким трюком я залил в прошивку только код библиотек, тогда как основной код программы у меня выполняется с файловой системы. Это позволяет легко модифицировать программу не перекомпилируя прошивку. На данный момент у меня свободно около 8.5кб ОЗУ. Это позволит реализовать еще довольно много разного полезного функционала в будущем. Ну а если памяти будет совсем не хватать, то можно и основную программу затолкать в прошивку.

И что с этим теперь делать?


Ок, железка спаяна, прошивка написана, коробка напечатана, устройство прилеплено на стену и радостно моргает лампочкой. Но пока это все черный ящик (в прямом и переносном смысле) и толку от него еще маловато. Пора что нибудь сделать с MQTT сообщениями, которые шлются на сервер.

Мой “умный дом” крутится на системе Majordomo. Модуль MQTT то ли есть из коробки, то ли легко устанавливается из маркета дополнений — уже не помню откуда он у меня взялся. MQTT штука не самодостаточная — нужен т.н. брокер — сервер, который принимает, сортирует и перенаправляет клиентам MQTT сообщения. Я использую mosquitto, который (как и majordomo) крутится все на том же нетбуке.

После того, как устройство хоть раз отправит сообщение значение тут же появится в списке.



Эти значения теперь можно связать с объектами системы, их можно использовать в сценариях автоматизации и подвергать различному анализу — все это out of scope этой статьи. Кому интересна система majordomo могу порекомендовать канал Электроника В Объективе — товарищ тоже строит умный дом и доходчиво рассказывает про настройку системы.

Покажу лишь пару графиков. Это простой график значений за сутки


Видно, что ночью водой почти никто не пользовался. Пару раз кто-то сходил в туалет, и, похоже фильтр обратного осмоса пару литров за ночь посасывает. Утром потребление существенно возрастает. Обычно я пользуюсь водой из бойлера, но тут я захотел принять ванную и временно переключил на городскую горячую воду — это также хорошо заметно на нижнем графике.

Из этого графика я узнал, что сходить в туалет это 6-7л воды, принять душ — 20-30л, помыть посуду около 20л, а чтобы принять ванную нужно 160л. За день моя семья потребляет где-то около 500-600л.

Для особо любознательных можно заглянуть в записи по каждому отдельному значению



Отсюда я узнал что при открытом кране вода течет со скоростью примерно 1л за 5с.

Но в таком виде статистику, наверное, не очень удобно смотреть. В majordomo есть еще возможности смотреть графики потребления по дням, неделям и месяцам. Вот, например, график потребления в столбиках



Пока у меня данных только за неделю. Через месяц этот график будет более показательным — каждому дню будет соответствовать отдельный столбик. Немного картину портят корректировки значений, которые я ввожу вручную (самый большой столбик). И пока не ясно, то ли я неправильно задал самые первые значения почти на куб меньше, то ли это баг в прошивке и не все литры пошли в зачет. Нужно больше времени.

Над самими графиками еще поколдовать нужно, побелить, покрасить. Возможно я также буду в отладочных целях строить график потребления памяти — вдруг там что-то утекает. Возможно буду как-то отображать периоды, когда отсутствовал интернет. Пока все это крутится на уровне идеи.

Заключение


Сегодня моя квартира стала чуточку умнее. С таким небольшим устройством мне будет удобнее следить за потреблением воды в доме. Если раньше я возмущался “опять много воды за месяц потребили”, то теперь я смогу найти источник этого потребления.

Кому-то покажется странным смотреть показания на экране, если он в метре от самого счетчика. Но в не очень отдаленном будущем я планирую переселиться в другую квартиру, где будет несколько стояков воды, а сами счетчики, скорее всего, будут расположены на лестничной площадке. Так что устройство удаленного снятия показаний будет весьма кстати.

Функциональность устройства я тоже планирую расширять. Я уже присматриваюсь к моторизованным вентилям. Сейчас для переключения бойлер-городская вода мне нужно поворачивать 3 крана в труднодоступной нише. Было бы гораздо удобнее делать это одной кнопкой с соответствующей индикацией. Ну и, само собой, защиту от протечек реализовать стОит.

В статье я рассказал свой вариант устройства на базе ESP8266. На мой взгляд у меня получился весьма интересный вариант прошивки на micropython с использованием корутин — просто и симпатично. Я постарался описать множество нюансов и косяков, с которыми столкнулся походу. Возможно я слишком детально все описывал, лично мне как читателю проще промотать лишнее, чем потом додумывать то, что было недосказано.

Как всегда я открыт для конструктивной критики.

Исходный код
Схема и плата
Модель корпуса

Комментарии (57)


  1. zerg59
    05.04.2018 08:54

    Я, имея в виду перспективу доделать умный дом, купил пару водосчетчиков Элехант. Они по блютусу отдают фактические показания. Приедет малина, буду её учить показания снимать.


    1. grafalex Автор
      05.04.2018 20:38

      Погуглил, прикольная штука. Жаль у нас в окрестностях такое не продают :(


      1. zerg59
        05.04.2018 21:31

        я у них на сайте заказывал. Ещё и календарь прислали


        1. grafalex Автор
          05.04.2018 21:39

          Я не из России. Боюсь мою местность это будет сначана проблематично переправить, далее не факт, что местный водоканал согласится это взять на учет, а потом непонятно как обслуживать, поверять, и т.п.


  1. dmsav
    05.04.2018 09:26

    Очень круто, а главное просто.
    Еще бы по корпусу пройтись наждачной бумагой и отшлифовать, вообще конфетка будет.


    1. grafalex Автор
      05.04.2018 09:32

      По корпусу я прошелся наждаком, но полностью зашлифовывать слои не стал — мне так больше нравится. На деле он и есть как конфетка. Просто корпус черный, покрашен глянцевой краской и когда фотографируешь со вспышкой получается вот так как на КДПВ (т.е. гораздо хуже чем на самом деле)


      1. dmsav
        05.04.2018 09:38

        А, тогда понятно)


      1. Prometheus
        05.04.2018 13:24

        корпус черный, покрашен глянцевой краской и когда фотографируешь со вспышкой получается вот так

        Поляризационный фильтр для объектива вам в помощь!


        1. grafalex Автор
          05.04.2018 21:41

          Эх, тыщу лет поляриком не пользовался. Картинку обновил. Впрочем, существенно лучше не стало :)


  1. qwerty1023
    05.04.2018 10:03

    Ох уж эти языки высокого уровня и решения задачи, что называется, «в лоб»… Начнем с того, что если бы устройство было «ведомым», то оно могло бы себе позволить с максимальной скоростью опрашивать состояние пина и надеяться что оно не пропустит изменение его состояния. А в данном случае опрос пина это одна из задач, и состояние пина может меняться довольно непредсказуемо. Самое логичное и простое — это по прерыванию инкрементировать счетчик в ОЗУ и установит флажок, что при следующем удобном случае нужно обновить значения в ЕЕПРОМ. Потом про сам ЕЕПРОМ. Где проверка целостности данных? И можно подумать над логикой сохранения значений. Один метод это использовать «плавающую» запись по всему адресному пространству для минимизации износа конкретных ячеек, другой метод мониторить питание (тогда нужно отказаться от внешнего аппаратного сброса и, например, добавить оптопару для контроля наличия 220В) и писать в ЕЕПРОМ только при пропадании питания.


    1. Polarisru
      05.04.2018 13:22
      +1

      +1, надежность на уровне станции юных техников


    1. grafalex Автор
      05.04.2018 21:59

      Спасибо за конструктивный комментарий. Постараюсь учесть во второй версии.

      Пара мыслей, отчасти в оправдание
      — литры пробегают достаточно медленно — 5 сек на литр. Импульс там порядка секунды — такое сложно пропустить
      — выбор между работой по прерываниям и опросом я сделал до того как прикрутил туда uasyncio. Без «многопоточности» там архитектура совсем другая была и с прерываниями было жутко неудобно. Теперь можно пересмотреть и вернуться назад к прерываниям.
      — На счет ЕЕПРОМ. Как можно проверить целостность двух чисел?
      — На износ ячеек пофиг — я ж туда FRAM собрался ставить. Конкретнее FM24CL04 — обещают неограниченное количество циклов перезаписи
      — Про мониторинг питания. Скорее всего в следующей версии будет что нибудь с батарейно-розеточным питанием


      1. qwerty1023
        05.04.2018 22:16

        литры пробегают достаточно медленно — 5 сек на литр. Импульс там порядка секунды — такое сложно пропустить

        А как часто проверяется состояние пина? Видишь сколько лишней работы делает микроконтроллер?

        На счет ЕЕПРОМ. Как можно проверить целостность двух чисел?

        CRC8 например.

        На износ ячеек пофиг

        Любая техническая задача имеет множество решений. FRAM вещь хорошая, но можно вполне решать проблему и имеющейся в данный момент микросхемой поменяв алгоритм работы.


        1. grafalex Автор
          05.04.2018 23:22

          Как мы оба видим путей решения одной и той же задачи может быть много :)
          В целом замечание принял.


  1. smart_alex
    05.04.2018 10:29
    -1

    Отличный проект, спасибо за проделанную работу и статью. Но если бы я автоматизировал счётчики, то сделал бы батарейный блок на nRF24L01 (на мой взгляд лучше лишний раз не использовать 220В в санузле), а основным сделал бы проводной или беспроводной АМС сервер. Вот здесь описание кучи подобных реализованных проектов.

    https://hi-lab.ru/arduino-mega-server/ams-pro


    1. grafalex Автор
      05.04.2018 22:05

      Спасибо за наводку.

      батарейный блок на nRF24L01

      А насколько хватит батареи? И правильно ли я понимаю, что нужен будет еще AMS сервер (т.е. 2 устройства вместо одного)?

      лучше лишний раз не использовать 220В

      Розетка там все равно есть для бойлера. Это не влажная зона а сам БП залит компаундом по самое не хочу.


      1. smart_alex
        06.04.2018 06:48

        Я не делал батарейные nRF24 сенсоры для снятий показаний со счётчиков воды, но зато делал аналогичные сенсоры температуры, контактные, протечки и прочие — по моим прикидкам двух батареек АА должно хватить минимум на год.

        Что касается АМС сервера, то, на мой взгляд, устройство, описанное в статье, имеет серьёзный архитектурный недостаток. Поясняю. В «умном доме» могут быть не только счётчики воды, а ещё 20-30 других датчиков (температуры, задымления и т. д.). И использовать на каждый ESP8266 это совершенно неправильно из-за привязки каждого датчика к 220В и засорения Wi-Fi. Гораздо более правильно использовать 1 сервер и подцепить к нему все беспроводные (батарейные) сенсоры.


        1. e_butcher
          06.04.2018 08:58

          использовать 1 сервер и подцепить к нему все беспроводные (батарейные) сенсоры

          И как они будут с сервером общаться, ведь снова по Wi-Fi? Почему вы думаете, что в этом случае засорения не будет?


          1. smart_alex
            06.04.2018 09:51

            Да как же по Wi-Fi? По nRF24 конечно. Я же вроде несколько раз это объяснил. Посмотрите схемы сетей по вышеприведённой ссылке — там хорошо видно, что на 1 АМС сервер приходится по несколько nRF24 соединений датчиков и актуаторов. И всё это, само-собой, прекрасно работает на практике в реальных проектах.


            1. e_butcher
              06.04.2018 09:53

              Ок, не по Wi-Fi, а по радио. Но частота же тоже 2.4ГГц, значит по идее так же эфир забивается?


              1. smart_alex
                06.04.2018 10:01

                Вы немного путаете тёплое с мягким. Во первых, 2,4 ГГц — это обобщённое название диапазона и частоты и ширина полосы и временные характеристики у этих сигналов разные. Во-вторых забивание Wi-Fi это больше проблема работы большого количества Wi-Fi клиентов с роутером, а не частотные и временные помехи друг для друга. Ну, и как я уже сказал, это на «ура» работает на практике — проверено на десятках инсталляций.


        1. grafalex Автор
          06.04.2018 15:25

          Решение на базе АМС также имеет серьезный архитектурный недостаток.

          В моем умном доме уже есть пусть и не 20-30 датчиков, а всего до десятка и используются фабричные от Xiaomi. У них это самое батарейное питание, отдельный радиоканал и свой шлюс ZigBee-WiFi — примерно то, что вы и предлагаете.

          К этому всему прилагается еще сервер majordomo, который занимается более высокоуровневой автоматизацией (в связке с Xiaomi). Городить здесь третий узел на базе AMS помоему уже излишне.

          Вот если бы Xiaomi свой Zigbee открыл…


          1. smart_alex
            06.04.2018 15:34

            Ну, эти все технологии (ESP8266, nRF24 и прочий «умный дом») настолько гибкие, что можно делать как хочешь, вариантов ровно миллион — каждый может выбрать архитектуру под себя и в соответствии со своими потребностями.

            В обойме АМС решений есть и сервер MajorDoMo:

            https://hi-lab.ru/arduino-mega-server/ams-pro/projects/main-server


            1. grafalex Автор
              06.04.2018 16:03

              Спасибо, посмотрю


          1. Mogwaika
            06.04.2018 16:20

            Если у вас есть Xiaomi и majordomo, почему не прицепили дверные датчики к счётчикам, как тут часто советуют?


            1. grafalex Автор
              06.04.2018 16:30

              Хм, отличная мысль! А ссылочку не запомнили на удачные решения?

              Вот только счетчик у меня в железном кожухе, боюсь магнит недобьет.


              1. Mogwaika
                06.04.2018 16:35

                Так вы параллельно геркону припаяйте, там же сухой контакт вроде бы, магнит не нужен.
                Я видел только в комментах тут и на форуме majordomo, инструкции вроде не было. Себе сделать всё некогда…


                1. grafalex Автор
                  06.04.2018 19:07

                  Хм, так просто :)
                  надо бы прикупить парочку таких датчиков на поиграться…


  1. BurlakovSG
    05.04.2018 12:36
    +1

    Я сделал из комбинации Attiny4313 и ESP модуля Wemos D1 mini Pro, которые общаются между собой по UART. Тинька отвечает за считывание импульсов и сохранение показаний в своей EEPROM (запись в память плавающая — не более 65000 раз в одну ячейку, что позволяет забыть об износе). Если пропадает внешнее питание, то Тинька питается от резервного источника питания (в моём случае это батарейка CR2032). Модуль ESP отвечает за отображение показаний (может возвращать как REST, JSON, так и просто веб-страницу), которые она запрашивает у Тиньки и передаче импульсов на сервер по MQTT (пока не реализовал). В дальнейшем хочу переделать на модули nRF52832 с использованием mesh-сети, если получится в ней разобраться. :)


    1. beho1der
      05.04.2018 16:08

      Может статью?


    1. shendrik
      05.04.2018 21:23

      У меня примерно такая-же конструкция, только Tiny2313 и питается она от литиевой батарейки. В память данные решил не сохранять, так как приняты все меры к тому, чтобы Tiny всегда была включена. На ESP8266 поднят сервер, который отображает дату, время, уровень сигнала WiFi, показания и серийные номера счетчиков, а так же позволяет сделать и сохранить в EEPROM все начальные установки.
      Кроме того, показания счетчиков по протоколу MQTT отправляются сервер откуда я их забираю на смартфон. Ну и маленький дисплей OLED не забыл, чтобы можно было посмотреть показания, если вдруг ноутбук и телефон разрядились.
      Еще хочу прикрутить управление кранами с электромоторами, чтобы закрывать и открывать воду из любой точки мира дистанционно.


      1. grafalex Автор
        05.04.2018 22:06

        Может статью? :)


      1. BurlakovSG
        06.04.2018 12:42

        Я тоже изначально делал на Tiny2313, но в итоге не хватило памяти когда начал делать кольцевой буфер для приёма сообщений через UART. Вот тут выложил код Тиньки.
        Тоже сначала хотел сделать универсальную плату, чтобы можно было подключить к ней датчики протечки и поставить реле для кранов, но подумал, что места в корпусе уже не хватит. Я запихал всё в такой корпус и провода в него входят через кабельный ввод.


        1. grafalex Автор
          06.04.2018 16:03

          Спасибо, почитаем-с


        1. shendrik
          06.04.2018 19:17

          Вот мое чудо, в стиле техно панк))
          Я использовал аппаратное подавление дребезга контактов, что существенно упрощает код для Tiny.
          Корпус оттуда же.
          image


          1. grafalex Автор
            06.04.2018 19:45

            Круто выглядит. Красиво и аккуратно!


            1. shendrik
              06.04.2018 20:00

              Самое главное — работает без глюков больше 1,5 лет. Я этот проект затеял только потому, что у меня дома одна пара счетчиков находится в труднодоступном месте. Теперь ежемесячно говорю себе спасибо))


  1. legioner
    05.04.2018 13:25

    У меня счетчики уже подключены к системе учета от УК. Как грамотно «размножить» сигнал от них, чтобы не получить проблем?


    1. grafalex Автор
      05.04.2018 22:09

      Не силен в аналоговой электронике, но по моему тут достаточно оптопары. Но тут больше вопрос к УК, как они отреагируют увидев врезку. Впрочем если показания на счетчике совпадают с ихними, то проблем быть не должно.


  1. kosachev
    05.04.2018 13:57
    +1

    Каждый раз, когда разводят платы с дорожками под антенной, где-то в мире грустит один котик.


    1. grafalex Автор
      05.04.2018 22:10

      Конструктивная критика принята :)

      Кстати, в ESP-07 антенна внешняя. Просто и без нее добивает…


  1. proton17
    05.04.2018 23:38

    HLK не имеет никакой встроенной защиты от перенапряжений по входу от слова совсем… Так что после предохранителя необходимо, как минимум, поставить параллельно входу варистор, что-то типа 471KD10. Иначе при хорошем броске напряжение в сети будет большой бах. Можно добавить во второй провод питания (там где нет предохранителя) до варистора маленький NTC термистор на 20-40 Ом и мощностью 1-2Вт для ограничения пусковых токов. На выходе, до электролита можно добавить дроссель на 5-10мкГн, а после электролита очень желательно хороший керамический кондер на 1мкф и 0.1мкф. А вообще я бы посмотрел в сторону таких ИП как IRM-05-3.3, немного дороже, но зато надежнее.


    1. grafalex Автор
      06.04.2018 15:33

      Спасибо огромное за советы. Обязательно учту в следующих проектах.

      На счет IRM-05-3.3. Не так чтобы сильно дорого, но жаба давит за БП отдавать больше, чем за все остальное вместе взятое. Чем он будет кардинально лучше от HLK с обвесом, или обычного зарядника для мобилки?


      1. proton17
        06.04.2018 15:50

        HLK, как и зарядка USB непонятно кем и для чего сертифицированы. Они не имеют нормального входного EMI фильтра и защиты от бросков напряжения в сети. IRM, кстати есть более дешевые модели на меньшую мощность, имеют нормальную сертификацию для применения в промышленной аппаратуре и рассчитаны на длительную работу. Про HLK еще были на буржуйских форумах посты, что встречаются подделки, как бы это не смешно звучало) Отличаются более дешевым компонентами. А так, в принципе, нормальная HLK с перечисленным выше обвесом, должна нормально работать, главное не грузить ее сильно, и все будет ок. А варистор и сейчас можно, и даже нужно, припаять прямо на вход HLK или на контакты клемника.


        1. grafalex Автор
          06.04.2018 16:02

          Ок, спасибо. Заскочу на радиорынок на неделе.


    1. Mike-M
      06.04.2018 22:12

      Присоединяюсь к совету сменить noname HLK на проверенный временем Mean Well. Думаю, и по КПД заодно выиграете.

      От себя добавлю: вместо варистора можно использовать супрессор типа 1.5KE400CA. Я такой установил в свой диммер в 2008 году. Прошло почти 10 работы в режиме 24/7 — полёт нормальный безупречный.

      Если хотите ещё большей надежности, переходите от китайских ESP на Texas Instruments CC3220. Помимо прочих плюшек, у этого модуля библиотека MQTT поставляется в составе родного SDK, причём не только в виде клиента, но и в виде сервера.
      И никаких танцев с бубнами вроде «не хватает» памяти. К слову, у данного модуля 1М+256КБ. Правда, за всё надо платить (в прямом смысле).


  1. geisha
    06.04.2018 03:46

    Про питонщину на ESP вам уже упомянули. Кроме того, что это очень ненадежный способ опрашивать харварь, в нативном SDK гораздо больше возможностей по энергосбережению и т.п. Да и сам код в вашем случае, как мне кажется, на сях будет компактнее.


    Ну и 220V это, конечно, эпик фэйл. Я, конечно, понимаю, что ESP очень много жрёт, но есть вагон других IOT решений. В эпоху когда во всех телевизорах делают опцию отключения статусного светодиода в целях энергосбережения вы ради пары байт держите монстра, который жрёт побольше среднего смартфона в активном режиме (лично замерял). Ради чего тогда умный дом?


    В общем, перфекционисты негодуют.


    1. grafalex Автор
      06.04.2018 15:49

      На сях компактнее не будет, это точно. Одна работа со строками чего стоит.

      Мне кажется это предрассудки на счет языков высокого уровня в эмбеддеде. Я недавно такое уже проходил с плюсами на микроконтроллере (раз, два) — не убедили. Ну да, есть некоторые ограничения, но не так, чтобы полностью отказываться от остальной части языка.

      Возвращаясь к питону. Тут нет необходимости бороться за микросекунды (это я тоже умею). Работа тут происходит вразвалочку, никто друг друга не ждет. Так что, как мне кажется, использование гораздо более медленного языка в угоду читаемости тут себе можно позволить.

      С опросом пина вы меня таки заклевали. На выходных попробую вернуться к прерываниям.

      На счет 220В. Питание от сети тут отнюдь не потому, что ЕСП много жрет. А потому, что во-первых уже есть розетка — почему бы от нее не запитать? А во-вторых в будущем нужно будет питать моторизированные вентили. От 12В или от 220В — это еще вопрос, но питание там все равно понадобится. Так почему на фоне этого не использовать более простые решения. Все равно на потреблении в 200мА в прыжке (0.66Вт) особо не наэкономишь, а батарейки менять нужно.


      1. geisha
        06.04.2018 21:45

        Ладно, признаю, я biased энергопотреблением и поэтому мне не нравится питон на ESP. Я когда-то давно видел на GT концепт датчиков а-ля NFC, которые получают питание от антенны и отправляют данные по BLE (наверное). Вот интересно, это уже реальность или есть какие-то сложности?


  1. telobezumnoe
    06.04.2018 07:38

    делал себе считыватель показаний с эл счетчика измеряя количество вспышек светодиода и время между импульсами для расчета текущей потребляемой мощности. обошёлся без сохранения данных в пзу, просто публикую их на mqtt сервер, а в начале опрашиваю данные с него и если число с сервера больше чем текущее показание, то использую их для начала отсчёта. пины у вас можно использовать без подтяжки отдельно резистором, простор подключая встроенные в контроллер пинап резисторы. и при замыкании геркона на входе будет просто "0" а в незамкнутом состоянии вход будет подтянут к питанию.


    1. grafalex Автор
      06.04.2018 15:56

      Я вот себе повторил решение от Электроника в Объективе. Демонстрировать не буду, ибо заклюют — там тоже ESP от 220 запитано :)

      Получилось проще и точнее, чем цепляться на лампочку. К тому же у меня счетчик на лестничной клетке — там кто хочешь ходит, и у электрокомпании могут возникнуть вопросы (хотя их электрик вроде как даже ничего против не имел подключиться напрямую к клеммам счетчика)

      пины у вас можно использовать без подтяжки отдельно резистором, простор подключая встроенные в контроллер пинап резисторы

      Так и есть. На входных пинах подтяжка внутренняя. Остальные подтяжки нужны чтобы контроллер завелся в правильном режиме (все согласно даташиту)


      1. telobezumnoe
        06.04.2018 21:48

        тоже хотел купить изначально счетчик с цифровым выходом, но электрик сказал что ничего пломбировать не будет при подключении к счетчику каких либо устройств, так что покупать больше навороченный счетчик не было смысла, ну а для быта той точности которую мне удалось достичь -достатоно. а запитываю все от компактной зарядки для телефона, коих вокруг достаточно


  1. SGordon123
    06.04.2018 09:36

    А если WiFi не держать постоянно подключенным, чисто сбор показаний и отправка раз в час, с ESP можно организовать батарейное питание?


    1. grafalex Автор
      06.04.2018 16:00

      Теоретически можно. Вроде ЕСП довольно мало в слипе жрет.
      Но вот даже включение WiFi на пару секунд ежедневно будет лопать батарею достаточно быстро. Тут я, пожалуй, соглашусь с предложениями предыдущих комментаторов строить на базе каких нибудь nRF24.

      Но как я уже сказал у меня нет ограничений по питанию — рядом есть розетка. К тому же мне все равно понадобится силовое питание для того, чтобы вентили закрывать в случае протечки.


      1. SGordon123
        06.04.2018 16:13

        Надо счетчик совмещенный с генератором подыскать, протекающая вода заодно крутит турбинку и подзаряжает акк для есп :-) Или термогенератор между холодной и горячей водой, пельтюх какой нибудь…


        1. shendrik
          06.04.2018 19:44

          У Боша в газовой колонке турбинка стояла, которая питала поджиг газа, когда открываешь воду. Можно использовать для подзаряда аккума


          1. SGordon123
            06.04.2018 20:06

            Круто!