Взаимодействие Ардуино и Телеграм-бота: простая инструкция
Записал видео, где простым языком, без лишней воды, но при этом достаточно подробно рассказываю как отправлять данные из телеги на ардуино и получать их обратно.
Записал видео, где простым языком, без лишней воды, но при этом достаточно подробно рассказываю как отправлять данные из телеги на ардуино и получать их обратно.
Открой для себя способ, как превратить обычный чат Telegram в центр управления всеми устройствами вокруг тебя – от Ардуино до умного дома! В этом ролике я раскрою все секреты создания мощного Telegram-бота, который станет твоим незаменимым помощником. Ты узнаешь, как легко и просто можно управлять устройствами, получать данные с датчиков в реальном времени и автоматизировать повседневные задачи, не выходя из любимого мессенджера. Неважно, профи ты или только начинаешь свой путь в мире DIY и программирования – этот гайд откроет тебе двери в захватывающий мир технологий!
Многие помнят линейку телефонов Siemens 65-75 серии, которые были в ходу около 20 лет назад. Мы попробуем провести необычный эксперимент на их основе, для чего напишем небольшую управляющую библиотеку на C, а также изменим код одной из встроенных в ОС Linux утилит. Подробнее в статье.
В серии телефонов «Siemens 75» существовали такие модели, как C75, ME75, CX75 и прочие. Они различались между собой дизайном корпуса и некоторым другим функционалом. Но их основные элементы были, во многом, схожи. Например, дисплей разрешением 132x176. Для работы с данным проектом в коробке с различным электронным мусором были обнаружены останки подобного телефона. Состояние изделия за 20 с лишним лет очень плохое, надписи стёрлись, но я предполагаю, что это был экземпляр телефона C75 – BenQ, позднего выпуска. Родной процессор телефона давно уже умер — какое-то время назад удавалось оживить его, прогрев плату паяльным феном, но теперь осталась лишь безжизненная плата и оболочка. Поэтому я решил перехватить информацию на уровне дисплея.
Нас интересует, в первую очередь, этот самый дисплей. Поэтому, для начала, разберём устройство:
Поскольку любая наука стоит «на плечах гигантов», я не буду повторять материал о видах дисплеев в данном телефоне. Ознакомиться с ним вы можете самостоятельно, это описал ещё в 2013 году Кизим Игорь, в своей статье.
У меня уже был некоторый опыт работы с данным дисплеем. Используя библиотеку, взятую из статьи Игоря, я подключал данный дисплей к микроконтроллеру ATmega8. Однако, это было более 5 лет назад, поэтому, для начала, я решил проверить, а работает ли вообще данный дисплей. Для чего собрал схему Игоря. Её ключевые особенности: установлен стабилитрон по питанию дисплея (5V -> 2.9V), установлены резисторы для понижения уровней логики (5V -> 3.3V). Однако, нам необходимо ещё одно напряжение – 12V, для питания диодной ленты подсветки. Поскольку я не хотел городить дополнительный инвертор, я расковырял подсветку и заменил диоды на те, которые могут работать напрямую от 5V, подсоединив их не последовательно, а параллельно. Таким образом я избавился от лишнего провода питания, хотя и качество подсветки несколько пострадало.
Теперь я имел 2 провода питания (+5V, GND), и 5 сигнальных проводов с уровнем логики 3.3V.
Однако, контроллера ATmega8 под рукой не оказалось, но был китайский клон Arduino Uno на ATmega328. Я попытался поискать библиотеку Arduino под этот дисплей, и нашёл такую. Но она была заточена под ESP8266. Хотя идею замены SPI в функции Send_to_lcd с программного на аппаратный я намотал на ус:
В итоге я взял оригинальную библиотеку Игоря (Igoryosha), и портировал её под Uno, просто заменив в текстовом редакторе функции присваивания (напр. LCD_CLK=0) на ардуиновские digitalWrite(LCD_CLK, LOW). Библиотека запустилась без особых проблем.
Так как в ней используется программная имитация SPI, выводы GPIO можно назначить произвольно (кроме тех, что заняты RX TX). Библиотеку под Arduino Uno для данного дисплея в итоге я оставил у себя на github, в ветке main.
Результат запуска программы и скорость работы такого SPI можно увидеть наглядно на видео:
Теперь можно подсобрать телефон для удобства работы. Я вытащил динамик, чтобы вывести провода через отверстие.
Проверено: дисплей работоспособен, а это означает, что наша задумка в конечном итоге получится.
Как можно было увидеть в предыдущем фрагменте, скорость отрисовки картинки просто черепашья. Оно и неудивительно: контроллер работает всего лишь на 16 МГц, ещё и использует программный SPI для вывода картинки. Ситуацию нужно исправлять, взяв более мощный и производительный контроллер. Тут я натыкаюсь на статью хабровчанина Hoshi.
В ней он данный дисплей подключает к Raspberry Pi, и использует его как «монитор». Поскольку мы стоим на плечах гигантов, было решено проследовать его примеру, и не изобретать ещё какие-либо методики. Но Raspberry Pi у меня не оказалось, нашёлся только Orange Pi PC.
Поэтому запустить код Hoshi без переделки у меня бы не вышло: в своей статье он оперирует GPIO через библиотеки чипсета bcm2835. Так как данного чипсета на моей плате нет, и библиотеку подключить не выйдет. Я начал поиски способа, как подключить GPIO на моём чипсете h3. Выяснилось, что для этого нужна библиотека wiringPi (на текущий момент уже не поддерживается официальными авторами). Однако, просто установить её через apt-get оказалось мало: устанавливался оригинал под Raspberry. Спустя некоторое время была найдена модификация этой библиотеки от zhaolei. Я собрал её через build (ветка h3), и именно она оказалась рабочей для моей платы. После выполнения build библиотека установилась в систему, и стало возможным вызвать её из требуемого места (например, проверить пины с помощью sudo gpio readall).
Назначение выводов GPIO для «апельсина» назначается через define по аналогии с Arduino:
Также следует отметить следующий момент: в схеме Igoryosha для AVR использовался логический уровень +5В, а на Orange Pi разъем GPIO сразу оперирует уровнями +3.3В. Поэтому резисторные делители я убрал. В итоге получилась следующая конструкция:
Однако, у меня сходу не получалось завести аппаратный SPI. Поэтому для повторной проверки работоспособности дисплея я взял, опять же, библиотеку Игоря из-под AVR, портировав её теперь и под wiringPi. К счастью, особых замен не понадобилось, только поменять в коде LOW и HIGH на 0 и 1 соответственно. Даже функции delay в wiringPi аналогичны ардуиновским. Также на данном этапе библиотека лишилась практически всех графических функций отрисовки примитивов, кроме отрисовки, непосредственно, одного кадра из буфера.
После чего я собрал программу через gcc (gcc –o example example.c –lwiringPi), и запустил из терминала.
Результат можно увидеть на видео:
Однако разницы, по сравнению с Arduino, практически нет. Отрисовка стала шустрее, но лишь немножко. Оно и неудивительно: чаще всего вызывается метод передачи по SPI (Send_to_lcd), а так как он у нас всё ещё программный, прироста в скорости мы не видим, сам GPIO работает достаточно медленно, по скорости сопоставим с обычным Arduino. Поэтому нам нужно исправлять ситуацию, задействовав аппаратный SPI.
Для замены программного SPI на апаратный можно, также, задействовать библиотеку wiringPi, а именно, из wiringPiSPI.h использовать функции wiringPiSPISetup и wiringPiSPIDataRW. Функции эти несколько хитрые. Но перед тем, как их использовать, нужно включить этот самый SPI. Информации о том, как это сделать конкретно на Orange Pi PC также в интернете нет, но удалось найти направление, в котором нужно искать. Немного погуглив, я выяснил, что spi включается правкой файла /boot/armbianEnv.txt (актуально для моей версии системы Armbian_23.11.1_Orangepipc_jammy_current_6.1.63_xfce_desktop.img).
В него нужно добавить следующие строки:
После чего сделать sudo reboot, и у нас в /dev/ появляется spidev0.0. Проверить это можно, выполнив команду ls /dev | grep spi. Если spidev0.0 появился, дальше библиотека wiringPiSPI подхватит его. Теперь контакты дисплея CS, CLK, DATA нужно подключить к пинам SPI0, как это сделано у Hoshi. Распиновка (40-pin) полностью соответствует Raspberry Pi. Пины RS и RESET оставляем на попечении обычного GPIO.
Однако, просто заменив SPI на аппаратный, я заметил, что FPS отрисовки практически не поменялся. Проблема заключалась в том, что если отправлять по одному байту, то с каждой посылкой SPI будет заново открываться и закрываться, а эта процедура отнимает очень много времени. Поэтому было решено отправлять данные пачками максимально возможной длины.
Возникла следующая проблема: буферизация пакетов SPI. На данном устройстве мы можем отправить только 4 килобайта данных за одну посылку. Наша страница же занимает порядка 44 Кб: 132*176*2, так как используется 16-битная цветность. В качестве решения можно было либо увеличить буфер SPI, что возможно, однако, мне не хотелось прибегать к данной методике. Поэтому я просто в своём коде раздробил страницу на 11 пачек:
И используя memcpy, копировал перед отправкой каждую пачку в буфер. К слову, это необходимо ещё и потому, что буфер побайтово очищается в процессе передачи, заменяя выходные данные на входные с буфера RX (MOSI pin).
Также библиотека позволяет регулировать скорость: от 500 КГц до предела в 32 МГц. Делается это в момент инициализации: int fd = wiringPiSPISetup(0, 32000000); мы выставляем канал 0, и скорость в 32 МГц.
После вышеописанных процедур мне удалось получить скорость кадров в 60 FPS. Я не уверен, способен ли дисплей отрисовать данные с такой скоростью, но таймер рапортовал именно так. Можно увидеть это на видеозаписи:
На первой половинке видео можно увидеть кусочек области дисплея, который передаётся за одну отправку (4096 байт), это примерно одна десятая всей экранной области. Скорость шины выставлена в 500 КГц. На второй половинке отправка всех 11 областей, и скорость шины в 32 МГц. То есть, скорость передачи примерно порядка 20 Мбит/сек. В данном случае, я считаю, достигнут потолок пропускной способности всей нашей сборки.
Следующее, что сделал Hoshi в своей статье – вывел статичную картинку, получив проблемы с цветностью. Поскольку я иду по его стопам, я попробовал вывести картинку из буфера, используя частично его код, поменяв только сдвиг (offset, так как мой заголовок занял другое количество байт). Однако, сначала я получил такую картинку, как показана на левой части изображения, и, лишь потом, такую, как на правой:
Я взял такую же картинку, как у Hoshi, и получил примерно такой же результат. Подводный камень заключался в следующем:
Два байта нужно поменять местами, этот момент был в коде Hoshi. Либо же добавить/убрать один лишний байт в начале пакета, чтобы вызвать сдвиг всего массива.
Я сохранил исходную картинку через Adobe Photoshop в формат BMP 16-bit. Однако, как выяснилось после просмотра в HEX-редакторе, белый цвет у меня получился не FF FF, а FF 7F, вследствие чего он отображался, как бирюзовый, и остальные цвета также имели искажения:
Произошло это из-за того, что редактор сохранил BMP файл в режиме X1R5G5B5 (с альфа-каналом), а у нас в дисплее используется R5G6B5, то есть, зелёный цвет занимает на один бит больше. Поэтому, при скармливании картинки дисплею, мало того, что один из старших битов пропадает, так ещё и происходит бинарный сдвиг одного цветового канала на единицу, из-за чего вся палитра оказывается искажена. После сохранения картинки в нужном режиме значения белого заменились на FF FF, потери одного бита данных больше не было, и она отрисовалась с нормальной цветностью.
На этом построение библиотеки для работы с дисплеем было закончено, и началось самое интересное – попытка вывести на него живой видеопоток.
Далее вышеупомянутый автор для рендеринга использует интерполяцию из фрейм-буфера ОС Linux /dev/fb0. Попытка запустить его код не привела ни к чему хорошему: в моём случае фреймбуфер отображается как чёрная сетка из-за несоответствия данных, да и мне не нужно было проводить интерполяцию картинки всего рабочего стола, а нужна была конкретная область экрана.
Слева: отрисовка из /dev/fb0. Справа: отрисовка из скриншота.
Так как моя GUI в Armbian работает на графической оболочке XFCE, у меня возникла идея выдрать требуемые пиксели непосредственно через неё. Для этого используются средства gdk и x11. Добавление всех необходимых библиотек сильно усложнило бы программу, поэтому мне пришлось прийти к костыльному решению проблемы.
Я решил копнуть в сторону встроенной в ОС утилиты xfce4-screenshooter. Данная утилита позволяет снять скриншот, в том числе, через командную строку. Однако, функции сохранения заданной области в ней нет, либо требуется задавать каждый раз область мышкой, что было мне неудобно. Поэтому я сделал форк кода данной утилиты. И добавил в опции командной строки, помимо FULLSCREEN, WINDOW и REGION ещё и аргумент FIXED, который сохранял в файл конкретно прописанную в коде область экрана.
Для удобства разработки пришлось поставить xubuntu на виртуальную машину с x86, после чего изменить исходный код, а затем собрать его же, но под armbian непосредственно на своём Orange Pi. Сборка утилиты осуществляется помощью xdt-autogen: сначала ./autogen.sh, далее установить библиотеки по требованию (через apt-install) затем с помощью make, и make install для замены установленного в систему скриншоттера на изменённый вариант. После этого готовый скриншот нужного размера стало можно выводить в файл с помощью одной лишь команды терминала – для определения файлового пути, формата и прочего используется оригинальный код скриншоттера.
Однако, данное решение имеет и недостаток: программа-скриншоттер работает, вероятно, таким образом, что сначала делает скриншот всего root-окна (рабочего стола), затем обрезает его до требуемого размера. При этом, на какой-то момент, отрисовка даже приостанавливается. Вся процедура съемки занимает порядочное количество времени: на десктопе xubuntu она осуществлялась примерно за 50-100 миллисекунд. На Orange Pi она же стала занимать порядка 100-400 миллисекунд. Видеозахват – в целом тяжелая процедура для ЦПУ. Поэтому уменьшение разрешения рабочего стола помогло, но незначительно. В идеале нужно выдирать изображение через низкоуровневый код непосредственно из экранной памяти, а не из пользовательской среды через функции gdk. Более того, в самом коде скриншоттера написано, что рекомендуемая задержка между скриншотами должна быть не менее 200 мс, то есть, это уже ограничивает нас до 5 FPS. В случае, если нужно просто проверить консольный вывод, этого достаточно, а вот для видеопотока оказалось маловато.
Прим. авт.: через некоторое время после написания статьи, мне удалось решить данную проблему, используя вместо скриншоттера ПО jsmpeg-vnc. С ним я получил 50 FPS и выше, плюс имеются встроенные функции обрезки кадра до нужного размера.
Данное ПО передаёт MPEG-поток через WebSocket протокол (выполняя трансляцию видеосигнала), позволяет закодировать только нужную область, чтобы не передавать весь рабочий стол. Далее кадр можно расшифровать и передать непосредственно в контроллер, минуя костыль в виде отображения его на экране и снятия скриншота. Если это будет интересно, можно показать подробнее в следующей статье.
Также, поскольку сохранение в bmp происходит через встроенные средства gdk, мне не удалось заставить программу сохранять в 16-битный формат. Она сохраняла в 24-битный формат, поэтому для преобразования цветовой палитры мне пришлось написать фрагмент кода на бинарных сдвигах:
Изначально содержимое нашего скриншота копируется в массив байтов (uint8_t) в порядке очередности. Для конвертации цвета 24bit -> 16 bit (ещё и с перевёрнутым порядком байтов) использованы сдвиги: мы сравниваем первый байт красного с пятью единицами, далее сравниваем второй байт зелёного с тремя единицами и сдвигаем результат в самое начало, далее сравниваем следующие разряды байта зелёного и сдвигаем в самый конец, далее сравниваем синий и сдвигаем его в середину. Таким образом, из цветности КККККККК ЗЗЗЗЗЗЗЗ ССССССС мы привели палитру к цветности ЗЗЗССССС КККККЗЗЗЗ, которую и принимает наш дисплей. Для отладки я использовал цветные картинки, после чего смотрел, корректно ли отображается цвет согласно своему описанию, или же цветовой канал требуется сдвинуть ещё на какое-то количество ячеек.
После чего я в цикле запустил скриншоттер через вызов терминала, а далее отрендерил картинку на экран. Это можно увидеть на видео:
Конечно, данный пункт программы нуждается в доработке – ссылки на заголовочные файлы библиотек gdk и x11 следует внести в общий файл программы, в котором происходит работа с дисплеем чтобы избежать костыля в виде сохранения картинки в кэш на жестком диске. Возможно, это несколько улучшит производительность. А для идеальной работы требуется переписать это всё на уровне ядра ос, чтобы превратить самодельную библиотеку в драйвер для устройства. Но на текущий момент ход программы получился такой:
Через терминал вызывается скриншоттер, который сохраняет кадр в cache.bmp;
Файл cache.bmp открывается, после чего отправляется его содержимое на дисплей.
Причём, основная потеря скорости идёт на этапе снятия скриншота, а не записи/чтения его с диска. Для увеличения FPS выше 5 необходимо заменить xfce4 скриншоттер на какое-то другое ПО. Тем не менее, мы движемся дальше.
Эмулятор телефона CX75 был написан лет 20 назад, и входил в официальный пакет программ для разработки java-приложений через WTK/JDK 2.0. Он пролежал у меня на жестком диске лет 15, после чего я запустил его для данной работы. Если кому-то интересно также запустить его на своём компьютере, делюсь файлами.
Для работы требуется JDK 6u45 и Windows XP. Насколько я помню, даже при запуске на Windows 7 эмулятор вылетал, на Win 10, тем более, работоспособность я не проверял. Поэтому запускать я его буду через виртуальную машину с WinXP.
Эмулятор полностью реализует функционал прошивки телефона 75-й серии, в том числе, можно устанавливать java-игры, подключать веб-камеру для съемки фото, и так далее. Единственное, вряд ли будет работать интернет, по причине того, что WAP технологии уже не получится использовать.
Вот так эмулятор выглядит в системном окне. Управлять можно с клавиатуры (джойстик – стрелки и enter, клавиши – цифры или тачпад), либо нажатием на виртуальные кнопки.
Выдаёт он картинку чётко размера 132x176, поэтому интерполяция не потребуется. Теперь нужно прокинуть картинку из виртуальной машины в Linux машину. Можно было использовать wine, но я не уверен, будет ли эмулятор адекватно работать на нём. Поэтому он запущен в XP. Для передачи картинки, опять же, ничего нового изобретать я не буду, использую TightVNC.
На Windows мы устанавливаем сервер, на armbian’е устанавливаем клиент через apt-get xtightvncviewer.
После чего запускаем клиент-сервер, и выставляем на экране требуемую зону отображения. FPS в данном случае также примерно равен 5 кадрам, поэтому дисплей будет рендерить с точно такой же скоростью.
И вот момент, ради которого всё затевалось: помещаем эмулятор телефона в экранную область на самом телефоне
Видео:
Таким образом, наш видеопоток проходит через следующие уровни:
Эмулятор CX75 (x86 C-программа, но порт ARM-совместимой прошивки);
Windows XP (виртуальная машина);
Windows 10 (через виртуализацию, но можно пропустить, выведя в VNC напрямую с XP);
Armbian xfce4 gui (через VNC);
Изображение cache.bmp (через xfce4-screenshooter);
Дисплей C75 (через wiringPi + wiringPiSPI).
Для эмуляции же клавиатуры достаточно просто припаять контактные площадки к контроллеру от USB-клавиатуры в соответствии со схемой их разводки:
Делать этого сейчас я не буду по причине того, что это несколько монотонное занятие, и вся задача заключается лишь в правильном сопоставлении таблиц кнопок и таблиц на контроллере от USB клавиатуры. После чего клавиатура подключается к виртуальной машине, и можно испытать полное погружение в эмулятор.
Результат проекта:
В ходе работы были изучены особенности работы с GPIO, SPI, GTK3, VNC, преобразованием цветности и некоторым другим функционалом компьютерных и микроконтроллерных систем.
Готовые файлы проекта под wiringPi.
Спасибо за внимание.
Написано специально для Timeweb Cloud и читателей Пикабу. Подписывайтесь на наш блог, чтобы не пропустить новые интересные материалы.
Также подписывайтесь на наш телеграмм-канал — только здесь, технично, информативно и с юмором об IT, технике и электронике. Будет интересно.
Облачные сервисы Timeweb Cloud — это реферальная ссылка, которая может помочь поддержать наши проекты.
Если сейчас приехать в пункт приема металлолома, то можно обнаружить просто огромные кучи различных телефонов и прочих электронных «отходов», которые стоят под открытым небом и ждут, когда придёт их черёд окончательного разложения. Однако при ближайшем рассмотрении выясняется, что многие девайсы оказываются полностью рабочими даже после недельного лежания под палящим солнцем и проливными дождями, а сдали их в чермет по причинам «не нужен, надоел, купил новый» и т. п. Я не считаю это правильным, ведь даже в простые кнопочные звонилки имеется возможность вдохнуть новую жизнь, если знать один интересный, но малоизвестный факт: для них можно писать нативные приложения на C и использовать железо телефона в своих целях. А это, на минуточку, как минимум: дисплей с подсветкой, вибромотор, динамик, клавиатура и GSM-радиомодуль с возможностью выхода в сеть. Сегодня мы с вами: узнаем, на каких аппаратных платформах работают китайские телефоны, какие существуют программные платформы и где взять для них SDK, а в практической части мы напишем 2D-игру с нуля, которая будет работать на многих китайских кнопочниках. Интересно? Тогда жду вас под катом!
Содержание:
Не J2ME едины
Аппаратные ресурсы
Кроссплатформенный рантайм
Кроссплатформенный рантайм: Win32
Кроссплатформенный рантайм: MRE
Кроссплатформенный рантайм: VXP
Наконец-то пишем игру
Тестируем на реальных девайсах
Заключение
Думаю, многие мои читатели помнят о такой платформе, как J2ME. Java-приложения стали фактически основной возможностью расширения функционала телефонов в 2000-х годах. API для них был достаточно хорошо стандартизировано, программы не зависели от архитектуры процессора и ОС устройства, а порог вхождения для написания собственных приложений был довольно низкий и даже новички могли за пару дней написать свою игрушку или какое-нибудь GUI-приложение!
Однако не одним J2ME мы были едины: существовало множество платформ, которые так или иначе пытались занять нишу Java на рынке. Некоторые из них я упоминал в своей прошлой статье о написании 3D-игры под Sony Ericsson с нуля: например, была такая платформа на телефонах Sony Ericsson серии T, как Mophun, а CDMA-телефонами с чипсетами Qualcomm использовалась нативная платформа BREW. Пожалуй, я не буду упоминать о .sis и .cab — поскольку это форматы нативных приложений для смартфонов, а не простых «фичефонов».
В какой-то момент, ближе к 2006-2007 году, прилавки российских официальных ритейлеров (по большей части это были телефоны Fly) и неофициальных продавцов на рынках заполонили различные китайские телефоны, которые предлагали какой-то немыслимый функционал для тех лет за копейки, да ещё и визуально напоминали флагманские модели известных брендов. Пожалуй, одним из самых популярных таких телефонов была Nokla TV E71/E72 (да, именно «нокла»), вышедшая примерно в 2008 году и производившаяся аж до 2011 года! За 2-3 тысячи рублей (это менее 100 баксов), пользователь получал здоровый 2.4" дисплей с разрешением 240x320 весьма неплохого качества (когда в те годы многие продолжали ходить с 176x220), да ещё и с тачскрином, гироскоп, огромный громкий динамик (пусть и не очень качественный), поддержку SD-карточек до 32Гб, нередко фронтальную камеру, а также премиальный дизайн с вставками из алюминия. Частенько китайцы заботливо клали в коробку ещё чехольчик и дополнительный аккумулятор :)
Были даже полные копии существующих устройств от Nokia. Особенно китайцы любили подделывать массовые модели на S40: они были очень популярными и китайцы хотели откусить свой кусок рынка у Nokia. Пусть и рынка серого импорта — очевидно, в салонах связи подделки никто не продавал:
Но была и ложка дёгтя в этой бочке меда: китайские телефоны очень часто не имели поддержки Java, из-за чего многие пользователи разочаровывались в них из-за отсутствия возможности установить необходимые им приложения. Никакой тебе оперы, аськи, игр… Скорее всего, это связано с необходимостью отчислений Sun, а также разработчикам реализации J2ME-машины (JBed/JBlend) и установки чипа флэш-памяти чуть большего объёма.
Но многие пользователи не знали, что такие девайсы не просто поддерживали сторонние приложения, но и умели выполнять настоящие нативные программы, написанные на полноценном C! Всему помешала китайская костыльность и тотальная закрытость. Платформа предполагалась для работы на внутреннем рынке. Для вызова менеджера нативных приложений необходимо было вводить специальный инженерный код в номеронабирателе, предварительно скопировав приложение в нужную папку, а SDK долгое время было платным и доступно только для компаний из Китая. Кроме того, далеко не все приложения могли запустить на конкретном девайсе — были серьезные проблемы с совместимостью.
Всё как вы любите: HiTech-девайсы на фоне ковра, который старше автора лет на 30 :)
В ранних китайских телефонах использовалась платформа Mythroad (MRP, MiniJ) от китайской компании SkyWorks, которая лицензировала свою технологию производителям чипсетов. Поддержку MRP можно было встретить на телефонах с чипсетами MediaTek, Spreadtrum, а также MStar (и возможно Coolsand). Mythroad предоставлял некоторое API для работы с железом телефона и разработки как UI-приложений, так и игр, кроме того, Mythroad позволял хранить ресурсы в одном бинарнике с основной программой и даже имел какой-то интерпретируемый язык помимо возможности запуска нативного кода. Для работы таких приложений необходимо было скопировать менеджер приложений dsm_gm.mrp и игру в папку mythroad во внутренней памяти устройства или на флэшке, а затем набрать в номеронабирателе код *#220807#, иногда при отключенной первой SIM-карте. Костыльно? Костыльно! Откуда об этом знать среднестатистическому пользователю? Не откуда! Но работало!
Эта платформа поддерживалась на большинстве подделок под брендовые устройства Nokia, Sony Ericsson и Samsung, а также iPhone и на многих китайских кнопочных телефонах 2008-2010 годов.
Ближе к 2010 году MediaTek разработала свою собственную платформу, которая должна была заменить MRP — WRE (VXP). Эта платформа была гораздо шире с точки зрения функционала (например, был доступ к UART) и её API был вполне удобно читаем для программиста, а SDK свободно доступен для всех. Один нюанс всё портил — приложения без подписи привязывались к IMSI (даже не IMEI) симки в девайсе и на некоторых девайсах требовали переподписания под каждую конкретную SIM или патчинг дампа оригинальной прошивки телефона на отключение проверки подписи. Эта платформа поддерживалась на многих кнопочниках и смарт-часиках 2010-2020 годов: к ним относятся новодельные телефоны Nokia, телефоны DNS и DEXP, Explay и т. п. Для запуска приложений достаточно было выбрать файл с разрешением VXP в проводнике и просто запустить его. Но с совместимостью всё равно имелись проблемы: если запустить VXP для версии 2.0 и выше, мы получим лишь белый экран. Ну хоть не софтресет, и на том спасибо!
Далеко не все такие часы поддерживают MRE, смотреть нужно от устройства к устройству
Большинство китайских кнопочных телефонов работает на базе одних и тех же чипсетов. В конце нулевых чаще всего использовались чипсеты MT6225, SC6520 и некоторые чипы от Coolsand. Средние хар-ки девайса были следующими:
Процессор: ARMv5 ядро на частоте ~104МГц, ARM926EJ-S. Нет FPU, есть Thumb. Большую часть процессорного времени программа могла забрать себе.
ОЗУ: ~4Мб SDRAM. Программам было доступно 512Кб-1Мб Heap'а. Это, в целом, довольно немало для большинства применений.
Флэш-память: ~32Мб, пользователю доступно пару сотен килобайт. Да, вы не ослышались, килобайт! Однако можно без проблем использовать MicroSD-флэшки до 32Гб.
Дисплей: от 128x128 до 320x480, почти всегда есть 18-битный цвет (262.000 цветов), в случае TV E71/E72 используется очень неплохая TN-матрица с хорошими углами обзора и яркой подсветкой. Иногда есть тачскрин.
Звук: громкий динамик, наушники.
Аккумулятор: ~800мАч, на некоторых девайсах может быть и 2.000мАч, а то и больше!
Ввод: клавиатура, иногда была поддержка QWERTY.
Внешние шины: почти всегда был доступен UART, причём его можно было свободно взять прямо с платы — он был явно подмечен! Взять GPIO с проца не выйдет (кроме, возможно, вибромотора), SPI и I2C также напрямую недоступны. Внешние шины можно реализовать с помощью UART через GPIO-мост из микроконтроллера.
В итоге мы получаем очень неплохие характеристики для устройства, которое сочетает в себе сразу всё. На базе такого девайса можно сделать и сигнализацию, и HMI-дисплей с интерфейсом для управления каким-нибудь устройством, и игровую консоль с эмуляторами… да на что фантазии хватает! И это за какие-то 200-300 рублей, если мы говорим о б/у устройстве или 600 рублей, если говорим о новом. Это дешевле, чем собирать девайс с подобным функционалом самому из готового МК (например, RP2040) и отдельных модулей. Кстати, дешевые 2.4" дисплеи на алике — это ни что иное, как невостребованные остатки дисплеев для подобных китайских телефонов на складах! А вы думали, откуда там значки на тачскрине снизу?
Однако в рамках данной статьи мы не будем ограничиваться лишь теорией и на практике напишем примитивную 2D-игрушку, которая будет работать сразу на трех платформах без каких-либо изменений в коде самой игры: Windows, MRP (Mythroad) и VXP. Но для того, чтобы достигнуть такого уровня абстракции от платформы, нам необходимо написать рантайм, который оборачивает все необходимые платформозависимые функции для нашей игры.
Игрушка будет простой: 2D скролл-шутер с видом сверху, а-ля Asteroids. Летаем по космосу, и стреляем по враждебным корабликам, стараясь не попасть под вражеские лазеры. Всё просто и понятно :)
Итак, что нам необходимо от абстракции для такой простой игры? Давайте посмотрим:
Графика: очистка экрана, отрисовка спрайтов с прозрачностью (без альфа-блендинга, только колоркей), отрисовка текста. При возможности, желательно использовать нативное API системы для рисования графики, а не городить собственный блиттер. Формат пикселя фиксирован: RGB565 (65к цветов).
Ресурсы: хранятся в одном образе с основной игрой. Фактически, все ресурсы упакованы в виде обычных массивов байт в заголовочных файлах. Я пользуюсь вот этой тулзой для конвертации спрайтов в массивы байтов.
Звук: воспроизведение хотя-бы одного WAV-потока. Почему одного? Потому что далеко не на всех платформах есть доступ к аппаратному микшеру… да и вообще не везде есть прямой доступ к PCM (привет MRP), иногда разработчики ограничиваются лишь одним каналом для WAV-звука без возможности воспроизведения нескольких аудиофайлов одновременно.
Ввод: абстракция от клавиатуры классического моноблока: стрелки, OK, левый и правые софткеи.
Стандартная библиотека: не на всех платформах можно вызывать функции напрямую из stdlib. Как минимум в MRP и, например, «эльфах» для Motorola, нет возможности вызывать аллокатор, rand и некоторые другие функции из обычных заголовочников стандартной библиотеки. На таких платформах, системные инклуды дефайнами подменяют стандартные функции на своих реализации:
#define malloc system_alloc
#define free system_free
Но если у нас игра кроссплатформенная, то и платформозависимые инклуды мы использовать не будем.
Выглядит всё достаточно просто, верно? Примерно такого набора функций хватит для нашей игры:
Давайте же перейдем к реализации рантайма на каждой платформе по отдельности. Начнём с Win32, поскольку адекватно отлаживать игру можно только на ПК.
На десктопе у нас будет фиксированное окно 240x320, в качестве GAPI будет использоваться аппаратно-ускоренный OpenGL, а для обработки ввода будет использоваться классически GetAsyncKeyState. Реализация точки входа, создания окна и инициализации контекста GL и главного цикла приложения у нас такая:
Реализация отрисовки спрайтов очень примитивная — OGL 1.0, полностью FFP, вся отрисовка — это 2 треугольника, формирующие квад. Спрайт заливается при первом использовании в текстуру, последующие кадры реюзается уже готовая текстура. Фактическая реализация всего рендерера — т. е. функций для рисования «просто картинок», без поддержки атласов, блендинга цветов (З.Ы - длинные листинги будут на пастбине, на Пикабу нет нормального тега для кода):
С вводом тоже всё просто. Есть биндинг кнопок клавиатуры к кнопкам на кейпаде телефона. inGetKeyState предполагается вызывать один раз за кадр, поэтому функция опрашивает ОС о состоянии нажатых кнопок на клавиатуре и назначает состояние виртуальных кнопок относительно состояния физических кнопок на клавиатуре.
Результат:
Переходим к реализации рантайма для первой китайской платформы — MRP. Обратите внимание — я использую нативное API платформы для рисования спрайтов. Связано это с тем, что софтварный блиттер работает невероятно медленно даже с прямым доступом к скринбуферу устройства, а в чипсете предусмотрена отдельная графическая подсистема с командбуфером для быстрой отрисовки примитивов и графики:
SDK для MRE можно найти здесь (SKYSDK.zip): оно уже пропатчено от необходимости покупки лицензии. MRP не развивается более 10 лет, поэтому, думаю, его можно считать Abandonware. Компилятор находится в compiler/mrpbuilder.NET1.exe. За китайские SDK в публичном доступе нужно поблагодарить пользователя 4pda AjlekcaHgp MejlbHukoB, который раздобыл их на всяких csdn и выложил в свободный доступ :)
У MRP собственная система сборки, основанная на конфигурациях. Поскольку MRP может работать на устройствах с разными платформами и размерами дисплеев, под каждую можно настроить свой конфиг, который пережмет ресурсы в нужный формат. Дабы ничего не ломать, я заюзал абсолютные пути:
Компиляция приложения:
mrpbuilder.net1.exe game.mpr
Начинаем с функций обработки событий и инициализации, которые вызывает рантайм при старте приложения: mrc_init вызывается при старте приложения, а mrc_event при возникновении события. Вся инициализация очень простая: создаём таймер для обновления и перерисовки состояния игры и вызываем инициализацию игры:
С вводом тоже никаких проблем нет, нажатия кнопок прилетают как события в mrc_event. Переводим кейкоды MRE в наши кейкоды и сохраняем их состояние:
Опять же, отлаживать MRP-приложение под реальным устройством проблематично, поэтому платформозависимый код должен быть минимальным. Кроме того, обратите внимание, что некоторые функции в MRP зависят от библиотек-плагинов. Линкер слинкует вашу программу, но на реальном устройстве их вызов вывалится в SIGSEGV и софтресет устройства. Также нельзя использовать ничего из стандартной библиотеки именно в стандартных заголовочниках (т. е. stdlib.h, string.h и т. д.), часть стандартной библиотеки реализовывается MRP и дефайнится в mrc_base.h
Что интересно, защиты памяти толком нет. Если приложение падает в SIGSEGV или портит память — систему, судя по всему, ребутит Watchdog. Защиты памяти никакой, можно напрямую читать и писать в память ядра, а также писать в регистры периферии чипсета. jpegqs, покумекаем над этим? :)
Переходим к рендереру. Тут буквально две функции, gClearScreen очищает экран, а gDrawBitmap рисует произвольный спрайт с форматом пикселя RGB565. В качестве ROP используется BM_TRANSPARENT — таким образом, mrc_bitmapShowEx будет использовать левый верхний пиксель в качестве референсного цвета для реализации прозрачности без альфа-блендинга.
void gDrawBitmap(CBitmap* bmp, int x, int y) {
mrc_bitmapShowEx((uint16*)bmp->pixels, x, y, bmp->width, bmp->width, bmp->height, BM_TRANSPARENT, 0, 0);
}
Да, всё вот так просто. Рантайм теперь запускается на реальных китайских девайсах и работает стабильно.
Теперь переходим к VXP — платформе не менее неоднозначной, чем MRP. Пожалуй, начать стоит с того, что VXP существует аж в трёх версиях: MRE 1.0, MRE 2.0 и MRE 3.0. В MRE 2.0 и выше появилась поддержка плюсов (в MRE 1.0 только Plain C) и довольно интересного GUI-фреймворка, MRE 1.0 же предлагает реализовывать гуй самому. Платформа распространена на большинстве кнопочных телефонов и смарт-часиков на чипсетах MediaTek, примерно начиная с 6235 и заканчивания 6261D. SDK можно скачать вот здесь (см MRE_SDK_3.0).
VXP сам по себе более функционален чем MRE, поскольку ориентирован исключительно на телефоны с чипсетами MediaTek. Но что самое приятное — есть доступ к уарту без каких либо костылей! То есть, если сделать GPIO-мост на условной ESP32, то мы можем получить готовый мощный МК с клавиатурой, кнопками, дисплеем, звуком и т. д. Звучит не хило, да? Кроме того, у нас есть доступ и к BT, и к GPRS, и к SMS без каких либо ограничений.
Однако в бочке мёда нашлась и ложка дёгтя: для компиляции MRE-приложений необходимо накатывать и крякать довольно старый компилятор ADS, который сам по себе поддерживает только C89 (например, нет возможности объявить переменную в объявлении цикла или середине функции, только в начале, как в Pascal). ADS уже вроде как Abandonware, так что это вроде не наказуемо… но всё равно неприятно.
Кроме того, на некоторых девайсах (в основном, фирменных Nokia а-ля 225), прошивка требует подписи у всех бинарников, либо если бинарник отладочный, то должна быть привязка к конкретному IMSI.
К тому же, каждая программа должна фиксированно указывать в заголовке, сколько Heap-памяти ей необходимо выделить. Оптимальный вариант — ~500Кб, тогда приложение запустится вообще на всех MRE-телефонах.
Зато у VXP есть адекватный симулятор под Windows. Но зачем он нам, если у нас порт игры под Win32 есть? :)
Начинаем с инициализации приложения. В процессе вызова точки входа, приложение должно назначить обработчики системных событий, коих бывает несколько. Для обработки ввода и базовых событий хватает всего три: sysevt (события окна), keyboard (физическая клавиатура. Есть полная поддержка QWERTY-клавиатур), pen (тачскрин).
vm_reg_sysevt_callback(handle_sysevt); vm_reg_keyboard_callback(handle_keyevt); vm_reg_pen_callback(handle_penevt);
Переходим к обработчику системных событий. Обратите внимание, что MRE-приложения могут работать в фоне, из-за чего необходимо ответственно подходить к созданию и освобождению объектов. Что важно усвоить с самого начала — в MRE нет понятия процессов и защиты памяти, как на ПК и полноценных смартфонах. Любая программа может попортить память или стек ОС, более того, программа использует аллокатор остальной системы, поэтому если ваша программа не «убирает» после себя, данные останутся в памяти со временем приведут к зависанию. Впрочем, WatchDog делает свою работу быстро и приводит телефон в чувство (софтресетом) за 1-2 секунды. Но как и в случае с MRE, есть приятный бонус: прямой доступ к регистрам чипсета :)
Переходим к обработке событий с кнопок. Тут всё абсолютно также, как и на MRE, лишь имена дейфанов поменялись :)
И наконец-то, к графике! Пожалуй, стоит сразу отметить, что более 20-30 FPS на большинстве устройств вы не получите даже с прямым доступом к фреймбуферу. Похоже, это связано с тем, что в MRE довольно замороченная графическая подсистема с поддержкой альфа-канала (только фиксированного во время вызова функции отрисовки картинки/примитивов, сам пиксельформат всегда RGB565) и нескольких слоев. Кроме того, похоже есть ограничения со стороны контроллера дисплея.
Изначально, MRE предполагает то, что все картинки в программе хранятся в формате… GIF. Да, весьма необычный выбор. Однако для работы с пользовательской графикой, есть возможность блиттить произвольные картинки напрямую из RAM. Вот только один нюанс — посмотрите внимательно не объявление следующей функции:
void vm_graphic_blt( VMBYTE * dst_disp_buf, VMINT x_dest, VMINT y_dest, VMBYTE * src_disp_buf, VMINT x_src, VMINT y_src, VMINT width, VMINT height, VMINT frame_index );
dst_disp_buf — это целевой RGB565-буфер. Логично предположить, что и src_disp_buf — тоже обычный RGB565-буфер! Но как бы не так. Документация крайне скудная, пришлось посидеть и покумекать, откуда в обычном 565 буфере возьмется индекс кадра. С подсказкой пришёл пользователь 4pda Ximik_Boda — он скинул структуру-заголовок, которая идёт перед началом каждого кадра. В документации об этом не сказано ровным счетом ничего!
Сначала я реализовал софтовый блиттинг, но он безбожно лагал. Мне стало интересно, почему нативный blt быстрее и… вопросы отпали после того, как я поглядел в ДШ чипсета: тут есть аппаратный блиттинг. И даже с ним девайс не может выдать более 20FPS!
Для реализации более-менее шустрого вывода графики, необходимо сначала создать канвас (фактически, Bitmap в MRE), создать и привязать к нему layer, получить указатель на буфер слоя и только потом скопировать туда нашу картинку. Да, вот так вот замороченно:
И только после этого всё заработало достаточно шустро :)
В остальном же платформа довольно неплохая. Да, без болячек не обошлось, но всё же перспективы вполне себе есть.
На данный момент, этого достаточно для нашей игры.
Рантайм у нас есть, а значит, можно начинать писать игрушку. Хоть пишем мы на Plain-C, я всё равно из проекта в проект использую +- одну и ту же архитектуру относительно системы сущностей, стейтов и т. п. Поэтому центральным объектом у нас станет CWorld, который хранит в себе на пулы с указателями на другие объектами в сцене, а также игрока и его состояние:
typedef struct {
CPlayer player;
int nextSpawn; // In ticks
CEnemy* enemyPool[ENEMY_POOL_SIZE];
CProjectile* projectilePool[PROJECTILE_POOL_SIZE];
} CWorld;
Система стейтов простая и понятная — фактически, между состояниями передавать ничего не нужно. При нажатии в главном меню на «старт», нам просто необходимо проинициализировать мир заново и начать геймплей, при смерти игрока — закинуть его обратно в состояние меню. Стейты представляют из себя три указателя на функции: переход (инициализация), обновление и отрисовка.
typedef void(CGameStateCallback)();
Поскольку мы хотим некоторой гибкости при создании новых классов противников, то вводим структуру CEnemyClass, которая описывает визуальную составляющую врагов и их флаги — могут ли они стрелять по игроку или просто летят вниз (астероиды), как они передвигаются (зигзагами например) и т. п.
А также описываем игрока:
typedef struct
{
int health;
int frags;
int score;
int speed;
int nextAttack;
int x, y;
} CPlayer;
Всё! Для текущего уровня реализации игры этого достаточно :)
Переходим к реализации игровой логики. Вообще, динамический аллокатор в играх для китайских платформ лучше использовать как можно меньше. Heap'а довольно мало (~600Кб), да и не совсем понятно, как этот аллокатор реализован, есть вероятность, что используется аллокатор и куча основной ОС.
Начинаем с реализации полёта кораблика. Для этого он должен реагировать на стрелки и не улетать за границы экрана, а ещё для красоты он должен «вылетать» из нижней границы экрана при старте игры:
Переходим к динамическим пулам с объектами. Как вы уже заметили, их всего два — враги и летящие снаряды. Реализация спавна врагов/снарядов простая и понятная: мы обходим каждый элемент пула, если указатель на объект не-нулевой, значит объект всё ещё жив и используется на сцене. Если нулевой — значит ячейка свободна и можно заспавнить новый объект:
При обходе пула во время обновления кадра, мы обновляем состояние каждого объекта и если его функция Think вернула true, значит объект больше не нужен и его нужно удалить:
if (enemyThink(world.enemyPool[i]))
{
sysFree(world.enemyPool[i]);
world.enemyPool[i] = 0;
}
А вот и реализация Think:
bool enemyThink(CEnemy* enemy) {
enemy->y += enemy->_class->speed;
if (enemy->y > gGetScreenHeight() || enemy->health <= 0) return true;
return false;
}
Но кораблики должны же откуда-то появляться! Для этого у нас есть переменная nextSpawn, которая позволяет реализовать самый простой тип спавнера — относительно времени (или в нашем случае тиков):
world.nextSpawn--;
if (world.nextSpawn < 0) {
CEnemy* enemy = spawnEnemy(&enemyClasses[0]);
world.nextSpawn = randRange(40, 70);
}
Результат: мы уже можем полетать, пострелять и поуворачиваться от вражеских корабликов!
Уже что-то напоминающее игру! Осталось лишь добавить подсчет очков, менюшку, разные виды противников, возможно какие-то бонусы и у нас будет готовая простенькая аркада. В целом, выше приведена достаточно неплохая архитектура для простых 2D-игр на Plain C. Фактически, она может быть хорошей базой и для ваших игр: в теме о китах на 4pda я встречал немало людей, которые банально не знали, с чего начать.
Но без тестов на реальных устройствах материал не был бы таким интересным! Поэтому давайте протестируем игру на двух реальных телефонах, как вы уже догадались, один — Nokla TV E71, а второй — клон Nokia 6700, который подарил мне мой читатель Никита.
На TV E71 игра идёт не сказать что очень бодро. Кадров 15 точно есть, что, учитывая разрешение 240x320, весьма неплохо для такого девайса.
а 6700,, даже учитывая более низкое разрешение — 176x220, дела примерно также — ~15FPS! Но поиграть всё равно можно. Уже хотите написать «автор наговнокодил, а теперь ноет из-за низкого FPS»? Ан-нет, я попробовал игры сторонних разработчиков — они идут примерно также :( К сожалению, таковы аппаратные ограничения устройства.
Исходный код игры с Makefile'ами и файлами проектов для Visual Studio и MRELauncher доступны на моём GitHub. Свободно изучайте и используйте его в любых целях :)
Но в остальном же, демка получилась довольно прикольной, как и сам опыт программирования для китайских телефонов. В общем и целом, китайцы пытались максимально упростить API и привлечь разработчиков к своей платформе. Если ради примера взглянуть на API для Elf'ов на Motorola, можно ужаснуться от state-based архитектуры платформы P2K. А тут тебе init, event, draw — и всё!
Но популярности помешала непонятная закрытость платформы, костыльный запуск программ, отсутствие нормального симулятора. А ведь сколько фишек было: даже возможность писать и читать память ядра!
А вы как считаете? Можно ли вдохнуть в китайские кнопочники новую жизнь, узнав о наличии возможности запуска нативного кода на них?
P. S.: Друзья! Время от времени я пишу пост о поиске различных китайских девайсов (подделок, реплик, закосов на айфоны, самсунги, сони, HTC и т. п.) для будущих статей. Однако очень часто читатели пишут «где ж ты был месяц назад, мешок таких выбросил!», поэтому я решил в заключение каждой статьи вставлять объявление о поиске девайсов для контента. Есть желание что-то выкинуть или отправить в чермет? Даже нерабочую «невключайку» или полурабочую? А может, у этих девайсов есть шанс на более интересное существование! Смотрите в соответствующем посте, что я делаю с китайскими подделками на айфоны, самсунги, макбуки и айпады! Да и чего уж там говорить: эта статья уже сама по себе весьма наглядный пример! Найти меня можно в комментариях тут, на Пикабу, и в тг @monobogdan
Понравился материал? У меня есть канал в Телеге, куда я публикую бэкстейдж со статей, всякие мысли и советы касательно ремонта и программирования под различные девайсы, а также вовремя публикую ссылки на свои новые статьи. 1-2 поста в день, никакого мусора!
Материал подготовлен при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, дабы не пропускать новые статьи каждую неделю!
Автор: kesn
Больше интересных фото и комментариев в оригинале материала
Когда на меня накатывает хандра, я бросаю всё и пилю свой игровой движок. Это неблагодарное занятие, но меня прёт.
В самом начале у меня были такие планы: вжух-вжух, щас возьму ведро, накидаю туда всяких библиотек для графики, физики и звуков, добавлю сетевую библиотеку по вкусу, перемешаю всё с какой-нибудь системой сообщений, и готово. Приключение на 15 минут.
И вот я тут спустя 5 лет.
Ладно, если быть честным, то я почти не уделял времени разработке, потому что постоянно спотыкался на всяких бесящих меня ошибках: то сериализация не работает с наследованием, то потоки не хотят нормально разделять память, то обновление языка ломало совместимость… Я могу, блин, целую Камасутру написать про соитие с игровым движком. Все эти ошибки сильно демотивируют, потому что хочется уже наконец-то заняться делом, а не ковыряться с байтиками.
Это не моё видео, но оно очень точно передаёт, как у меня происходит разработка:
С другой стороны, конечно, когда эти проблемы решаешь, чувствуешь себя богом и королём жизни, и после этого ты вроде как опять хочешь программировать. И даже кажется, что это была последняя трудность. Ха-ха, наивный!.. Но мне это нравится. Типа как альпинисты идут в гору и страдают, когда можно пойти в бар с друзьями и попить пивко. Каждому своё.
Ну и вот про одну такую ошибку я хотел поговорить. Есть такой движок — ODE (Open Dynamics Engine). Он появился где-то в палеолите, динозавры его накодили, от документации остались только царапины на скалах. Но он работает, он простой в использовании, и у него есть сишные заголовки, поэтому я мог просто написать враппер на Nim и использовать его в своём движке. В Nim вообще ни хрена нету, поэтому канонический способ — это взять какую-нибудь библиотеку из Си, научиться её вызывать, а потом говорить всем, что ты написал крутую программу на Nim.
Итак, сначала я просто добавил кубики на сцену и отрисовал их. Физический движок был в полной гармонии с графическим, и когда мне графика отрисовывала, что я приближаюсь к кубу и толкаю его, кубик действительно отлетал и вращался. Короче говоря, всё шло так, как я и планировал.
Разумеется, если вы не планируете делать Minecraft, то вам может понадобиться что-то поинтересней, чем кубик. В ODE есть специальный класс Trimesh, который как раз позволяет вам сделать сложную геометрию. Фактически, вы можете создать любое тело из набора треугольников. Типа такого:
Машина глупая, поэтому мы не можем ей сказать "нарисуй зайца", мы можем ей сказать "вот такие есть вершины, соедини их вот так-то, и это будет называться зайцем". Я, естественно, не стал рубить с плеча и решил вместо зайца сделать простой треугольник и проверить, что всё корректно с ним работает.
Я создал треугольник, он успешно отрисовался на сцене, я начал ходить по миру… и обнаружил, что треугольник не в том месте, где нарисован, а где-то непонятно где.
Так и появилась эта дурацкая невидимая стена.
Фактически, я мог ходить по сцене, и где-то я упирался в тот самый треугольник, который вообще-то должен был быть там, где нарисован. Я в принципе даже что-то такое и ожидал, потому что, как я уже где-то писал, только три раза в своей жизни я написал код, который заработал с первого раза. Наверняка я где-то перепутал координату — вместо X передал Y, или наоборот, ну что-нибудь такое. Эти программисты, вы знаете!
Отрисовать этот треугольник я не мог, потому что координаты были правильные, графический движок всё отрисовывал правильно, но вот физический движок как-то неправильно интерпретировал мои правильные данные. Поэтому я стал ходить по миру и пытаться определить очертания этой невидимой стены. В конце концов я её нашёл (она была достаточно странной), и я решил немного подвигать треугольник, чтобы посмотреть, как он влияет на эту стену. Казалось бы, если я просто где-то перепутал координаты, то подвинув треугольник, я немножко подвину эту стену. Но хрен мне там! Стена исчезала и появлялась совершенно случайно, прыгала далеко даже от малейшего изменения координат, и я не мог понять, почему.
И тут я вспомнил эту недалёкую женщину из заЩИТников! Если кто не знает, она сделалась невидимой и решила спрятаться в дожде. Отличный план, надёжный, как швейцарские часы:
Я подумал, что это прям мой случай, и решил полить свою стену дождём, чтобы увидеть её. Дождя у меня не было, зато были кубики, поэтому я создал штук 50 и стал кидать их вниз. При касании стены они к ней прилипали, и я мог видеть её очертания. А когда что-то видишь — отлаживать в разы легче!
Что ж… Это была хорошая попытка понять, по какому закону стена появляется в том месте, где она появляется, но это мне ничего не дало. Даже видя эту стену, я не находил никакой закономерности.
Если нужно где-то найти таких же неудачников, как я, то самое лучшее место для этого — интернет. И я нашёл его — единственного человека, который отстрадал своё и рассказал об этом. Представляете, в 2006 году у какого-то чувака из Германии пятая точка горела точно так же, как у меня сейчас! Не знаю, что он выкурил (похоже, что исходники), но, ОКАЗЫВЕТСЯ, физический движок ожидает от вас трёхмерные точки, но передавать их надо как четырёхмерный вектор, просто в четвертой координате надо поставить мусор, типа так: [x1, y1, z1, 0, x2, y2, z2, 0, ...]. Скажите, как по const dReal* Vertices я должен понять, что там ждут в гости четырёхмерные вершины?
За что я люблю опенсорс — можно всегда докопаться до истоков всего. Я полез в исходники, и вот что обнаружил.
В 2003 году пришёл Russel Smith и добавил всю эту функциональность с trimesh collisions, в том числе интересующую меня строчку:
Тут всё понятно, потому что в определении чётко говорится, что dVector3 — это четырёхмерный вектор (есть некий шарм в этой логике).
А потом через пару месяцев врывается Erwin Coumans и переписывает так, чтобы тип был непонятен:
И только представьте себе, через 20 лет это изменение находит какого-то чувака (меня), который пишет вообще на другом языке программирования, и заставляет этого чувака гореть в тщетных попытках понять, какого хрена не работает.
Я переписываю код с добавлением четвертой координаты, и все начинает работать.
Такие дела.
Вообще этот пост был задуман как развлекательный, типа "смотрите, погромист опять страдает, хахаха". Но мне кажется, что он поднимает достаточно глубокую проблему: как только вы выкладываете код, он начинает свой долгий путь сквозь время. Никто не знает, когда и кто его будет читать — может, вы или ваш коллега через пару месяцев, может, тысячи независимых разработчиков через пару лет, может, какой-то парень с горящим продом.
Получается такой вот эффект бабочки, как с этой невидимой стеной. Поэтому когда вы в следующий раз сядете писать код, представьте, что какой-то разраб через 20 лет будет в нём разбираться, и, пожалуйста, постарайтесь сделать жизнь этого чувака хоть чутоку легче. Ведь однажды этим кем-то можете оказаться вы сами.
Не только лишь все могут смотреть в будущее, но вы сможете, если подпишетесь на мой уютненький канал Блог Погромиста.
А ещё я держу все свои яйца в одной корзине (в смысле, все проекты у одного облачного провайдера) — Timeweb. Поэтому нагло рекламирую то, чем сам пользуюсь — вэлкам.
Источник: Наука и Технологии
Шлем, который защищает голову от падения смартфона, когда вы засыпаете.
Автор: CyberexTech
Больше интересных фото и комментариев в оригинале материала
Однажды у меня возникла потребность подключения своего узла учета электрической энергии к системе умного дома «Home Assistant», но вот беда, установленный счетчик ЭНЕРГОМЕРА СЕ101 не обладал смарт функциями, позволяющими без проблем подключать его к системам умного дома, а цена электросчетчиков со смарт функциями просто заоблачная. Но я нашел экономичное решение с ценой решения задачи менее $5 и об этом далее.
Итак, как я говорил ранее, для учета электрической энергии у нас установлен счетчик ЭНЕРГОМЕРА СЕ101 модификации R5 — достаточно бюджетное решение. Чтобы изучить подопытного в плане доступных интерфейсов, изучим техническую документацию на данный счетчик. Я особо не испытывал иллюзий в плане наличия доступных интерфейсов, но мне повезло.
Бинго! — сказал я, найдя в документации следующие пункты:
2.3.1. Принцип действия счетчика основан на перемножении входных сигналов тока и напряжения по методу сигма-дельта модуляции с последующим преобразованием сигнала в частоту следования импульсов, пропорциональную входной мощности. Суммирование этих импульсов отсчетным устройством дает количество активной энергии. Счетчик также имеет в своем составе испытательный выход для подключения к системам автоматизированного учета потребленной электроэнергии или для поверки.
2.3.2. Конструктивно счетчик выполнен в пластмассовом корпусе. В корпусе размещена печатная плата, на которой расположена вся схема счетчика. В качестве датчика входного тока используется шунт, соединенный с контактами колодки. Зажимы для подсоединения счетчика к сети, испытательный выход закрываются пластмассовой крышкой.
2.3.3. Испытательный выход реализован на транзисторе с «открытым» коллектором, для его функционирования необходимо подать питающее напряжение постоянного тока от 5 до 24 В. Величина номинального тока через контакты испытательного выхода в состоянии «замкнуто» равна (10±1) мА, максимально допустимая не более 30 мА.
Техническая документация нам прямо говорит:
Счетчик имеет в своем составе испытательный выход для подключения к системам автоматизированного учета потребленной электроэнергии или для поверки.
Ниже приведена схема подключения счетчика
Решено! Будем использовать данный выход для интеграции электросчетчика с Home Assistant. Нам останется только преобразовать импульсный сигнал в удобную форму данных, чем и займется дешевый микроконтроллер ESP8266.
Дополнительная информация по испытательному выходу:
Импульсный выход электросчетчика реализован с гальванической развязкой с помощью оптопары, поэтому мы можем смело подключать данный выход напрямую к микроконтроллеру без дополнительной развязки. Как правило, к данному выходу имеется свободный доступ и клеммы не пломбируются энергоснабжающей компанией.
Здесь все просто. Каждый счетчик имеет параметр «частота следования импульсов, пропорциональная входной мощности», который указан на панели счетчика, в моём случае это 3200 imp/kW*h. То есть, чтобы нам узнать накопленную мощность, просто нужно будет значение инкрементного счетчика, который суммирует входящие импульсы от счетчика, поделить на 3200. Чтобы отслеживать потребление за определенный период времени нам нужно будет реализовать дополнительные интервальные счетчики.
Итак, как описывалось ранее, преобразование импульсных значений электросчетчика достаточно тривиальная задача, с которой вполне справится микроконтроллер ESP8266.
Принципиальная схема устройства:
Рендер печатной платы:
На самом деле, схема настолько простая, что монтаж можно реализовать навесным методом.
Пример навесного монтажа. Особо впечатлительным не смотреть! )
Модель корпуса, как обычно, я разрабатывал во FreeCAD.
В корпусе предусмотрен отсек для аккумулятора, который обеспечит устройство резервным питанием. Питание устройства и зарядка аккумулятора выполняется с помощью популярной платы на буде контроллера заряда TP4056. Но использование аккумулятора не обязательно, я просто перестраховался.
Модель корпуса напечатана HIPS пластиком на 3D принтере.
Прошивка устройства написана в среде Arduino IDE, большую часть прошивки занимает веб интерфейс и функция работы с MQTT протоколом. Для работы с входным сигналом используется аппаратное прерывание. Ссылку на исходный код прошивки я оставлю конце статьи, а теперь давайте рассмотрим веб интерфейс устройства:
Вход в устройство
Для входа в устройство требуется авторизация, пароль по умолчанию: admin
Главная страница устройства
На главной странице отображены данные по потреблению электроэнергии:
Потребляемая мощность (сред. 10 сек, 1 мин, 5 мин, 60 мин), Вт*ч — усредненное рассчитанное часовое потребление за период 10 сек, 1 мин, 5 мин, 60 мин. Для отслеживания динамики по потреблению эл.энергии.
Показания счетчика — накопленное значение кВт*ч
Конфигурация передачи данных по MQTT протоколу
Как вы могли заметить, для интеграции с Home Assistant используется протокол MQTT, настройки вполне понятные, не требует дополнительных описаний.
Ниже указаны топики, для примера, указано корневое имя топика «m_power»:
m_power/10s — передача значения «Потребляемая мощность (сред. 10 сек)», периодичность отправки 10 сек.
m_power/1m — передача значения «Потребляемая мощность (сред. 1 мин)», периодичность отправки 1 мин.
m_power/5m — передача значения «Потребляемая мощность (сред. 5 мин)», периодичность отправки 5 мин.
m_power/60m — передача значения «Потребляемая мощность (сред. 60 мин)», периодичность отправки 60 мин.
m_power/total — передача значения «Накопленная мощность», периодичность отправки 5 мин.
Настройка типа счетчика
Здесь необходимо ввести два параметра вашего электросчетчика:
"Количество импульсов счетчика на 1 кВт*ч" — данный параметр указан на табло счетчика
"Текущие показания счетчика" — данный параметр передается в устройство, как начальное значение накопленной мощности, далее к этому параметру будут прибавляться рассчитанные контроллером данные.
С интерфейсом можно закончить, интерфейс интуитивно понятен и разработан для обычного пользователя, надеюсь что проблем с использованием не должно возникнуть.
Чтобы интегрировать данное устройство в Home Assistant, необходимо прописать в файле конфигурации configuration.yaml следующий код:
Обратите внимание, что в примере указан корень топика с именем «m_power».
Код карточки «объекты» для вывода данных:
В результате у вас должно получиться что-то подобное:
Важное предупреждение!
Подключение нашего контроллера к импульсному выходу электросчетчика выполняется согласно схеме, что была указана выше. Ниже пример подключения к моему электросчетчику.
Подключение выполнялось свободной «витой парой», которая шла из моей квартиры, потому нет необходимости размещения контроллера в щите, где установлен электросчетчик. После подключения витой пары, крышка которая закрывает клеммник, была установлена на место.
Ниже фотография собранного устройства с подключенным счетчиком.
Видео работы контроллера при включенном электрическом водонагревателе.
Ниже демонстрация графиков данных, полученных с контроллера:
В итоге у получилось простое и дешевое устройство для интеграции «глупого» электросчетчика в системы умного дома. Я очень доволен результатом! Теперь нет необходимости переодически записывать показания со счетчика для отправки, а просмотр статистики потребления, по временным интервалам, дает возможность анализа расхода электроэнергии с последующей оптимизацией.
Дополнительная опция
Если у счетчика отсутствует испытательный выход, то в качестве источника импульсов можно использовать светодиод, подключив фототранзистор ко входу собранного контроллера.
Спасибо всем за уделенное внимание!
Исходные файлы проекта:
Автор: CyberexTech
Больше интересных фото и комментариев в оригинале материала
В начале 2023 года, во многих СМИ появилась информация, что стрелки часов Судного дня перевели на десять секунд. Сейчас они замерли на отметке 90 секунд до полуночи, и теперь часы показывают самый высокий уровень риска ядерной катастрофы за всю историю проекта. Эта информация побудила меня создать устройство для мониторинга фоновой радиации — мог бы написать я, но на самом деле, всё началось гораздо раньше и об этом расскажу далее.
Однажды вечером, в 2015 году, мне захотелось посмотреть статистику фоновой радиации в регионе где я проживаю, зашел на мой любимый проект под названием «Народный мониторинг» и начал искать близлежащие датчики, которые могли бы измерять фоновую радиацию. На моё удивление, я не обнаружил подобных датчиков. Ладно, не беда, сказал я себе, ведь я живу в регионе, где есть государственные предприятия атомной энергетики, на их сайте должна же быть публичная информация об уровне фонового излучения. Зашел на сайт, да, действительно, есть статистика по уровню фонового излучения, где на графике показана прямая линия без намека на динамику, естественно, данный результат меня не устроил и я решил собирать статистику самостоятельно, разработав свое устройство.
Немного информации о том, что собой представляет ионизирующее излучение (радиация)
На рисунке схематично изображена проникающая способность трех видов излучения
Альфа-излучение представляет собой поток альфа-частиц (ядер гелия-4). Альфа-частицы излучаются при радиоактивном распаде и могут быть легко остановлены листом бумаги. Бета-излучение — это поток электронов, возникающих при бета-распаде, для защиты от бета-частиц энергией до 1 МэВ достаточно алюминиевой пластины толщиной в несколько миллиметров. Гамма-излучение обладает гораздо большей проникающей способностью, поскольку состоит из фотонов с высокой энергией, не обладающих зарядом, для защиты эффективны тяжёлые элементы (свинец и т. д.), поглощающие фотоны Гамма-излучения в слое толщиной несколько см.
Как можно видеть, наиболее опасные типы радиационного излучения это Бетта и Гамма, если речь идет о внешнем воздействии. При попадании излучающих частиц во внутрь человека, все виды излучения представляют опасность.
Так, немного разобрались что такое радиация, надеюсь, эта информация не утомила вас. Предлагаю перейти к конструкции устройства.
Так как основным эффектом радиационного излучения является ионизация, то для детектирования излучения используется простое решение — трубка (счетчик) Гейгера-Мюллера.
Краткое описание работы трубки(счетчика) Гейгера-Мюллера:
При пролете частицы с высокой энергией сквозь трубку, образуется ионный канал, который замыкает цепь электродов, создавая импульс на загрузочном резисторе. Эти импульсы и будут сигналом регистрации фонового излучения, остается только передать эти импульсы в микроконтроллер для вычисления накопленной дозы излучения.
Чтобы работать с трубкой, необходим источник высокого напряжения на 400В. В качестве источника высокого напряжения я решил применить повышающий Step-UP преобразователь на базе ШИМ контроллера MAX1771, который хорошо себя показал в проекте часов на ламповых индикаторах. За интеллектуальную часть устройства отвечает микроконтроллер ESP8266.
Итак, ниже размещена принципиальная схема разработанного устройства.
Рендер печатной платы
Печатная плата в собранном виде
Один из вариантов корпуса устройства
Корпус спроектирован во FreeCAD, данный вариант корпуса изготовлен с учетом применения трубки СИ180Г.
Еще немного картинок готового устройства:
На фото вариант устройства с применением массива трубок СИ1-Г
Программная часть не подразумевает каких-то сложных решений, наша задача подсчитать импульсы, приходящие с трубки Гейгера-Мюллера за определенный промежуток времени и применить расчетный коэффициент трубки. В прошивке устройства будет использоваться несколько временных интервалов подсчета импульсов, 10 сек, 1 мин, 5 мин, 60 мин. Подсчет импульсов будет выполняться с помощью аппаратного прерывания.
Так как у нас, для усиления сигнала импульса трубки, используется транзистор с обратной проводимостью, то нам необходимо использовать параметр FALLING в аппаратном прерывании.
Ниже представлена функция счетчиков импульса
Функция вычисления накопленной дозы для разных временных интервалов
Ссылка на исходный код будет добавлена конце статьи.
В данной версии программного обеспечения для конфигурации устройства используется веб-интерфейс. Для первичной настройки при отсутствии подключения к сети устройство создает Wi-Fi точку доступа, при подключение к которой выполняется автоматическая переадресация на веб-страницу устройства (реализована с применением технологии Captive portal).
Интерфейс главной страницы
Доступ к конфигурации устройства выполняется только после авторизации.
Интерфейс настройки подключения по MQTT протоколу
Для передачи данных в Home Assistant, как вы уже могли догадаться, используется протокол MQTT. Ниже приведен пример интеграции, в примере использован формат данных JSON и имя топика «r_sensor/jsondata».
Формат данных JSON для данного устройства:
Ключи, которые начинаются на «CP» — это «сырые» данные, полученные от трубки, Ключи начинающиеся на «val» — это итоговое значение уровня излучения.
Для интеграции в Home Assistant в конфигурационном файле configuration.yaml, добавьте следующий код (пример):
Чтобы добавить в dashboard Home Assistant карточку с отображением данных, создайте карточку «Объекты» и в текстовом редакторе необходимо вставить следующий код:
В итоге должна получиться подобная карточка с отображением данных:
На этом мы закончили базовую интеграцию датчика в Home Assistant.
В устройстве предусмотрена функция звуковой индикации. Да-да, это тот самый звук потрескивания при регистрации частицы. Думаю, радиофилы и радиофобы будут довольны).
Так же в устройстве предусмотрена звуковая сигнализация о превышении допустимого уровня излучения, она срабатывает при достижении уровня радиации более 100 мкР/ч.
Демонстрацию можно увидеть здесь
В итоге у нас получилось интересное и компактное решение для мониторинга уровня фоновой радиации, которое запитывается от обычного USB порта.
Устройство прошло несколько итераций развития, как программного, так и аппаратного. На данный момент это проверенное временем и отлаженное устройство, которое не стыдно показать.
Первая версия устройства (2015 год):
На этом можно завершать. Всем большое спасибо за внимание!
Небольшая бонусная история, связанная с данным устройством:
В конце сентября 2017 года, внезапно устройство начало регистрировать повышенный уровень фоновой радиации, о чем оно меня предупредило. Я не мог понять, что происходит и склонялся к тому, что возникла какая-то аппаратная проблема и не принял в серьез эту ситуацию. Спустя пару минут всё нормализовалось. Диагностика не показала каких-либо проблем с устройством. В начале октября появилась информация в СМИ о выбросе изотопа Рутений-103 и все стало ясно. Облако Рутения-103 пролетело где-то рядом. Это была первая аномалия, которая была зафиксирована моим устройством.
Исходники проекта: