[Из песочницы] Программируем проходной выключатель. MicroPython на esp8266 (sonoff) с OTA. Часть 1

habr.png

Всем привет.

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

Как выяснилось, та конструкция, которую я посчитал наиболее удобной, оказывается, вообще не работает. Пришлось затратить какое-то время на разбор этого, в дополнение я решил достаточно подробно описать весь процесс. Объем статьи начал увеличиваться большими темпами, так что я решил разделить её на части и выбросить все излишние на мой взгляд подробности.

Первая часть состоит из трёх частей:


  1. Теоретические рассуждения по выбору наиболее простой среды для разработки проходного выключателя,
  2. Практический запуск выбранной базовой прошивки на выбранном оборудовании, подводные камни,
  3. Разработка прошивки

Для умного дома вида «сделай сам, если у тебя появилась минутка свободного времени» в список с обязательными требованиями к оборудованию, помимо классических пунктов (например, стабильность), добавляются лёгкость разработки, монтажа и поддержки. От устройств требуется, чтобы к ним легко можно было подключить необходимые датчики либо устройства управления. Чтобы были удобные и простые способы связи со всей системой. Необходимо обеспечить лёгкость записи прошивок в это устройство, с учётом, что устройство может находится там, где добраться до него будет достаточно сложно. Ну и конечно лёгкость разработки, это особенно критично для самоделкина, когда, например, через 2 года после работы без сбоев всей системы
вдруг хочется добавить какие-то корректировки в прошивку. Чтобы внести эти исправления, нужно вспомнить, как работает эта система, на что порой может уйти больше времени, чем на саму корректировку.

Рассмотрим банальный пример: необходимо сделать простой проходной выключатель с возможностью управлять им в том числе с ПК. В недавние времена эта задача была достаточно сложная, нужно было взять какой-нибудь микроконтроллер (наиболее популярны были avr или pic), и чтобы написать прошивку, как правило, нужно прочитать документацию на него. Если хочется сделать всё «из коробки», нужно развести плату, куда поставить AC/DC, микроконтроллер и интерфейс связи. После ЛУТ-а (или заказа печатных плат) всё спаять, купить программатор, зашить прошивку. А потом через 2–3 года, при необходимости что-то исправить, искать всё оборудование и изучать всё почти с нуля…

Для упрощения этого процесса на рынке стали появляться готовые решения. Наиболее успешным решением является Arduino. Это решение даёт IDE, загрузчик с функцией обновления, что позволяет работать с устройством исключительно через стандартный интерфейс без использования программаторов. Даёт возможность делать прошивки, имея только
очень поверхностное понимание о том, как там всё устроено. Набор внешних модулей даёт возможность подключать устройства без паяльника. Но всё равно для внесения правок требуется устанавливать ПО Arduino, хранить где-то прошивки.

Наш проходной выключатель получится достаточно большим, будет содержать Arduino board + AC/DC + модуль реле. А при необходимости внести корректировки придётся мучительно вспоминать, где лежит код, и снова устанавливать ПО Arduino.

Для того, чтобы избавить себя от необходимости компилировать исходный код (т.е. устанавливать дополнительное ПО и хранить его), самым логичным решением кажется либо использование интерпретаторов, либо непосредственное компилирование кода на самом микроконтроллере. К счастью на данный момент появились проекты, которые позволяют это делать. Например, NodeMCU, интерпретатор языка lua под микроконтроллер esp8266: в самой прошивке встроена поддержка файловой системы, что позволяет загружать/считывать скрипты на/с устройства. Ещё одним достаточно серьёзным проектом является Micropython, это урезанный вариант python, который специально заточен под микроконтроллеры. О нём и пойдёт речь.

MicroPython является реализацией одного из наиболее популярного ныне языка программирования python. Поддерживает большое количество архитектур и SoC (bare-arm, CC3200, esp8266, esp32, nRF, pic16bit, stm32). Проект активно развивается и имеет большое количество дополнительных модулей.

В качестве аппаратной части очень хорошо подходит микропроцессор esp8266, по причине того, что на рынке продаются бюджетные модули выключателей по wifi, построенные как раз на нём. Они содержат всё, что нам нужно: AC/DC, микроконтроллер со встроенным интерфейсом связи (wifi). Выпускаются под торговой маркой Sonoff. Микропроцессоры esp8266 не содержат памяти, она напаивается отдельно и может иметь разный размер. Для Sonoff Basic ставят модули 1Mb.

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

