Всем привет.


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


Запуск базовой прошивки на esp8266. Sonoff Basic.


При отсутствии подводных камней можно было бы переходить сразу к программированию на 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)      # Реле
        # Вызываем self._auto(), если была нажата или отпущена кнопка 
        self.pin.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING, handler=self._auto)
        self.value = 0

    def _auto(self, _=0):
        if self.value: 
            res = self.pin.value()
        else:           
            res = not self.pin.value()
        self.pout.value(res)

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

sw = SW(14, 12)  

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


from switch import sw

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


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

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


  1. Andrew_Pinkerton
    03.02.2019 14:49

    1. Непонятно зачем в заголовке указан sonoff
    Ну ок, в нем есть ESP8266

    2.

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

    Странный у вас простой способ.
    Достаточно реализовать исполнительное устройство с беспроводным интерфейсом и веб мордой,
    и управляйте им откуда угодно.
    Но причем здесь проходной выключатель?

    3.
    в дополнение я решил достаточно подробно описать весь процесс

    настолько подробно, что в статье практически нет технических деталей, одна вода.

    4.
    содержать Arduino board
    может всё таки содержать ESP8266 board?

    P.S. Я не придираюсь, но не могу понять огромное количество текста на такую простую задачу.

    Первая часть состоит из трёх частей
    И ужасно форматированный код Python


    1. vlx
      04.02.2019 10:01

      Я чет тоже немношк офигел с простого проходного выключателя на микроконтроллере.


  1. olartamonov
    03.02.2019 15:14

    При замыкании свободного пина на Vcc в микроконтроллере происходит, соответственно, включение или выключение реле


    Я так понимаю, знакомство со словами «дребезг контактов» вам только предстоит.


    1. ffasm Автор
      04.02.2019 02:32

      Да, дребезг будет заметен только когда выключатель начнёт отправлять информацию о своём состоянии по mqtt в сеть. В связи с тем, что python по умолчанию складывает irq события в scheduler, это будет достаточно интересно проявляться. Но статья и так получилась загромождённая, вопрос прерываний, либо переход на опрос по таймерам лучше рассмотреть в следующих сериях.


  1. aivs
    03.02.2019 20:02
    +1

    В моих проектах для питания 3.3В хорошо себя зарекомендовал Hi-Link.


    1. mazy
      03.02.2019 23:02

      Да. Отличная вещь. Тоже их использую. И еще 5в/5w


  1. kotomyava
    03.02.2019 23:35

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


    1. geisha
      04.02.2019 02:21

      Вы, видимо, не имели дела с китайскими SDK раз у вас возникают такие мысли.


    1. BalinTomsk
      04.02.2019 06:44

      Мне казалось VS волне неплох.


  1. immaculate
    04.02.2019 04:13

    В MicroPython соблюдать требования PEP-8 необязательно?
    Код условий, например, совершенно нечитаемый.


    1. ffasm Автор
      04.02.2019 08:38

      Спасибо за конкретные рекомендации, прочитал PEP-8 (каюсь, первый раз в жизни), поправил.


  1. onegreyonewhite
    04.02.2019 06:50

    if (val==2):    self.value=not self.value
    else:           self.value=val

    Первая мысль при виде этого — «где мой миксер? глаза — вы мне больше не нужны!»
    Ещё такое ощущение, что вы все 3 вида комментариев учились использовать.
    За что вы так с людьми, которые будут это читать?
    Ладно PEP-8, но совесть-то у вас есть?

    P.S.: Простите, если я излишне эмоционален. На днях пришлось много Python-кода исследовать в одном проекте заказчика, и я ещё не отошёл от травмы.


    1. ffasm Автор
      04.02.2019 09:03

      Спасибо за конструктивную критику, прочитал PEP-8, привёл в соответствие.

      Не очень понял критику про комментарии. Все в соответствии с PEP-8:
      1. Один для документирования публичной функции change(),
      2. Два «встрочных» на мой взгляд стоят в наилучшем для них месте, ровно как в примере PEP-8,
      3. Последний из-за большой длины строки перенёс перед строкой. На мой взгляд комментарий достаточно важен.


      1. onegreyonewhite
        05.02.2019 04:22

        Уже лучше. И вы молодец, что адекватно реагируете на критику (пусть даже она действительно была резковата с моей стороны). Я думаю за комментарии вам стоит почитать это руководство.

        1. Если документируете, то лучше бы использовать так:

        '''
        Программное переключение выключателя
        
        :param val: -- Положение выключателя (0 - выкл, 1 - вкл, 2 - переключение)
        :type val: int
        :rtype: None
        '''

        2. «Старайтесь реже использовать подобные комментарии.» — цитата из руководства выше, с которой я солидарен. Такой комментарий легко может скрыться, потому что находится за границами экрана + можно его банально пропустить глазами, когда дошёл до конца строчки кода.
        3. Вот только так и пишите комментарии.

        Ко всему прочему, судя по всему вы забыли, что у вас нет метода .auto(), а есть ._auto(). А если вы ещё проверите код flake'ом, то он ещё может ругнуться на неиспользуемый аргумент v в этой же функции.


        1. ffasm Автор
          05.02.2019 10:12

          Если человек потратил своё время на то чтобы указать на проблемы (пусть даже в грубоватой форме) то глупо их игнорировать.

          1. В общем случае согласен, но в моём: «Однострочники предназначены для действительно очевидных случаев»
          2. Я считаю, что эти комментарии бесполезны, и очень хорошо если при чтении кода вы их пропустите. Но если неопытный человек видит код работы с GPIO первый раз в жизни, он может споткнуться на этих строках, на встречу ему придут комментарии. См. случай «Впрочем, такие комментарии иногда полезны:» из раздела «Встрочные» комментарии"

          Я прочитал руководство PEP-8 и считаю, что моя позиция не нарушает его принципов.

          По поводу auto спасибо, действительно схалтурил, впредь буду выкладывать что-то только как минимум после flakes.


  1. ffasm Автор
    04.02.2019 09:01

    <удалено>


  1. iig
    04.02.2019 12:54

    Проходной выключатель — имеется в виду что-то вроде этого? Лампа, которой можно управлять с 2 и более мест?
    image

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


    1. ffasm Автор
      04.02.2019 20:30

      Вот эскиз того, что мы хотим сделать, зелёном выделено то, что было затронуто в этой статье.



      В 1.3 части мы сделали простой выключатель. Можно было бы сделать проходной, но пока не будет работать mqtt не хочется занимать RX, TX. Т.е. дописать работу с остальными выключателями я планировал в самом конце.


  1. savenko_egor
    05.02.2019 10:12

    хранить где-то прошивки

    GitHub для этого был придуман. Если не хотите в общий доступ, то Bitbucket или же тот же GitHub (в нём уже есть бесплатные приватные репозитории).
    А при необходимости внести корректировки придётся мучительно вспоминать, где лежит код, и снова устанавливать ПО Arduino.

    В чём проблема установить ПО? Я что-то не могу уловить смысла. Не хотите Arduino IDE, ставьте vscode и плагин к нему «PlatfromIO».

    Я плохо читал, или так и не нашёл какое ПО Вы ставили для первой загрузки Вашей прошивки?
    Если я всё правильно понимаю, то Вы в любом случае ставите какое-то ПО для первой заливки прошивки в МК. То есть исходя из этого, я тогда не совсем понимаю почему Вы так упорно утверждаете что «и снова устанавливать ПО Arduino».


    1. ffasm Автор
      05.02.2019 11:34

      Использовать git (или аналоги) действительно очень удобно, как и использовать удобное ПО для разработки. Но в MicroPython при желании от этого можно отказаться, а в Arduino нет.
      Для меня это важный плюс и я написал эту статью для тех, кто считает аналогично, либо кому просто нравится язык Python.

      В идеальном случае, если вы купили устройство с прошитой OTG+MicroPython из минимального софта вам понадобится только браузер и доступ к сайту с проектом webrepl (он даёт возможность залесть на устройство, скачать записать ваши python скрипты). Для OTG обновления ещё будут нужны: python, скрипт otg-client.py и прошивка, но обновлять саму прошивку MicroPython нужно(если нужно) достаточно редко.

      В моём случае для первой прошивки пришлось воспользоваться устройством USB<->UART и программой esptool (которая записывает прошивку). Для исправления проблем конечно пришлось скачать и esp-sdk, micropython, yaota8266 поковыряться в C коде поискать решения на github и собрать. Но это вовсе не означает что MicroPython плохое решение, просто оно пока не доведено до идеала.


      1. savenko_egor
        05.02.2019 11:47

        Я не в коем случае не имел ввиду что MicroPython — это плохо. Нет.
        Просто меня удивило то, что Вы говорите что ставить Arduino IDE это «лишнее телодвижение», но при этом Вы установили кучу всего =)
        Плюс добавлю то, что на Windows по дефолту нет Python, а это значит что его нужно установить, а установить Python на Windows, по моему это труднее чем установить Arduino IDE.

        P.S. Сам люблю и уважаю Python. Но для работы с МК, мне кажется Python это слишком топорно что ли.


    1. iig
      05.02.2019 11:44

      +100500

      А при необходимости внести корректировки придётся мучительно вспоминать, где лежит код, и снова устанавливать ПО Arduino.


      А когда код внутри устройства, придется долго вспоминать, что он делает и как туда попал.
      Если есть доставка прошивок по ОТА (есть какой-то сервер), не вижу никаких проблем в организации репозитория для исходников и CI для сборки/упаковки прошивок.