Banned Book Library

от автора

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

Идея состоит в том, что если вы живёте в месте, где запрещены книги, которые вы считаете важными, то вы можете теоретически загрузить цифровую копию на одну из таких лампочек. Затем вы можете пойти и установить её где-нибудь в своём сообществе. Пока лампочка включена, любой, кто находится поблизости, может получить доступ к запрещённому материалу при условии, что у него есть электронное устройство с Wi-Fi. Поскольку устройство является лампочкой, его будет трудно обнаружить, и оно, скорее всего, останется незамеченным. Киберпанковская цифровая «мёртвая почта». Эти устройства также довольно недороги, так что оставлять их по всему городу, надеюсь, не очень накладно.

Я думаю, идея размещения именно запрещённых книг пришла мне в голову после прочтения короткого рассказа Бена Брауна «Библиотека». Прошло уже некоторое время с тех пор, как я читал его, но, если я правильно помню, в рассказе есть персонажи, которые поддерживают «библиотеку», действующую как цифровой архив творческих работ, руководств по эксплуатации, 3D-моделей и т.д. Вещей, которые другие могут найти полезными или интересными, и которые не хочется потерять, если их вдруг уберут из Интернета. Это лишь часть истории, и её было весело читать. Вам стоит прочитать её!

В общем, несколько месяцев назад я наконец решил приступить к работе над этим проектом. Результат — Banned Book Library!

Аппаратное обеспечение

Я обсудил эту идею с некоторыми ребятами из местной группы DEFCON. У одного из них был опыт работы с домашней автоматизацией, и он посоветовал мне обратить внимание на Tasmota. Tasmota — это прошивка с открытым исходным кодом, которую можно установить на различные умные устройства для интеграции их в систему домашней автоматизации, например, HomeAssistant. Основная идея этой прошивки — предоставить локальное управление устройством. Многие из этих устройств полагаются на облачные сервисы, которые со временем видоменяются или иногда полностью исчезают, делая гаджеты непригодными к использованию. Tasmota позволяет освободиться от этих облачных сервисов и разместить всё локально. На самом деле, это ещё одна отличная параллель с историей «Библиотеки» Бена Брауна. Также актуален «Несанкционированный хлеб» Кори Доктороу.

Я не слышал о Tasmota, но, прочитав о ней, понял, что это хороший путь. Я примерно предполагал, что многие из этих умных лампочек используют чипы ESP32 или подобные. Отсутствие опыта работы с ними делало начало немного пугающим. Я подумал, что, возможно, будет проще модифицировать прошивку Tasmota для своих нужд, чем писать всё с нуля. В итоге я не стал модифицировать Tasmota, но это путешествие привело меня к сайту, который продаёт Wi-Fi лампочки с предустановленной Tasmota. На странице товара даже было указано, что лампочка использует ESP32C3 4MB. Там было перечислено, какие выводы GPIO используются для управления различными светодиодами, что позже оказалось полезным:

R:GPIO6

G:GPIO7

B:GPIO5

CW:GPIO3

WW:GPIO4

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

Одна вещь, которая показалась мне потенциальной проблемой, — это размер флеш-памяти. Было указано 4 МБ. Это не очень много места для размещения библиотеки книг… В эти 4 МБ должны были поместиться прошивка, веб-сайт и все книги. Не так уж много места. Я подумал, что смогу обойти это ограничение, добавив хранилище, например, считыватель microSD-карт. Но об этом позже.

Я купил две такие лампочки, чтобы поэкспериментировать. Я решил, что могу случайно сломать или «закирпичить» одну из них, так что иметь запасную было хорошей идеей.

Разборка

Лампочки прибыли через несколько дней, и я открыл коробку, чтобы посмотреть на них.

Первое, что я хотел сделать, — это вскрыть её и посмотреть, с чем я имею дело. Меня в основном интересовало, доступны ли контакты, чтобы я мог подключить считыватель microSD-карт. Чтобы снять белый пластиковый колпак сверху, я провёл лезвием по окружности лампочки между основанием и колпаком. Мне пришлось пройтись дважды, второй раз направляя нож вниз, чтобы прорезать герметик внутри. Затем я смог просто повернуть и стянуть колпачок. Повреждения были минимальными.

Под ним открылась круглая дочерняя плата со всеми светодиодами. Эта печатная плата была соединена с другой, находящейся под ней, с помощью шести контактов. Также в центре было отверстие, через которое немного торчала материнка. Это оказалась антенна ESP32. Корпус лампочки был выложен алюминием, а дочерняя плата также была сделана из него. Вероятно, они спроектировали всё так, чтобы обеспечить приличный Wi-Fi сигнал.

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

Теперь я мог очень чётко увидеть ESP32C3, а также некоторые другие вспомогательные схемы. Я не эксперт в электронике, но полагаю, что большинство компонентов внутри предназначены для преобразования переменного тока сети в более чистое постоянное напряжение 3.3 В для ESP32, а также напряжения, необходимого для питания светодиодов. Я никогда не подключал это устройство к сети, пока оно было открыто, поэтому я не измерял напряжение для светодиодов.

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

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

Я отколол часть компаунда от материнской платы, чтобы получше рассмотреть её. Это создало беспорядок.

Это было огромной проблемой и не тем, что я хотел бы сделать обязательным шагом в процессе настройки одной из таких «мёртвых почт». Я действительно хотел, чтобы этот проект был как можно более доступным, требующим минимальных инструментов и навыков работы с оборудованием. И, кроме того, у меня не было возможности правильно установить всё обратно. Даже если бы мне удалось вернуть её на место, я бы не доверял безопасности. Насколько мне известно, изменение могло стать причиной пожара.

Тем не менее, это дало мне некую платформу для разработки. Я подумал, что раз уж всё равно разобрал эту штуку, то можно припаять провода для последовательного программирования. Я не делал этого раньше, поэтому пришлось немного почитать о вопросе, чтобы разобраться. В основном мне нужно было подать питание 3.3 В на чип. Плюс по одному проводу на выводы TX и RX последовательного UART. Первый вопрос был: какие именно контакты для этого подходят?

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

Это помогло мне определить выводы VCC, GND, TX и RX. Для GND я в итоге выбрал металлический экран, так как он тоже был заземлён и его было гораздо легче паять. Я припаял провода ко всем остальным контактам. Мне пришлось удалить несколько конденсаторов, чтобы получить доступ к ним.

Чтобы запрограммировать устройство через последовательный порт, его нужно перевести в специальный режим загрузки. Обычно для этого требуется замкнуть один из контактов ESP32 на землю при подаче питания. Я не помню, как я это понял, но в итоге это оказался контакт IO9. Поэтому я припаял туда ещё один провод.

Затем я настроил свой лабораторный блок питания на 3.3 В и подключил его к чипу. Подача питания на провода VCC и GND запустила его, и я увидел, что точка доступа IoTorerro ожидает подключения для настройки устройства.

Чтобы перевести его в режим загрузки, я выключил устройство. Затем я подключил свой FTDI-адаптер к USB-порту ноутбука и к проводам GND, TX и RX на материнской плате. После этого я вручную замкнул провод IO9 на экран и включил устройство. Я заметил, что на этот раз оно потребляло всего около 0.09 ампер, что было намного меньше, чем раньше. Значит, что-то изменилось.

Я подумал, что первое, что мне стоит сделать, — это попытаться выгрузить всю прошивку. Это, надеюсь, позволило бы мне вернуть её на устройство, чтобы начать с чистого состояния. Я использовал для этого esptool:

esptool --chip esp32c3 --port /dev/ttyUSB0 --baud 114200 read-flash 0x0 0x4000000 ./tasmota_original_firmware.bin

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

Ранние эксперименты

Hello World

На раннем этапе я просматривал исходный код Tasmota, чтобы понять, смогу ли модифицировать его для создания Banned Book Library. Прошивка оказалась гораздо сложнее, чем я предполагал. Кроме того, она поддерживала множество различных архитектур и устройств. В ней также было много функций, которые мне были не нужны. И, учитывая цель моего проекта, я хотел попытаться уменьшить объём прошивки, чтобы освободить больше места для хранения книг. Поэтому я отказался от идеи модифицировать Tasmota.

Затем я обнаружил, что ESP32 можно программировать с помощью Arduino. Я использовал Arduino лет 10-15 назад, так что у меня был некоторый опыт. Я помнил, что это довольно доступный инструмент, упрощающий работу со встроенными системами. Но я никогда не использовал его для программирования ESP32.

Я настроил среду Arduino IDE на ноутбуке, сконфигурировал её для использования последовательного программатора ttyUSB0, а также указал правильный чип ESP32C3. Затем я написал простейшую программу Hello World для отправки сообщения обратно на ноутбук через последовательный порт. Это позволило бы проверить, могу ли я прошить устройство и заставить его делать что-то новое.

Я использовал встроенную функцию в Arduino IDE для загрузки кода на устройство. Она взяла на себя все сложные моменты и просто сделала это. Я проверил монитор последовательного порта и обнаружил, что всё работает! Я получал последовательный вывод от устройства. Значит, теперь можно писать свою собственную прошивку для этой штуки.

Веб-сервер

Следующее, что я хотел сделать, — это настроить открытую точку доступа Wi-Fi и веб-сервер. Кажется, я начал с этого руководства, чтобы понять, что делать, хотя модифицировал его, так как в то время меня не интересовало управление светодиодом. Позже я переключился на использование Async Web Server и использовал руководство, чтобы разобраться в деталях.

MicroSD-карта

После того как это заработало, я захотел попробовать подключить microSD-карту. Я приобрёл несколько плат-переходников от Sparkfun.

Я изучил техническую документацию ESP32C3, чтобы понять, как подключить считыватель SD-карт. В конечном итоге мне это удалось. Однако, вместо того чтобы паять к этому устройству, я решил переключиться на Adafruit ItsyBitsy ESP32, который валялся у меня без дела от предыдущего проекта. ItsyBitsy было проще использовать, так как он выводит все контакты таким образом, что я мог припаять к ним штыревые разъёмы. Это значительно упростило подключение считывателя microSD-карт для прототипирования.

Затем я следовал другому руководству, чтобы понять, как запрограммировать ESP32 для работы с устройством. В итоге у меня получилось заставить всё работать, и я даже использовал устройство для размещения файлов на веб-сервере с помощью LittleFS, однако вся идея с добавлением microSD провалилась, поэтому я не буду вдаваться в подробности.

Настоящая проблема с идеей microSD-карты заключалась в том, что припаивать провода к этому ESP32C3 в реальном устройстве было очень сложно. Не было способа сделать это, не вынув плату из корпуса, что, по сути, разрушало устройство. Я пытался использовать творческий подход.

Сначала я хотел задействовать некоторые контакты управления светодиодами. Было шесть контактов, идущих от материнской платы к дочерней. Пять из них предназначались для различных цветов светодиодов: тёплый белый, холодный белый, красный, зелёный и синий. Мне не нужен был RGB вообще. И я мог бы отказаться от одного из белых цветов (тёплого или холодного), если бы понадобилось. Однако это не сработало. Устройство спроектировано так, что материнская плата подаёт питание на дочернюю через один контакт. Другие пять контактов через транзисторы уходят на материнскую плату. ESP32 устанавливает высокий уровень на своих выводах GPIO, что активирует транзисторы и замыкает цепь для каждого цвета на землю. Это означало, что выводы GPIO в этой конфигурации могли использоваться только для вывода. Никакого ввода.

Затем у меня возникла безумная идея сделать «зажим», который можно было бы надеть сверху на ESP32 и, возможно, позволить контактам коснуться открытых выводов ESP32. Я разработал небольшую деталь для 3D-печати, которая должна была полагаться на ESP32C3 и закрепляться зажимом.

Это оказалось слишком ненадёжным. После нескольких итераций дизайна я полностью отказался от идеи.

Отступление

На этом этапе я решил посмотреть на другие лампочки. Может быть, есть другие устройства, которые лучше подходят для пайки дополнительных компонентов? Проблема была в том, что я не хотел разориться, покупая 20 разных светодиодных ламп, чтобы просто проверить, осуществима ли идея. Я начал с изучения предыдущих исследований, нашёл несколько статей с разборкой различных умных лампочек, но все они выглядели очень похоже на мою. Это показало, что не все они используют ESP32. На этом этапе я решил, что хочу придерживаться ESP32, поскольку уже потратил время на изучение его программирования.

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

Philips WiZ сначала выглядела многообещающе. Она использовала ESP32C3-mini-1, и весь чип был доступен после снятия только пластикового колпачка!

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

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

Был еще один интересный DIY-проект умной светодиодной лампы на Hackaday, который меня заинтриговал, но мне больше нравилась идея перепрофилирования готового устройства.

В конечном итоге я решил остановиться на лампе с Tasmota и попытаться работать в пределах моих 4 МБ.

Проблема хранения данных

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

$ esptool -p /dev/ttyUSB0 --baud 115200 read_flash 0x8000 0x1000 part_dump.binWarning: Deprecated: Command 'read_flash' is deprecated. Use 'read-flash' instead.esptool v5.1.0Connected to ESP32-C3 on /dev/ttyUSB0:Chip type:          ESP32-C3 (QFN32) (revision v0.4)Features:           Wi-Fi, BT 5 (LE), Single Core, 160MHz, Embedded Flash 4MB (XMC)Crystal frequency:  40MHzMAC:                0c:4e:a0:31:cb:e4Stub flasher is already running. No upload is necessary.Configuring flash size...Read 4096 bytes from 0x00008000 in 0.4 seconds (87.2 kbit/s) to 'part_dump.bin'.Hard resetting via RTS pin...

Я использовал gen_esp32part.py, чтобы создать CSV-файл с описанием разделов.

$ gen_esp32part.py part_dump.binParsing binary partition input...Verifying table...# ESP-IDF Partition Table# Name, Type, SubType, Offset, Size, Flagsnvs,data,nvs,0x9000,20K,otadata,data,ota,0xe000,8K,safeboot,app,factory,0x10000,832K,app0,app,ota_0,0xe0000,2880K,spiffs,data,spiffs,0x3b0000,320K,

Это выявило пять разделов:

  • nvs

  • otadata

  • safeboot

  • app0

  • spiffs

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

Я не уверен, для чего именно используется otadata, кроме того, что это связано с обновлениями по воздуху.

Раздел safeboot — это вторичная загрузочная прошивка, которую Tasmota использует для основной прошивки. Обычный способ работы с OTA-обновлениями — иметь два дублирующих раздела прошивки одинакового размера. Вы загружаетесь из раздела A, а затем, когда устанавливаете обновление, оно записывается в раздел B. Затем вы перезагружаетесь в раздел B. Если всё выглядит нормально, прошивка может быть затем записана в раздел A. Таким образом, если обновление прошивки в разделе B завершится неудачей, устройство сможет восстановиться, перезагрузившись в раздел A. Недостаток этого метода в том, что нужно два раздела прошивки равного размера, что занимает много места.

Tasmota использует несколько иной подход в конфигурации safeboot. Вместо двух дублирующихся образов прошивки есть основная прошивка, хранящаяся в разделе app0. Затем есть вторая, меньшая прошивка, хранящаяся в разделе safeboot. safeboot может подключаться к предварительно настроенной сети Wi-Fi и прошивать раздел app0 — и это, насколько я могу судить, всё, что она умеет. Вы даже не можете использовать прошивку safeboot для настройки Wi-Fi, это должно быть сделано через основную прошивку. Safeboot должен считывать настройки из nvs. Преимущество такого подхода в том, что основная прошивка Tasmota может быть больше и иметь больше функций, не занимая вдвое больше места для OTA-обновлений. 

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

При такой конфигурации основная прошивка занимала почти 3 МБ, а safeboot — около 1 МБ. Для хранилища оставалось всего 320 КБ. Этого может хватить на одну электронную книгу, в зависимости от длины. Не идеально.

Мне пришло в голову, что моей собственной прошивке вряд ли понадобится 2880 КБ, так как она будет намного проще, чем Tasmota. Я подумал, что, возможно, смогу изменить размер разделов в самой прошивке, чтобы уменьшить app0 и увеличить spiffs. Это дало бы больше места для веб-файлов и книг.

В конечном итоге я понял, как это сделать.

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

Таблица разделов хранится по смещению 0x8000 во флеш-памяти. Поэтому всё, что нам нужно сделать, — это перезаписать таблицу нужными нам данными. Нельзя просто изменить смещения и размеры разделов, потому что в конце данных таблицы есть контрольная сумма MD5. Поэтому нам нужно также обновить это значение, иначе устройство не загрузится. Мы не можем переместить раздел app0, пока загружены в этот раздел, иначе не сможем перезагрузиться в эту прошивку, так как она не будет совмещена с началом раздела.

Я изменил файл partition.csv так, как хотел, чтобы они выглядели, и сохранил его как partitions.csv.new:

# Name,   Type, SubType, Offset,  Size, Flagsnvs,      data, nvs,     0x9000,  0x5000,otadata,  data, ota,     0xe000,  0x2000,safeboot, app,  ota_1,   0x10000, 0xD0000,app0,     app,  ota_0,   0xE0000, 0x120000,spiffs,   data, spiffs,  0x200000,0x200000,

Это позволило бы выделить 2 МБ для файлов веб-сервера и книг в разделе SPIFFS, что казалось достаточным. Затем я использовал gen_esp32part.py для создания реального двоичного файла таблицы разделов из CSV-файла:

text

$ gen_esp32part.py partitions.csv.new partitions_new.bin

Я использовал xxd, чтобы вывести важные данные в виде массива C:

rick@nixlap ~/Projects/BannedBookLibrary/idf/library/main$ xxd -i ../partitions_new.bin |head -n17                                                                                                                                                                                                                                                                                   ✭mainunsigned char ___partitions_new_bin[] = {  0xaa, 0x50, 0x01, 0x02, 0x00, 0x90, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00,  0x6e, 0x76, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x00,  0x00, 0xe0, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x6f, 0x74, 0x61, 0x64,  0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x00, 0x11, 0x00, 0x00, 0x01, 0x00,  0x00, 0x00, 0x0d, 0x00, 0x73, 0x61, 0x66, 0x65, 0x62, 0x6f, 0x6f, 0x74,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xaa, 0x50, 0x00, 0x10, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00,  0x61, 0x70, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x82,  0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x73, 0x70, 0x69, 0x66,  0x66, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0xeb, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xda, 0xa8, 0x74,  0x2c, 0xcd, 0xc5, 0x28, 0xab, 0xd5, 0x0d, 0xf6, 0x41, 0xd3, 0xa7, 0xdd,

Затем я поместил это в файл partition.h:

unsigned char partition_table[] = {  // ... (массив байт из предыдущего шага)};unsigned int partition_table_len = 192;

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

bool edit_partition_table() {  int result = esp_flash_init(esp_flash_default_chip);  Serial.printf("esp_flash_init result: 0x%x\n", result);  uint8_t current_md5[MD5SUM_SIZE];  memset(current_md5, 0x0, MD5SUM_SIZE);  result = esp_flash_read(esp_flash_default_chip, current_md5, CONFIG_PARTITION_TABLE_OFFSET + OFFSET_TO_PART_MD5SUM, MD5SUM_SIZE);  Serial.printf("esp_flash_read result: 0x%x\n", result);  if (memcmp(partition_new_md5, current_md5, MD5SUM_SIZE) != 0) {    Serial.printf("Patching partition table...\n");    result = esp_flash_erase_region(esp_flash_default_chip, CONFIG_PARTITION_TABLE_OFFSET, 0x1000);    Serial.printf("esp_flash_erase_region result: 0x%x\n", result);    result = esp_flash_write(esp_flash_default_chip, partition_table, CONFIG_PARTITION_TABLE_OFFSET, partition_table_len);    Serial.printf("esp_flash_write result: 0x%x\n", result);    Serial.printf("Erasing NVS partition...\n");    result = esp_flash_erase_region(esp_flash_default_chip, 0x9000, 0x5000);    Serial.printf("esp_flash_erase_region result: 0x%x\n", result);    Serial.printf("Setting default boot partition\n");    const esp_partition_t * part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, "app0");    esp_ota_set_boot_partition(part);    Serial.printf("Restarting...\n");    ESP.restart();  } else {    Serial.printf("Partition table already patched\n");  }  return true;}

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

ESP-IDF

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

После множества проб, ошибок и битья головой о стену я в конце концов настроил среду ESP-IDF, чтобы посмотреть, решит ли это мою проблему. Для настройки фреймворка можно использовать команду idf.py menuconfig. Это открывает меню-конфигуратор, похожий на меню Linux. Вы можете использовать это интерактивное меню для включения или отключения функций, необходимых для вашего проекта. Это также пригодится позже, когда я буду создавать свою пользовательскую прошивку safeboot и искать способ её уменьшить.

В menuconfig есть опция SPI_FLASH_DANGEROUS_WRITE_ALLOWED. Этот параметр должен быть установлен в Allowed. Вам также нужно установить SPI_FLASH_DANGEROUS_WRITE_ABORTS в disabled. Когда вы выходите из меню, оно перенастраивает IDF с новыми настройками. Затем вы можете читать и записывать таблицу разделов!

Однако возникли новые проблемы. Работа напрямую с IDF означала, что все приятные вещи из Arduino не будут работать. Мне пришлось бы переписывать код прошивки на более низком уровне. Или нет? Я обнаружил, что существует такая вещь, как «Arduino as a Component». Когда этот компонент добавлен в IDF, вы можете использовать все преимущества Arduino, но также больше контролировать сам IDF! Лучшее из двух миров! Я следовал инструкциям на этой странице, чтобы настроить компонент.

Затем я столкнулся с ещё некоторыми проблемами, пытаясь переустановить различные библиотеки, которые использовал, такие как ElegantOTA, Async_TCP, ASyncWebServer и т.д. Мне нужно было клонировать репозитории либо в директорию components моего проекта, либо в директорию libraries компонента Arduino, в зависимости от пакета. Мне также пришлось подправить некоторые файлы CMakeLists.txt. В итоге я написал скрипт build.sh, включённый в репозиторий, который автоматизирует всё это, чтобы мне не нужно было запоминать или делать это вручную.

Со всеми этими настройками я наконец смог собрать проект с помощью idf.py build. После компиляции мне нужно было прошить его, так как у меня больше не было Arduino IDE. Я снова использовал esptool:

esptool -p /dev/ttyUSB0 write-flash 0xe0000 build/library.bin

Страница настройки

После загрузки образа моей прошивки всё начало собираться воедино. Основной образ прошивки включает некоторые важные конечные точки веб-сервера, но не сам код библиотеки. Он хранится в разделе LittleFS и должен быть записан отдельно. Для этого я включил ElegantOTA.

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

Эта страница проведёт вас через использование ElegantOTA для записи образа файловой системы, а также прошивки safeboot.

Safeboot

Я хотел быть уверен, что смогу выполнять OTA-обновления основной прошивки. Изначально я думал, что смогу использовать Tasmota safeboot для этого. Проблема в том, что Tasmota safeboot применяет конфигурацию Wi-Fi, хранящуюся в разделе nvs, для выполнения обновлений. Это заставило меня осознать, что устройство сохраняет SSID и пароль Wi-Fi в открытом виде в разделе nvs. Это нехорошо с точки зрения OPSEC! Вы бы не хотели оставлять одну из таких лампочек где-то с вашими учётными данными Wi-Fi.

Чтобы решить эту проблему, сначала моя прошивка стирает раздел nvs. Она также стирает раздел SPIFFS для надёжности. Однако без данных конфигурации Wi-Fi в NVS, Tasmota safeboot не сможет подключиться ни к одной сети Wi-Fi. Это означает, что вы не сможете присоединиться обратно к устройству для обновления основной прошивки. Я понял, что мне придётся сделать ещё один пользовательский образ прошивки для раздела safeboot, чтобы решить эту проблему.

Я нашёл этот репозиторий на GitHub, и он содержит довольно минимальный пример прошивки, настраивающий точку доступа и хостящий минимально необходимый веб-сервер для выполнения OTA-обновлений. Он не использует Arduino, поэтому он должен быть менее раздутым и, следовательно, поместиться в меньший раздел.

Я начал с этого проекта, а затем модифицировал его там и сям в соответствии с потребностями моего проекта. В итоге мне пришлось отключить некоторые другие функции в menuconfig, чтобы образ поместился в раздел safeboot, но это сработало!

Раздел safeboot не защищён паролем, но административные функции библиотеки защищены, и вы используете их для перезагрузки в safeboot. Достаточно хорошо.

Веб-приложение

Библиотека

Основное взаимодействие начинается с главной страницы. На этой странице изображён жёлтый транспортный контейнер с дверью.

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

Основные страницы библиотеки были написаны мной вручную по старинке… поиском того, что я хотел сделать, копированием, вставкой и модификацией, чтобы создать что-то своё. Или чтением о том, как сделать что-то похожее на то, что я хотел, и биться головой о стену, пока не разберусь. Я не фанат генеративного ИИ. Мне пришлось больше узнать о CSS, чтобы сделать внешний вид красивым. Мой первоначальный план состоял в том, чтобы сделать действительно простую HTML-страницу со списком файлов. Но в конце концов я решил, что хочу, чтобы она выглядела круто и была более увлекательной. Это также позволило мне изучить CSS, что, кстати, оказалось довольно интересно.

Основной сайт довольно прост.

Есть подраздел, описывающий то, на что вы смотрите, раздел с книгами, включая название, автора и причину, по которой она была оспорена или запрещена. Затем есть раздел с ссылками для внешних источников. Однако ссылки не будут работать, пока пользователь подключён, потому что точка доступа Banned Book Library не имеет подключения к Интернету.

Администрирование

Также есть административная панель по адресу /admin.

Эта страница защищена паролем. Она позволяет вам управлять цветовой температурой светодиода. Идея в том, что если вы разместите лампочку где-то в общественном месте, вы можете попытаться подобрать тот же цвет, который был там раньше, чтобы изменение было менее заметным. Это было действительно легко реализовать. Компания, продающая эти лампочки, сообщает, какие выводы GPIO используются для управления каждым цветом светодиода. Поэтому вы можете использовать AnalogWrite() для установки интенсивности каждого цвета.

// Turn on cool white lightvoid handle_cw_on() {  // Turn everything off  handle_off();  // Enable cool white  analogWrite(CW, BRIGHTNESS);  prefs.putUChar("color", COOL);}

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

В административных функциях также есть кнопка для перехода на страницу восстановления.

Восстановление

Восстановление позволяет частично восстановить таблицу разделов и загрузиться в safeboot.

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

Одна проблема с восстановлением заключается в том, что я ещё не придумал хорошего способа восстановить раздел safeboot. Это означает, что если вы перезапишете Tasmota обратно, то при нажатии «Firmware Upgrade» в интерфейсе Tasmota он перезагрузится в этот пользовательский safeboot. Он должен быть пригоден для обновления Tasmota, но не будет корректно работать с процессом обновления Tasmota.

Из-за этой особенности восстановление раздела не полностью восстанавливает таблицу разделов. Фактически, он оставляет подтип safeboot как OTA_1 вместо восстановления до Factory. Я полагаю, что разделы Factory не могут быть обновлены по OTA. Я оставил его как OTA_1 в надежде, что это, возможно, позволит каким-то образом выполнить обновление через метод, который я ещё не придумал.

Captive portal

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

Многие точки доступа используют технологию captive portal. Когда вы подключаетесь к точке доступа, ваше устройство предлагает пройти аутентификацию в сети. Оно перенаправляет вас на какую-то веб-страницу, где вы можете войти в систему. Это распространено в таких местах, как отели. Я подумал, что могу реализовать что-то подобное для Banned Book Library.

Я точно не знал, как это работает, поэтому немного изучил этот вопрос. Похоже, старый способ заключался в том, чтобы перехватывать любой HTTP-запрос, который делал клиент, и отвечать перенаправлением на портал. Сейчас это работает не очень хорошо, потому что почти все веб-сайты используют HTTPS. Контроллер Wi-Fi не сможет перехватывать и изменять трафик, если клиент не настроен доверять сертификатам, подписанным контроллером.

В наши дни, похоже, есть несколько методов. Один из них — использовать опцию DHCP для указания того, что портал используется. Другой заключается в том, что различные типы устройств отправляют определенные запросы для проверки его наличия.

Как обычно, я искал существующий пример кода для начала. Мне удалось найти код на GitHub, который реализует портал для ESP32. Он делает две вещи. Во-первых, настраивает DNS-сервер, который отвечает на каждый запрос своим собственным IP-адресом. Таким образом, независимо от того, какой URL запрашивается, пользователь будет направлен на веб-сервер ESP32, то есть Banned Book Library. Во-вторых, он перехватывает некоторые конкретные HTTP-запросы и отправляет определённые ответы с перенаправлением. Похоже, эти запросы предназначены для устройств Microsoft, Android, iOS и т.д. Я использовал часть этого кода в своем проекте, что значительно ускорило работу.

/ Captive portal things// https://github.com/LuanTechAutomation/esp-captive-portal/blob/main/src/main.cppvoid config_captive_portal() {  server.on("/connecttest.txt", [](AsyncWebServerRequest *request) { request->redirect("http://logout.net"); }); // windows 11 captive portal workaround  server.on("/wpad.dat", [](AsyncWebServerRequest *request) { request->send(404); }); // Honestly don't understand what this is but a 404 stops win 10 keep calling this repeatedly and panicking the esp32 :)  server.on("/generate_204", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // android captive portal redirect  server.on("/redirect", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // microsoft redirect  server.on("/hotspot-detect.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // apple call home  server.on("/canonical.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // firefox captive portal call home  server.on("/success.txt", [](AsyncWebServerRequest *request) { request->send(200); }); // firefox captive portal call home  server.on("/ncsi.txt", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // windows call home  // catch all  server.onNotFound([](AsyncWebServerRequest *request) {      request->redirect(localIPURL);      Serial.print("onnotfound ");      Serial.print(request->host()); // This gives some insight into whatever was being requested on the serial monitor      Serial.print(" ");      Serial.print(request->url());      Serial.print(" sent redirect to " + localIPURL + "\n");  });}

Заключительные мысли

Ограничение по размеру

Устройство ограничено 4 МБ общей памяти. Это не очень много. Несколько файлов электронных книг, которые я посмотрел, весили около 350 КБ каждый. Таким образом, лампочка может вместить несколько из них. Изначально я представлял веб-сервер с огромным количеством запрещённых книг.

Сначала это разочаровало, но теперь мне даже нравится идея, что вы ограничены в выборе книг. Это означает, что каждая «мёртвая почта» будет отражать вкус автора, который её создал. Он должен выбирать конкретные книги, которые важны для него или которые он считает важными для доступа. Мне нравится эта мысль.

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

Идеи на будущее

Управление цветом

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

Меш-сети

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

Другое

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

ссылка на оригинал статьи https://habr.com/ru/articles/1048256/