Первый подводный камень, это конечно же базовая прошивка, которая записана на вашу плату. Если вы купили отладочную плату, то скорее всего обнаружите на ней NodeMCU, если Sonoff Basic, то проприетарную прошивку. Для подготовки этой платы под себя необходимо записать туда нужную прошивку. В некоторых микроконтроллерах для этого необходимо приобрести
специальный программатор, в нашем случае нам повезло, нужно всего-навсего раздобыть USB<->UART преобразователь. Если вы работаете с микроконтроллерами, он вам не один раз пригодится, да и цена у них обычно в пределах $3.

Для Sonoff Basic отсутствует гребёнка, позволяющая подключиться по UART, а нам это необходимо, чтобы запрограммировать устройство. Для того, чтобы просто запрограммировать устройство, необязательно брать паяльник в руки, достаточно прислонить контакты и записать прошивку. С учётом того, что дальнейшая работа будет через wifi, нам эти контакты больше не понадобятся. Но мы реализуем проходной выключатель, а значит нам необходимы припаянные,
как минимум, три ножки.

Для Sonoff Basic есть всего 1 свободный разъём GPIO, и 2 разъёма RX, TX. С учётом, что сами RX, TX нам нужны один раз (зашить прошивку), в дальнейшем их можно перепрограммировать на GPIO, благо esp8266 это позволяет сделать. Но в таком случае нам необходимо отказаться от отладки через UART, к счастью мы и так планировали это сделать, так как отлаживаться через wifi, с точки зрения удобства, значительно проще.

Так как версия MicroPython в процессе может меняться, нам интересно отладить способ обновления через wifi. На помощь приходит OTA. OTA это прошивка, которая позволяет перепрограммировать устройство. Работает она достаточно просто. После включения устройства прошивка определяет, нужно ли её перепрограммировать, если нужно, запускает специальную
программу обновления по wifi, если нет, запускает пользовательскую прошивку. Реализация может быть разная, прошивка может перезаписать сама себя либо писать в свободную область памяти. Определять, нужно ли вообще запускать программу перезаписи, также можно разными способами. Например, считать чексумму пользовательской прошивки, если она не сходится,
то принудительно уходить в перепрошивку. Можно считывать данные с GPIO либо записывать информацию о необходимости запуска обновления ещё куда-нибудь.

В качестве программы обновления проект MicroPython ссылается на проект yaota8266. Yaota8266 утверждает, что производит перепрошивку устройства и подписывает каждый пакет. Следует заметить, что публичный ключ встраивается в саму прошивку, из-за чего выкладывать уже собранную прошивку нет смысла, так как необходимо зашить туда свой ключ.
Функции модификации приватного ключа в собранном образе нет, так что в нашем случае проще собрать прошивку самостоятельно. Интересная особенность заключается в том, что функция проверки подписи есть, но закомментирована в коде, т.е. по факту мы получаем сложности без каких-либо выигрышей в плане безопасности. Базовая версия yaota8266 не собирается,
благо на github есть форки, которые решают эту проблему плюс добавляют возможность определять, нужно ли делать перепрошивку на основе записи в область RTC, что даёт возможность переключать MicroPython в режим загрузчика.

Даже после включения всех исправлений наша пошивка с OTA будет записывать с ошибками, но успешно работать на NodeMCU отладочных платах. Это связано с таймаутами. При обновлении c хост машины посылаются UDP пакеты и ожидается ответ, если запись в flash происходит дольше обычного, происходит timeout, и пакет пересылается вновь. Благо это легко исправить,
просто увеличив таймауты в коде ota-client.

Связка OTA + MicroPython на Sonoff тоже имеет интересные странности. Одна из них связана с тем, что штатные функции работы с SPI Flash в esp-sdk оперируют блоками 4k, и для реализации файловой системы FAT был выбран именно этот размер блока. В свою очередь, из-за того что SPI Flash всего 1Mb, из которых ~300Kb это прошивка OTA, ~500Кb это прошивка MicroPython, для файловой системы остаётся менее 200Kb, т.е. менее 50 блоков. Однако выбранная библиотека, реализующая fatfs, не может создать ФС, где блоков меньше 50. Решить проблему можно разными способами: уменьшить размер блока (FAT позволяет выставлять 512), исправить библиотеку FatFs, использовать SPI FS (надеясь, что там нет таких странностей). Я пошёл по пути уменьшения блока до 512.

