В этот раз я задумался о том, чтобы спрятать в велосипед GPS-трэкер в качестве меры предосторожности. На рынке есть масса автономных устройств для слежения за автомобилями, грузом, велосипедами, багажом, детьми и животными. Подавляющее большинство из них взаимодействуют с пользователем с помощью СМС. Более дорогие варианты предоставляют функциональность Find my phone, но привязаны к конкретному онлайн-сервису.
В идеале хотелось бы иметь полный контроль над трекером: использовать его в удобном режиме без СМС и регистрации. Поверхностное гугление вывело меня на пару модулей из поднебесной, один из которых, A9G pudding board, я и заказал (~15$).  Эта статья о том, как я заставил работать python на этом модуле. Если A9G — аналог ESP (производитель, кстати, один и тот же), то сам pudding board является аналогом платы NodeMCU за исключением того, что на pudding board нет встроенного конвертера USB-UART. Зато есть много другого интересного. Спецификации от производителя: Рабочее напряжение 3.3В, входное напряжение — 5-3.8В (в зависимости от подключения). Вообще, модуль имеет всё необходимое железо для того, чтобы собрать из него простенький кнопочный мобильный аппарат. Но из примеров создаётся впечатление, что китайцы его покупают для продажи из автоматов или автоматов с азартными играми или что-то вроде этого. Альтернативами модулю являются довольно популярные модули SIM800, у которых, к сожалению, нет SDK в свободном доступе (т.е. модули продаются как AT модемы). К модулю прилагается SDK на удовлетворительном английском. Устанавливается под Ubuntu, но предпочтительными являются Windows и контейнеры. Всё работает через тыкание в GUI: ESPtool для этого модуля только предстоит зареверсить. Сама прошивка собирается Makefile-ом. Дебаггер наличествует: прежде чем зависнуть, модуль вываливает stack trace в сервисный порт. Но лично я так и не смог перевести адреса в строчки кода (gdb сообщает, что адреса ничему не соответствуют). Вполне возможно, что это связано с плохой поддержкой Linux как такового. Соответственно, если хотите повозиться с модулем — попробуйте это сделать под Windows (и отписаться на github). В противном случае вот инструкция для Linux. После установки нужно проверить правильность путей в .bashrc и удалить (переименовать) все файлы CSDTK/lib/libQt*: иначе, прошивальщик (он же дебаггер) просто не запустится из-за конфликта с, вероятно, установленным libQt.  К прошивальщику идёт инструкция. Тут всё сложнее, чем, на NodeMCU. Модули выглядят похоже, но на pudding board нет USB-TTY чипа и microUSB используется только для питания. Соответственно, вам понадобится USB-TTY на 3.3V. А лучше — два: один для дебаг порта и ещё один для UART1: первый используется для заливки прошивки а второй вы сможете использовать как обычный терминал. Чтобы не тащить все эти сопли к компьютеру я дополнительно приобрел USB разветвитель на 4 порта с двухметровым проводом и внешним блоком питания (обязателен). Суммарная стоимость этого набора с самим модулем составит 25-30$ (без блока питания: используйте от телефона). Модуль приходит с AT прошивкой: можно подключить к 3.3В ардуине и использовать в качестве модема через UART1. Свои прошивки пишутся на C. make создает два файла прошивки: один шьётся около минуты, другой — достаточно быстро. Шить можно только один из этих файлов: первый раз — большой, последующие разы — маленький. Суммарно, у меня в процессе разработки на рабочем столе открыта китайская SDK (coolwatcher) для управления модулем, miniterm в качестве stdio и редактор кода. Содержание API отражает список наверху и напоминает ESP8266 в свои ранние дни: у меня ушло часа 3 на то, чтобы запустить HelloWorld. К сожалению, набор функций, доступных пользователю, весьма ограничен: к примеру, нет доступа к телефонной книге на SIM-карте, низкоуровневой информации о подключении к сотовой сети и тому прочее. Документация по API ещё менее полная, поэтому опираться приходится на примеры (которых два десятка) и include-файлы. Тем не менее, модуль может очень многое вплоть до SSL-подключений: очевидно, производитель сфокусировался на наиболее приоритетных функциях. Впрочем, программирование китайских микроконтроллеров посредством китайского API надо любить. Для всех остальных производитель начал портировать micropython на этот модуль. Я решил попробовать себя в open-source проекте и продолжить это доброе дело (ссылка в конце статьи).  Micropython — это open-source проект портирующий cPython на микроконтроллеры. Разработка ведётся в двух направлениях. Первое — это поддержка и развитие общих для всех микроконтроллеров core-библиотек, описывающих работу с основными типами данных в python: объекты, функции, классы, строки, атомарные типы и тому прочее. Второе — это, собственно, порты: для каждого микроконтроллера необходимо "научить" библиотеку работать с UART для ввода-вывода, выделить стэк под виртуальную машину, указать набор оптимизаций. Опционально, описывается работа с железом: GPIO, питание, беспроводная связь, файловая система.
Всё это пишется на чистых С с макросами: у micropython есть набор рекомендованных рецептов начиная с объявления строк в ROM до написания модулей. В дополнение к этому, полностью поддерживаются самописные модули на питоне (главное — не забывать об объёме памяти). Кураторы проекта ставят целью возможность запустить джангу (картинка с буханкой хлеба). В качестве рекламы: проект продаёт собственную плату для студентов pyboard, но также популярны порты для модулей ESP8266 и ESP32. Когда прошивка готова и залита — вы просто подключаетесь к микроконтроллеру через UART и попадаете в питонский REPL. После этого можно начинать писать на почти обычном python3, не забывая об ограничениях памяти. Модуль A9G не поддерживается официально (список официально поддерживаемых модулей доступен в micropython/ports, их около десятка). Тем не менее, производитель железа форкнул micropython и создал окружение для порта A9G: micropython/ports/gprs_a9, за что ему большое спасибо. На момент, когда я заинтересовался этим вопросом, порт успешно компилировался и микроконтроллер приветствовал меня REPL. Но, к сожалению, из сторонних модулей присутствовала только работа с файловой системой и GPIO: ничего, связанного с беспроводной сетью и GPS доступно не было. Я решил исправить эту недоработку и поставил себе цель портировать все функции, необходимые для GPS-трекера. Официальная документация на этот случай излишне лаконична: поэтому, пришлось ковыряться в коде. Первым делом идём в micropython/ports и копируем micropython/ports/minimal в новую папку, в которой будет находится порт. Затем, редактируем main.c под вашу платформу. Имейте ввиду, что вся вкуснятина находится в функции main, где нужно вызвать инициализатор mp_init(), предварительно подготовив для него настройки микроконтроллера и стэк. Потом, для event-driven API, необходимо вызвать pyexec_event_repl_init() и скармливать вводимые через UART символы в функцию pyexec_event_repl_process_char(char). Это и обеспечит взаимодействие через REPL. Второй файл — micropython/ports/minimal/uart_core.c описывает блокирующий ввод и вывод в UART. Привожу оригинальный код для STM32 для тех, кому лень искать. main.c uart_core.c После этого нужно переписать Makefile используя рекомендации / компилятор от производителя: тут всё индивидуально. Всё, этого в идеале должно хватить: собираем, заливаем прошивку и видим REPL в UART.
После оживления micropython необходимо позаботиться о его хорошем самочувствии: настроить сборщик мусора, правильную реакцию на Ctrl-D (soft reset) и некоторые другие вещи, на которых я не буду останавливаться: см. файл mpconfigport.h. Самое интересное — написание собственных модулей. Итак, модуль (не обязательно, но желательно) начинается с собственного файла mod[имя].c, который добавляется Makefile (переменная SRC_C если следовать конвенции). Пустой модуль выглядит следующим образом: Конечно, порт сам по себе не узнает о константе mp_module_mymodule: её необходимо добавить в переменную MICROPY_PORT_BUILTIN_MODULES в настройках порта mpconfigport.h. Кстати, нескучные обои имя чипа и название порта меняются тоже там. После всех этих изменений можно попытаться скомпилировать модуль и импортировать его из REPL. У модуля будет доступен только один атрибут __name__ с именем модуля (отличный случай для проверки автодополнения в REPL через Tab). Следующий этап по сложности — добавление констант. Константы часто необходимы для настроек (INPUT, OUTPUT, HIGH, LOW и т.п.) Тут всё достаточно просто. Вот, к примеру, константа magic_number = 10: Тестируем: Добавление функции в модуль следует общему принципу: объявить, обернуть, добавить (привожу чуть более сложный пример, чем в документации). Тестим: С классами (типами) всё тоже относительно просто. Вот пример из документации (ну почти): Тестим: Получившийся тип можно наследовать, сравнивать, но у него нет ни конструктора ни каких-либо ассоциированных данных. Данные добавляются "рядом" с конструктором: предлагается создать отдельную структуру, в которой будет хранится отдельно питонский тип и отдельно — произвольный набор данных. Как взаимодействовать с этими данными? Один из самых сложных способов — через конструктор. Из других полей есть ещё .print, и, полагаю, вся остальная магия Python3. Но make_new вовсе не обязателен для получения экземпляра объекта: инициализацию можно производить в произвольной функции. Вот неплохой пример из micropython/ports/esp32/modsocket.c: Следующий этап — добавление привязанных методов. Впрочем, это мало чем отличается от всех остальных методов. Возвращаемся к примеру из документации: Всё! Как насчёт добавления не-функций, использования @property и вообще собственного __getattr__? Пожалуйста: это делается вручную в обход mymodule_hello_locals_dict_table. Что-то больно лаконичный attr получился, скажете вы. Где же все эти mp_raise_AttributeError (прим: такая функция не существует)? На самом деле, AttributeError будет вызван автоматически. Секрет в том, что dest — это массив из двух элементов. Первый элемент имеет смысл "вывода", write-only: он принимает значение MP_OBJ_SENTINEL если значение необходимо записать и MP_OBJ_NULL если его нужно прочитать. Соответственно, на выходе из функции ожидается MP_OBJ_NULL в первом случае и что-то mp_obj_t во втором. Второй элемент — "ввод", read-only: принимает значение объекта для записи, если значение необходимо записать и MP_OBJ_NULL, если его необходимо прочитать. Менять его не надо. Вот и всё, можно проверять: Самое интересное — что автодополнение по Таb в REPL по-прежнему работает и предлагает .val! Я, если честно, никак не эксперт в C, поэтому могу только предполагать как это происходит (переопределением оператора '=='). Возвращаясь к модулю A9G, я описал поддержку всех основных функций, а именно, SMS, GPRS (usockets), GPS, управление питанием. Теперь можно залить что-то вроде этого на модуль и оно будет работать: Проект приветствует любую посильную помощь. Если вам понравился проект и/или эта статья — не забудьте оставить лайк на гитхабе.