В микроконтроллерах используется SPI Flash — это NOR и/или NAND память. Примечательность этой памяти в том, что там нет понятия «записать какие-либо данные». Можно только либо сбросить значение (в 0xff), либо установить нужные биты в »0». SPI Flash это обычно NOR память, она имеет функцию сброса любого байта в 0xff, тогда как NAND умеет сбрасывать только блоками. Т.е. если минимальный размер блока сброса 4k, для того, чтобы записать
1 байт памяти, необходимо считать весь блок, сбросить его в 0xFF, а потом записать блок, установив нужный байт в нужное значение. У производителей SPI Flash примерно одинаковый набор API для работы, но, как показала практика, команда записи одного байта SPI Flash может отличатся. Где-то будет автоматически сбрасывать перед записью в 0xFF, где-то нет.

При изменении FAT раздела до 512 байт есть шанс получить битую систему, если конкретная SPI Flash не поддерживает автоматический сброс байта при записи. И именно такая память мне попалась в Sonoff Basic. Ходят слухи, что раньше там устанавливали Winbond 25q80bv, но сейчас PUYA 25q80h, у который минимальный блок для очистки — 256 байт. Решение, казалось бы,
простое, нужно просто перед записью FAT блока стирать две страницы, куда он будет записываться, но реализация усложняется тем, что sdk-esp поддерживает только удаление блоками в 4k. Так как запись в FAT наш выключатель будет проводить очень редко,
только при обновлении скриптов прошивки, можно пойти плохим путём и обновлять блок 512 байт блоками по 4k. В документации на эту память сказано, что память выдерживает 100 000 циклов перезаписи, т.е. подобный обход проблемы сократит нам это значение в 4 раза, т.е. до 25 000.

В MicroPython по умолчанию есть консоль, она называется REPL и работает через COM порт. Нас подобное положение дел не очень устраивает, так как мы хотим общаться с устройством через wifi. К счастью в MicroPython штатно идёт и WebRepl, но не запускается автоматически. Можно прописать автозапуск в boot.py, но я решил запускать прямо из _boot.py, системного файла, зашит в сам файл прошивки.

После первого запуска наша прошивка создаст файловую систему, запустит webrepl и создаст точку доступа. Можно подключиться к ней и прописать параметры для подключения к вашей локальной сети, либо, как сделал я, настроить сеть, используя com порт, после чего уже использовать только wifi.

Для ознакомительной работы можно использовать webrepl клиент, написанный на javascript. Клиент можно запустить в браузере на соответствующей странице проекта. Ещё вариант, использовать проект mpfshell, он даёт более удобные функции для работы с устройством.

Итак, после преодоления всех этих подводных камней можно переходить непосредственно к программированию выключателя.

Для разработки прошивки нам нужно иметь примерное представление, как работает GPIO. В целом это можно понять чисто интуитивно:


  1. Если мы установили режим вывода (OUT), то ножка выдаёт либо GND либо Vcc.
  2. Если мы установили режим ввода (IN), то ножка «болтается в воздухе», в таком случае микроконтроллер может выдавать что угодно
  3. Чтобы микроконтроллер не выдавал что угодно, ножку можно подтянуть к нужному значению с помощью встроенных в микроконтроллер
    подтягивающих резисторов PULL_UP или PULL_DOWN.

Ещё нужно иметь представление о том, что такое прерывания: в нашем случае это код, который нужно выполнить, если произошло какое-то событие: была нажата/отжата кнопка либо пришло сообщение из локальной сети о том, что нужно выключить/включить устройство.

Для начала напишем программу простого выключателя (а не проходного) на Python.

from machine import Pin 

class SW:
    def __init__(self, portin, portout):
        self.pin  = Pin(portin , Pin.PULL_UP) # Кнопка
        self.pout = Pin(portout, Pin.OUT)     # Реле
        # Устанавливаем вызов функции auto(), если была нажата или отпущена кнопка 
        self.pin.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self.auto)
        self.value=0

    def change(self,val=2):
        ''' Если указано 0, выключить, если 1, включить, если 2 переключить'''
        if (val==2):    self.value=not self.value
        else:           self.value=val
        self.auto(2)

    def auto(self,v):
        if self.value: res=self.pin.value()
        else:          res=not self.pin.value()
        self.pout.value(res)

sw=SW(14,12)

Я назвал этот файл switch.py и прописал его запуск в boot.py:

from switch import sw

После запуска прошивки я получил объект sw, если выполнить sw.change (), произойдёт программное переключение
выключателя в другое положение. При замыкании свободного пина на Vcc в микроконтроллере
происходит, соответственно, включение или выключение реле.

Следующим этапом будет запуск MQTT клиента и возможность переключить выключатель с телефона.

© Habrahabr.ru