Настройка устройства M5Stack с нуля
Находит подключённую плату M5Stack, прошивает UIFlow и устанавливает приложения Claude Buddy, подсказывая физические шаги.
- Что делает
- Находит подключённую плату M5Stack, прошивает UIFlow и устанавливает приложения Claude Buddy, подсказывая физические шаги.
- Когда брать
- Когда подключил новую плату M5Stack или Cardputer и хочешь её прошить, настроить или сбросить.
- Когда не брать
- Если плата уже прошита и нужно только поправить одно приложение: для этого есть cardputer-buddy.
- Пример запроса
- Я подключил Cardputer-Adv по USB, прошей его и поставь приложения Claude Buddy.
- Нужно подключить
- терминал, Python, устройство M5Stack по USB, клон репозитория build-with-claude
Входит в плагин cwc-makers. В Cowork и Claude Code можно поставить плагин целиком.
Как включить
- Скачайте архив и распакуйте его.
- Положите папку
m5-onboardв~/.claude/skills/. - Откройте Claude Code и опишите задачу своими словами: Claude подхватит скилл по описанию.
Текст
---
name: m5-onboard
description: Полная настройка с нуля для только что подключённого устройства M5Stack на ESP32 (Cardputer, Cardputer-Adv, Core, CoreS3, Stick): определяет его по USB, прошивает UIFlow 2.0 и устанавливает набор приложений Claude Buddy на MicroPython. Используй всякий раз, когда пользователь подключает плату M5Stack или ESP32 либо хочет её прошить, настроить или сбросить, а также по фразе «m5-onboard go».
---
Настройка M5Stack с нуля
Этот скилл автоматизирует весь процесс «холодного старта» устройства M5Stack на ESP32: определить его по USB, распознать модель, прошить UIFlow 2.0 и загрузить набор приложений на MicroPython в /flash/, чтобы устройство загружалось уже в пользовательское ПО. Приложения, которые мы поставляем (Claude Buddy, Snake, Hello), общаются по BLE или USB. Процесс работает в macOS, Linux и Windows; скилл разрабатывался на M5Stack Basic v2.6 (мост CH9102, ESP32-D0WDQ6-V3, 16 МБ флеш-памяти) и обобщён на остальное семейство Core, а Cardputer-Adv (ESP32-S3, родной USB) сейчас служит целевым устройством по умолчанию.
Где лежат скрипты
Этот скилл входит в плагин cwc-makers как справочный материал, но исполняемые скрипты и набор приложений buddy/ находятся в локальном клоне https://github.com/moremas/build-with-claude (этот клон создаёт команда /maker-setup). Каждый вызов scripts/*.py ниже запускай изнутри каталога onboard/ этого клона, чтобы --apps buddy указывал на соседний набор buddy/device/.
Когда использовать
Используй, когда пользователь подключает устройство M5Stack и хочет его настроить. Дерево решений:
- Новое или неизвестное устройство → запусти
onboard.py --apps buddyцеликом (определить → распознать → прошить → установить приложения). Это путь по умолчанию. - Устройство уже прошито, пользователю нужно только установить или обновить приложения → запусти
install_apps.py --src buddy(или любой--src <path>с каталогом файлов.py). - Устройство прошито, но что-то работает не так → запусти
smoke_test.py(проверка I2C, экрана, динамика и кнопок). - Пользователь хочет узнать, что висит на шине и что устройство умеет →
smoke_test.py.
Если подключено несколько устройств, спроси, какой порт использовать, — не угадывай. Если пользователь настраивает устройство, с которым уже работал (например, «то же самое, что в прошлый раз» или «ещё один Buddy»), по умолчанию используй --apps buddy, пока он не скажет иначе.
Какой вариант предполагать
Установка, в которой живёт этот скилл, настраивает преимущественно платы Cardputer-Adv, поэтому onboard.py теперь по умолчанию использует --variant cardputer-adv. На практике это значит:
- Если пользователь ничего не говорит о модели, бери вариант по умолчанию. Почти наверняка у него в руках Cardputer-Adv.
- Если пользователь говорит «Cardputer» (без «Adv»), уточни: у этих двух моделей одинаковый корпус, но разные образы прошивки, и если прошить не тот, устройство уйдёт в бесконечную перезагрузку.
- Если пользователь называет любую другую плату («Core2», «CoreS3», «Basic», «Fire»), явно передай соответствующий
--variant: значение по умолчанию тут не подходит. - В любом случае чип — ESP32-S3, и
detect.pyне сможет отличить Cardputer от Cardputer-Adv до прошивки UIFlow (одинаковый идентификатор производителя родного USB-JTAG, до прошивки нет зондирования I2C). Поэтому это вопрос намерения пользователя, а не аппаратного отпечатка.
Рабочий процесс
Главный управляющий скрипт — scripts/onboard.py. Он запускает вспомогательные скрипты по порядку и сам обрабатывает переходы между ними (ждёт перезагрузок, запоминает MAC-адрес, сообщает о ходе работы). Вызывай его напрямую, а не собирай цепочку из вспомогательных скриптов сам, если только пользователь не просит выполнить часть шагов.
Команда настройки по умолчанию (новый Cardputer-Adv, установить набор buddy):
python3 scripts/onboard.py --apps buddy
Как запускать это из инструмента Bash в Claude Code. НЕ вызывай onboard.py как обычную команду Bash на переднем плане. Инструмент Bash собирает вывод и не передаёт его ассистенту, пока команда не завершится, а эта команда идёт 2–3 минуты. Такое молчание неотличимо от зависания, и ассистент обычно сдаётся ещё до того, как пользователь увидит подсказку про нажатие кнопок. Вместо этого всегда запускай с run_in_background: true, пиши лог через tee в файл, а затем используй инструмент Monitor (или периодический tail через Read), чтобы выводить пользователю в реальном времени заголовки этапов, сигналы «жив» (heartbeat) и подсказки. 2>&1 проблему не решает: весь ход работы уже пишется в stderr, а терминал его прекрасно показывает. Решение — в семантике потоковой передачи, а не в перенаправлении. Рабочая схема:
# Launch (background, tee log):
python3 scripts/onboard.py --apps buddy 2>&1 | tee /tmp/m5-onboard.log
# Monitor (surfaces key events without drowning in byte-progress spam):
tail -f /tmp/m5-onboard.log | grep -E --line-buffered \
"^====|heartbeat|Heads up|Enter download mode|download mode!|rebooted into UIFlow|Manual reset|DONE|ERROR|Error|Traceback|FAIL|failed|No USB|not detected|Attempt [0-9]|Device already in download|Download mode port|Post-flash port|Waiting for device"
Передача физических шагов пользователю (ОБЯЗАТЕЛЬНО)
На платах с родным USB этап прошивки не может продолжиться без ручного нажатия кнопок: программного пути нет. Когда в отслеживаемом логе появится Enter download mode (или скрипт, похоже, ждёт на этапе FLASH), ты ОБЯЗАН остановиться и своими словами попросить пользователя сделать следующее на задней стороне Cardputer, прежде чем продолжать:
- Нажми и удерживай кнопку G0
- Не отпуская G0, коротко нажми и отпусти кнопку RST
- Удерживай G0 ещё около секунды, затем отпусти
- Экран должен полностью погаснуть — это значит, что режим загрузки включён
Если устройство вместо этого загрузилось в UIFlow, а не погасло, скажи пользователю, что G0 отпустили слишком рано, и попроси повторить, удерживая дольше. Не двигайся дальше, не перезапускай скрипт и не пытайся обойти это программно, пока пользователь не подтвердит, что экран тёмный: иначе прошивка не начнётся. То же относится к любой более поздней подсказке Manual reset: передай пользователю физический шаг и дождись его.
Пользователи, которые запускают onboard.py напрямую в собственном терминале (не через Claude Code), увидят весь вывод сразу — там ничего менять не нужно.
Если --port не указан, detect.py выбирает наиболее вероятного кандидата во всех трёх ОС: ESP32-S3 с родным USB (/dev/cu.usbmodem* в macOS, /dev/ttyACM* в Linux, COMx в Windows) или мост UART CH9102/CP210x на старых платах. Последовательные порты Bluetooth отфильтровываются. Если кандидатов несколько, он спрашивает.
Известное имя набора приложений buddy указывает на каталог buddy/device/ в этом репозитории (собственное меню запуска + Hello + BLE-клиент Claude Buddy + Snake). Любое другое значение --apps считается путём в файловой системе.
Чтобы пропустить повторную прошивку и только загрузить (или обновить) приложения на уже настроенное устройство:
python3 scripts/install_apps.py --port <PORT> --src buddy
Здесь <PORT> — тот порт, который detect.py вывел при последнем полном запуске, например /dev/cu.usbmodem1101, /dev/ttyACM0 или COM3.
Этапы
- Определение (
detect.py) — перечисли последовательные порты, оставь только мосты USB-UART (CH9102 с идентификатором производителя0x1A86, Silabs CP210x0x10C4, FTDI0x0403) или родной интерфейс USB-JTAG ESP32-S3 (0x303A). Проверь через esptool, что это нужный чип. Имена портов зависят от ОС (/dev/cu.usbmodem*в macOS,/dev/ttyACM*/ttyUSB*в Linux,COMxв Windows), но pyserial скрывает эту разницу. - Распознавание модели (
detect.py) — наряду с поиском портаdetect.pyчитает сигнатуру раздела заводского теста и/или сканирует I2C, когда UIFlow уже установлен, и сверяет результат сreferences/hardware_signatures.md, чтобы предложить подходящий вариант прошивки (Basic-16MB, Core2, CoreS3, Cardputer-Adv и т. д.). Выбор варианта пользователем происходит черезonboard.py --variant; отдельного флагаdetect.py --identifyнет. - Загрузка прошивки (
fetch_firmware.py) — запроси API манифеста M5Burner и скачай нужный бинарный файл UIFlow 2.0 во временный каталог системы. Между запусками он кэшируется: кэш можно очистить когда угодно, файл просто скачается заново. - Прошивка (
flash.py) —esptool write_flash 0x0 <image>на скорости 460800 бод для мостов UART,--no-stubна 115200 бод для устройств S3 с родным USB. 921600 периодически даёт сбои на мосту CH9102 — не повышай скорость. Прошивка по родному USB может изредка выдаватьLost connection, retryingпосреди стирания; esptool восстанавливается сам. Завершающий шагwatchdog-resetпосле прошивки может завершиться ошибкой, даже если сама прошивка прошла успешно:flash.pyразбирает stdout esptool, считает этот конкретный вид сбоя некритичным, если появилосьHash of data verified, аonboard.pyпереходит кflash.native_reset()и затем, при необходимости, к подсказкам по ручному RESET. - Установка приложений (необязательно,
install_apps.py) — загрузка каждого.pyиз исходного каталога в/flash/через REPL в режиме вставки (paste mode), затем перезагрузка черезrepl_reset(DTR/RTS на родном USB ничего не делает — не используй их). Структура источника: корневые*.py→/flash/,apps/*.py→/flash/apps/(стандартное меню запуска UIFlow сканирует именно его). Если в наборе есть корневойmain.py,install_apps.pyтакже выставляет в NVSboot_option=2, чтобы не запускалось собственное меню UIFlow и процесс загрузки взял на себя нашmain.py— это критично для приложений с BLE на ESP32-S3 (см. подводные камни ниже). - Проверка работоспособности (необязательно,
smoke_test.py) — сканирование I2C, тестовая картинка на экране, сигнал динамика, чтение кнопок.
Критические подводные камни (уже учтены в скриптах — не пересматривай)
Скрипты уже обрабатывают всё это правильно, и ты не должен менять поведение, даже если пользователь просит «просто запустить esptool вручную» или что-то подобное:
- Платам ESP32-S3 с родным USB (Cardputer, Cardputer-Adv, CoreS3) для входа в режим загрузки нужна физическая «пляска» с BtnG0+BtnRST. Программного пути нет. У чипа нет моста DTR/RTS, поэтому ничто из того, что умеют esptool или pyserial, не переведёт его в загрузчик в ПЗУ: пользователь должен физическими кнопками удерживать GPIO0 в низком уровне на время импульса сброса. Именно на Cardputer-Adv обе кнопки (BtnG0 и BtnRST) находятся на задней стороне устройства — они маленькие, утоплены вровень с корпусом, и проще всего нажимать их ногтем.
onboard.py:_wait_for_download_portвыводит эту подсказку во время FLASH: *нажми и УДЕРЖИВАЙ BtnG0, коротко нажми BtnRST, сначала отпусти BtnRST, продолжай удерживать BtnG0 ещё около секунды, отпусти BtnG0, экран должен полностью погаснуть.* Если устройство снова загружается в UIFlow, BtnG0 отпустили слишком рано: подсказка повторяется и просит держать дольше. НЕ пытайся автоматизировать это черезesptool --before default_resetили DTR/RTS в pyserial: на родном USB они ничего не делают (эти выводы не подключены к EN), а их добавление лишь скрывает настоящую подсказку. - Не отключай устройство во время FLASH. Особенно при родном USB. Обрыв посреди прошивки оставляет внутреннюю флеш-память в несогласованном состоянии. Маскированное ПЗУ после этого обычно остаётся доступным (нажми одну только BtnG0 на задней стороне или проделай полный танец BtnG0+BtnRST), поэтому восстановление сводится к повторному запуску
m5-onboard go: он идемпотентен, снова войдёт в режим загрузки, перепрошьёт и заново загрузит приложения. Не паникуй и не вскрывай корпус: маскированное ПЗУ зашито в кремний и переживает повреждённую флеш-память, пока цел физический уровень USB. - **Скорость — 460800 бод на мостах UART и 115200 с
--no-stubна родном USB.** Не 921600 ни там, ни там. Мост CH9102 теряет синхронизацию наerase_flashпри 921600 (это не теория: так и происходит). Путь с повышением скорости заглушки (stub) на родном USB даёт «Lost connection» посреди прошивки; 115200 без заглушки парадоксальным образом быстрее от начала до конца, потому что никогда не падает. - **Записи в NVS должны использовать
set_str, а неset_blob** *(важно для установщикаboot_optionвinstall_apps.py).* При запуске UIFlow вызываетnvs.get_str(), а ESP-IDF помечает записи-блобы и записи-строки по-разному. Ключ, помеченный как blob, возвращаетESP_ERR_NVS_NOT_FOUNDнаget_str, и устройство уходит в бесконечную перезагрузку. Если предыдущая попытка записала blob, вызовиnvs.erase_key(name)передset_str. - Многострочным блокам в REPL нужен режим вставки. Если отправлять
try:/except:построчно, REPL будет бесконечно накапливать отступы. Нажми Ctrl-E, чтобы войти в режим вставки, отправь блок и Ctrl-D, чтобы выполнить.mpy_repl.pyоборачивает это. - Аппаратный сброс — это DTR=False, RTS=True, 100 мс, RTS=False, но только на устройствах с мостом UART. На платах ESP32-S3 с родным USB линии DTR/RTS не подключены к EN/GPIO0, так что этот импульс молча ничего не делает. Для перезагрузки после установки на таких устройствах используй
mpy_repl.repl_reset()(отправляетmachine.reset()через REPL) —install_apps.pyуже так делает. Если ты обходишьinstall_apps.pyи собираешь свою цепочку, не рассчитывай, что DTR/RTS на порту usbmodem перезагрузят устройство: файлы окажутся на диске, но продолжит работать старый код. Эта регрессия однажды нас подвела. - Бесконечный цикл отладки кучи в простое — это нормально. Пока устройство ждёт на экране сопряжения, UIFlow 2.0 выводит диагностику asyncio. Не считай это зависанием.
- **Периферии BLE на Cardputer-Adv (ESP32-S3) нужны NVS
boot_option=2+ собственныйmain.py.** Стандартныйboot_option=1в UIFlow запускает фоновую BLE-рекламу для сопряжения Flow, которая клинит контроллер NimBLE: последующие вызовыgap_advertise(adv_data=...)из пользовательского кода падают с OSError(-519) «Memory Capacity Exceeded» при любом содержимом данных, и устройство в итоге рекламирует пустые поля AD, которые iOS и десктопное приложение Claude Buddy отфильтровывают.main.pyиз набора лежит в/flash/и берёт на себя процесс загрузки (показывает простое меню по/flash/apps/), сам BLE не трогает и оставляет контроллер в чистом состоянии для того приложения, которое выберет пользователь.install_apps.pyтеперь автоматически выставляетboot_option=2, если в наборе есть корневойmain.py— не ломай это поведение.
После настройки (что пользователь видит на устройстве)
Когда m5-onboard go доходит до баннера DONE, устройство готово к самостоятельной работе:
- Питание. Сдвинь переключатель на правой грани Cardputer-Adv, чтобы включить его. Этот же переключатель его выключает. Без кабеля плата работает от встроенного LiPo-аккумулятора; USB-C его заряжает.
- Загрузка. Пробегает короткий журнал загрузки, затем автоматически появляется меню запуска. В меню перечислены все
.pyиз/flash/apps/и записи верхнего уровня/flash/*.py. - Навигация. Клавиши со стрелками (или курсорные клавиши клавиатуры в стиле трекпоинта) прокручивают меню; Enter запускает выбранное приложение; ESC возвращает в меню запуска из приложения.
- Автоподключение к WiFi мероприятия.
main.pyиз набора при каждой загрузке подключается к жёстко прописанной сети WiFi мероприятия (SSIDcardputer) и показывает результат на экране перед появлением меню запуска. Учётные данные лежат вbuddy/device/wifi_event.py; подключение выполняется по мере возможности, и меню запуска всегда продолжает работу, даже если подключиться не удалось. Если ты используешь этот набор вне мероприятия, отредактируйwifi_event.pyили удали вызов_connect_wifi_with_splash()изmain.py. - Claude Buddy по BLE. Только в первый раз: в Claude Desktop открой Help → Troubleshooting → Enable Developer Tools (один раз, сохраняется между запусками). Затем меню Developer → Hardware Buddy → Connect. BLE работает независимо от состояния WiFi: связь с Claude.app локальная.
- Возврат к UIFlow. Набор buddy ставит в
/flash/толькоmain.py(без заменыboot.py), поэтому штатныйboot.pyUIFlow никогда не затрагивается и резервной копииboot_uiflow.pyдля восстановления нет. Откат — удалить нашmain.pyиз REPL устройства:os.remove('/flash/main.py'), затемmachine.reset(). При следующей загрузке управление возьмёт штатное меню запуска UIFlow. Чтобы начать совсем с чистого листа, включая прошивку, запусти скилл заново без--apps.
Файлы
scripts/onboard.py— главный управляющий скриптscripts/detect.py— поиск порта + определение чипаscripts/fetch_firmware.py— API M5Burner + загрузкаscripts/flash.py— обёртка над esptoolscripts/install_apps.py— загружает каталог файлов.pyв/flash/через REPL в режиме вставки; перед перезаписью сохраняетboot.pyкакboot_uiflow.py; также записывает ключ NVSboot_option, если в наборе есть корневойmain.pyscripts/smoke_test.py— I2C + экран + динамик + кнопкиscripts/mpy_repl.py— общие вспомогательные функции для последовательного порта и REPL (режим вставки, аппаратный сброс, захват журнала загрузки)references/hardware_signatures.md— отпечатки чипа и I2C → модель → прошивкаreferences/uiflow2_nvs.md— справочник ключей NVS с типами и вариантами отказа
Зависимости
pyserial— включён в поставку вonboard/scripts/vendor/serial/(версия 3.5, BSD-3-Clause).esptool— зависимость pip, указана вrequirements.txt. Возможность импорта проверяется черезimportlib.util.find_spec("esptool"); запасной поиск исполняемого файла охватывает~/Library/Python/*/bin/в macOS,~/.local/bin/в Linux,%APPDATA%\Python\Python3XX\Scripts\в Windows.
onboard.py при запуске выполняет предварительную проверку: если esptool (или, в редком случае удалённого каталога vendor, pyserial) отсутствует, он перечисляет, что нужно, и спрашивает пользователя, ставить ли это сейчас. На Y (или Enter) он выполняет python -m pip install --user <missing> в текущем интерпретаторе и проверяет результат. Внутри venv флаг --user убирается, чтобы установка попала в site-packages этого venv. Неинтерактивные вызывающие (stdin по конвейеру) получают вместо вопроса подсказку по ручной установке.
Сам Python должен существовать до того, как этот скилл сможет хоть что-то сделать: интерпретатор нельзя установить изнутри самого интерпретатора. git не нужен: если git --version не срабатывает, команда /maker-setup скачивает архив GitHub через curl+tar (оба предустановлены в macOS, Linux и Windows 10+). Claude отвечает за то, чтобы определить наличие Python и установить его, если он отсутствует, *до* любого вызова scripts/*.py. Проверка — просто запуск python3 --version / python --version; если он не срабатывает, Claude первым делом ставит Python через родной менеджер пакетов хоста.
Установка Python по ОС (задача Claude, если Python отсутствует):
- Windows —
winget install -e --id Python.Python.3.13 --silent --accept-source-agreements --accept-package-agreements. Занимает около 30 секунд, без интерфейса, PATH настраивается верно. Если после этого текущая оболочка не видитpython, попроси пользователя закрыть и снова открыть терминал (Windows обновляет PATH только в новых оболочках). - macOS — Python 3 обычно уже установлен как
/usr/bin/python3в любой актуальной macOS (поставляется Apple). Если по какой-то причине его нет, основной путь —brew install python@3.13через Homebrew; если нет и самого Homebrew, предложи поставить его через/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"(но только если пользователь подтвердит: Homebrew — более серьёзный шаг, чем winget). - Linux — используй менеджер пакетов дистрибутива. Debian/Ubuntu:
sudo apt-get update && sudo apt-get install -y python3 python3-pip. Fedora:sudo dnf install -y python3 python3-pip. Arch:sudo pacman -S --noconfirm python python-pip. Может понадобиться sudo; если нужно, покажи пользователю запрос пароля.
pyserial — входит в поставку скилла:
Закреплённая версия pyserial 3.5 лежит в scripts/vendor/ (BSD-3-Clause, совместима с Apache). Каждый скрипт, импортирующий serial, перед первым сторонним импортом вызывает vendor_path.ensure_on_syspath(), который добавляет scripts/vendor/ в начало sys.path, поэтому встроенная копия подхватывается независимо от того, что установлено у пользователя в системе. Итог: перечисление портов и ввод-вывод REPL работают на свежем клоне без шага pip. Около 500 КБ, чистый Python, одно и то же дерево для macOS / Linux / Windows.
esptool — зависимость pip, ставится автоматически при первом запуске:
esptool распространяется по GPLv2+ и намеренно не включён в поставку: чтобы репозиторий оставался чисто Apache-2.0, GPL-часть живёт в окружении пользователя под управлением pip, а не в дереве репозитория. Предварительная проверка скилла ищет импортируемый esptool и, если его нет, предлагает установить (python -m pip install --user esptool; --user убирается внутри venv, чтобы пакет попал в site-packages). Для вызовов подпроцессов мы используем [sys.executable, "-m", "esptool", ...]; подпроцесс наследует пользовательский site, поэтому установленный через pip модуль импортируется без проблем. requirements.txt описывает это для явной настройки; запрос — путь по умолчанию для тех, кто впервые пришёл на мероприятие и ещё не запускал pip.
Неинтерактивные вызывающие (stdin по конвейеру, CI) пропускают вопрос и получают вместо него подсказку python -m pip install --user esptool.
**Запасной вариант, если кто-то удалил scripts/vendor/:**
Тот же путь предварительной проверки также заново устанавливает pyserial через pip, если встроенной копии нет. Это покрывает случай, когда кто-то скачал архив только с исходниками без vendor или вручную подрезал репозиторий, чтобы сэкономить место.
USB-драйвер — только для Windows и только для старых плат:
Драйвер USB-UART CH9102 в Windows по-прежнему приходится ставить вручную: у WCH нет манифеста winget. Он нужен только для плат с мостом UART (Basic, Fire, Core2, StickC). Платы ESP32-S3 с родным USB (Cardputer, Cardputer-Adv, CoreS3) определяются как составные устройства USB-CDC со встроенными драйверами Windows и дополнительной установки не требуют.
Заметки по платформам
Скилл работает в macOS, Linux и Windows. Неочевидные моменты:
- Названия портов. pyserial скрывает поиск, но то, что видит пользователь, в каждой ОС выглядит по-своему. Передавай ту форму, которую сообщил
detect.py: - macOS:
/dev/cu.usbmodem1101(родной USB) или/dev/cu.usbserial-XXXX(CH9102) - Linux:
/dev/ttyACM0(родной USB) или/dev/ttyUSB0(мост UART) - Windows:
COM3,COM4и т. д. (если не уверен, открой «Диспетчер устройств» → «Порты») - Права в Linux — прочти это, прежде чем винить железо. В большинстве дистрибутивов доступ к
/dev/ttyUSB*//dev/ttyACM*без sudo требует членства в группе (dialoutв Debian/Ubuntu/Arch,uucpв Fedora). Симптом:detect.pyнаходит порт, но этап прошивки падает сPermission deniedилиCould not open port. Исправляется один раз, надолго: ``bash sudo usermod -aG dialout $USER # log out / log back in — group change only takes effect for new sessions`sudo python3 scripts/onboard.py ...` годится как разовый вариант, но добавить пользователя в группу строго лучше: после этого открытие порта в pyserial в пользовательском режиме проходит без проблем. - Подводные камни PATH в Windows.
pip install --user esptoolкладёт исполняемый файл в%APPDATA%\Python\Python3XX\Scripts\. Если этого каталога нет в PATH,pipвыводит предупреждение, а больше ничего установку не видит.detect.pyищет там напрямую как запасной вариант, поэтому скилл работает даже без исправленного PATH. Но если ты вызываешь esptool вне скилла (или получаешь ошибки «esptool not found» от других инструментов), сделай одно из двух: - Заново запусти установщик Python и отметь «Add Python to PATH» (в установщике это включено по умолчанию), ИЛИ
- Добавь
%APPDATA%\Python\Python3XX\Scriptsв PATH через «Свойства системы» → «Переменные среды», ИЛИ - Используй
python -m esptool ..., что работает всегда независимо от PATH. - Python из Windows Store. На новых компьютерах с Windows 11 Python может быть предустановлен через Microsoft Store. Он работает, но ведёт себя странно с PATH (живёт в
%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.*\).detect.pyпроверяет и это расположение. Если есть выбор, версия изwinget install Python.Python.3.13предсказуемее. - Определение пути к набору приложений. Сокращение
--src buddyвinstall_apps.pyразрешается в таком порядке: $M5_BUDDY_DIR, если задана — явное переопределение, всегда побеждает. Полезно, когда нужно указать форк или изменённый набор, которого нет в этом клоне.- Каталог
buddy/device/внутри этого репозитория, который находится черезos.path.realpath(__file__)подъёмом отinstall_apps.py. Работает для любого расположения клона, включая установки скилла через символические ссылки в~/.claude/skills/m5-onboard/. ~/Downloads/m5stack/buddy/device.~/Desktop/m5stack/buddy/device.
В большинстве установок срабатывает пункт 2. Задавай M5_BUDDY_DIR только в необычном случае, когда нужно указать набор вне этого клона: export M5_BUDDY_DIR=/path/to/buddy/device (Unix) или $env:M5_BUDDY_DIR="C:\path\to\buddy\device" (PowerShell).
- Кэш прошивки. Скачанная прошивка сохраняется в
~/.cache/m5-onboard/(или$XDG_CACHE_HOME/m5-onboard/); каталог создаётся с правами 0700, если его нет. Файлы кэша сверяются по MD5 при записи и повторно при использовании. Очищать кэш безопасно: следующий запуск скачает всё заново.
Перевод: iiuniversitet. Оригинал: https://github.com/anthropics/claude-plugins-official/tree/main/plugins/cwc-makers/skills/m5-onboard, лицензия Apache-2.0. Изменения: перевод на русский язык.
Оригинал на английском
---
name: m5-onboard
description: End-to-end onboarding for a freshly-plugged-in M5Stack ESP32 device (Cardputer, Cardputer-Adv, Core, CoreS3, Stick) — detect on USB, flash UIFlow 2.0 firmware, and install the Claude Buddy MicroPython app bundle. Use whenever the user plugs in or wants to flash/provision/reset an M5Stack or ESP32 board, or says "m5-onboard go".
---
# M5Stack Onboarding
This skill automates the full cold-start workflow for an M5Stack ESP32 device: detect on USB, identify model, flash UIFlow 2.0, and push a MicroPython app bundle onto `/flash/` so the device boots into user software. The apps we ship (Claude Buddy, Snake, Hello) talk over BLE or USB. The workflow runs on macOS, Linux, and Windows; the skill was developed against an M5Stack Basic v2.6 (CH9102 bridge, ESP32-D0WDQ6-V3, 16 MB flash) and generalized to cover the rest of the Core family, with the Cardputer-Adv (ESP32-S3, native USB) as the current default target.
## Where the scripts live
This skill ships as part of the `cwc-makers` plugin for reference, but the executable scripts and the `buddy/` app bundle live in a local clone of https://github.com/moremas/build-with-claude (the `/maker-setup` command creates this clone). Run every `scripts/*.py` invocation below from inside that clone's `onboard/` directory so `--apps buddy` resolves to the sibling `buddy/device/` payload.
## When to use
Use this when a user plugs in an M5Stack device and wants it provisioned. The decision tree:
- **Fresh/unknown device** → run `onboard.py --apps buddy` end-to-end (detect → identify → flash → install apps). This is the default path.
- **Already-flashed device, user just wants apps installed/refreshed** → run `install_apps.py --src buddy` (or any `--src <path>` to a directory of `.py` files).
- **Flashed device, something feels broken** → run `smoke_test.py` (I2C + LCD + speaker + button check).
- **User wants to know what's on the bus / what the device can do** → `smoke_test.py`.
If multiple devices are plugged in, ask which port to target — don't guess. If the user is provisioning a device they previously worked with (e.g. "same thing as last time" or "another Buddy"), default to `--apps buddy` unless they say otherwise.
### Which variant to assume
The rig this skill lives on provisions **Cardputer-Adv** boards overwhelmingly, so `onboard.py` now defaults to `--variant cardputer-adv`. In practice that means:
- If the user says nothing about the model, go with the default. They're almost certainly holding a Cardputer-Adv.
- If the user says "Cardputer" (no "Adv"), ask — the two models share a form factor but take different firmware images, and flashing the wrong one boot-loops the device.
- If the user names any other board ("Core2", "CoreS3", "Basic", "Fire"), pass the matching `--variant` explicitly — the default won't apply.
- The chip is ESP32-S3 either way, and `detect.py` won't be able to tell Cardputer from Cardputer-Adv before UIFlow is flashed (same native USB-JTAG VID, no pre-flash I2C probe). So this is a user-intent question, not a hardware-fingerprint one.
## The workflow
The main orchestrator is `scripts/onboard.py`. It drives the sub-scripts in order and handles the handoffs between them (waiting for reboots, capturing MAC, reporting progress). Prefer calling it directly over stitching the sub-scripts yourself unless the user asks for a partial run.
The default provisioning command (fresh Cardputer-Adv, install the buddy bundle):
```
python3 scripts/onboard.py --apps buddy
```
**How to invoke this from Claude Code's Bash tool.** Do NOT call `onboard.py` as a foreground Bash command. The Bash tool captures output and does not stream it back to the assistant until the command exits — and this command runs 2–3 minutes. That silence looks identical to a hang, and the assistant will usually give up before the button-dance prompt ever reaches the user. Instead, always run with `run_in_background: true`, `tee` to a log file, and then use the Monitor tool (or periodic `tail` via Read) to surface stage banners, heartbeats, and prompts to the user in real time. `2>&1` is not the fix — all progress already writes to stderr, which a terminal shows fine. The fix is streaming semantics, not redirection. The pattern that works:
```
# Launch (background, tee log):
python3 scripts/onboard.py --apps buddy 2>&1 | tee /tmp/m5-onboard.log
# Monitor (surfaces key events without drowning in byte-progress spam):
tail -f /tmp/m5-onboard.log | grep -E --line-buffered \
"^====|heartbeat|Heads up|Enter download mode|download mode!|rebooted into UIFlow|Manual reset|DONE|ERROR|Error|Traceback|FAIL|failed|No USB|not detected|Attempt [0-9]|Device already in download|Download mode port|Post-flash port|Waiting for device"
```
### Relaying physical steps to the user (REQUIRED)
The flash stage **cannot proceed without a manual button press** on native-USB boards — there is no software path. When the monitored log shows `Enter download mode` (or the script appears to wait at the FLASH stage), you MUST stop and tell the user to do the following on the **back of the Cardputer**, in your own words, before continuing:
1. Press and **hold** the **G0** button
2. While still holding G0, briefly press and release the **RST** button
3. Keep holding G0 for about one more second, then release it
4. The screen should go fully dark — that means download mode is active
If the device reboots into UIFlow instead of going dark, tell the user G0 was released too early and to try again holding it longer. Do not move on, retry the script, or attempt a software workaround until the user confirms the screen is dark — the flash will not start otherwise. The same applies to any later `Manual reset` prompt: relay the physical step and wait for the user.
Users running `onboard.py` directly in their own terminal (not via Claude Code) will see all output live — no changes needed there.
If `--port` is omitted, `detect.py` picks the most likely candidate across all three OSes: native-USB ESP32-S3 (`/dev/cu.usbmodem*` on macOS, `/dev/ttyACM*` on Linux, `COMx` on Windows), or a CH9102/CP210x UART bridge on older boards. Bluetooth-serial ports are filtered out. If multiple candidates are present, it asks.
The known apps name `buddy` resolves to the `buddy/device/` directory in this repo (custom launcher + Hello + Claude Buddy BLE client + Snake). Any other `--apps` value is treated as a filesystem path.
To skip re-flashing and just push (or refresh) the apps onto an already-provisioned device:
```
python3 scripts/install_apps.py --port <PORT> --src buddy
```
Where `<PORT>` is whatever `detect.py` printed on the last full run — for example `/dev/cu.usbmodem1101`, `/dev/ttyACM0`, or `COM3`.
### Stages
1. **Detect** (`detect.py`) — enumerate serial ports, filter to USB-UART bridges (CH9102 vendor `0x1A86`, Silabs CP210x `0x10C4`, FTDI `0x0403`) or the ESP32-S3 native USB-JTAG interface (`0x303A`). Probe with esptool to confirm the chip. Port names differ per OS (`/dev/cu.usbmodem*` on macOS, `/dev/ttyACM*`/`ttyUSB*` on Linux, `COMx` on Windows) but pyserial abstracts that.
2. **Identify** (`detect.py`) — alongside port discovery, `detect.py` reads the factory-test partition signature and/or scans I2C once UIFlow is on, and cross-references `references/hardware_signatures.md` to suggest the right firmware variant (Basic-16MB, Core2, CoreS3, Cardputer-Adv, etc.). User-facing variant choice happens via `onboard.py --variant`; there is no separate `detect.py --identify` flag.
3. **Fetch firmware** (`fetch_firmware.py`) — query the M5Burner manifest API and download the appropriate UIFlow 2.0 binary into the system temp dir. Cached between runs — safe to clear the cache anytime, it just re-downloads.
4. **Flash** (`flash.py`) — `esptool write_flash 0x0 <image>` at **460800 baud** for UART bridges, `--no-stub` at 115200 baud for native-USB S3 devices. 921600 fails intermittently on the CH9102 bridge — do not increase it. Native-USB flash can intermittently throw `Lost connection, retrying` mid-erase; esptool recovers. The post-flash `watchdog-reset` teardown step can fail even when the flash itself succeeded — `flash.py` parses esptool's stdout, treats that specific failure pattern as non-fatal when `Hash of data verified` appeared, and `onboard.py` falls back to `flash.native_reset()` and then manual-RESET coaching if needed.
5. **Install apps** (optional, `install_apps.py`) — paste-mode REPL upload of every `.py` from a source directory into `/flash/`, then reboot via `repl_reset` (DTR/RTS is a no-op on native USB — don't reach for it). Source layout: root `*.py` → `/flash/`, `apps/*.py` → `/flash/apps/` (UIFlow's stock launcher scans that). When the bundle ships a root `main.py`, `install_apps.py` also sets NVS `boot_option=2` so UIFlow's own launcher doesn't run and our `main.py` takes over the boot flow — critical for BLE-using apps on ESP32-S3 (see gotchas below).
6. **Smoke test** (optional, `smoke_test.py`) — I2C scan, LCD test pattern, speaker beep, button read.
## Critical gotchas (baked into the scripts — do not second-guess)
These are things the scripts already handle correctly but which you should not override if the user asks you to "just run esptool manually" or similar:
- **Native-USB ESP32-S3 boards (Cardputer, Cardputer-Adv, CoreS3) require a physical BtnG0+BtnRST dance to enter download mode.** There is no software path. The chip has no DTR/RTS bridge, so nothing esptool or pyserial can do will put it into the ROM bootloader — the user has to hold GPIO0 low across a reset pulse with the hardware buttons. On Cardputer-Adv specifically both buttons (BtnG0 and BtnRST) are on the **back of the device** — small, flush-mounted, often easiest to press with a fingernail. `onboard.py:_wait_for_download_port` prompts for this at runtime during FLASH: *press and HOLD BtnG0, briefly press BtnRST, release BtnRST first, keep holding BtnG0 for ~1 more second, release BtnG0, screen should be fully dark.* If the device reboots back into UIFlow instead, BtnG0 was released too early — the coaching retries and tells the user to hold it longer. Do NOT try to automate this with `esptool --before default_reset` or pyserial's DTR/RTS; both are no-ops on native USB (the pins aren't wired to EN), and adding them just hides the real prompt.
- **Do not unplug the device during FLASH.** Especially on native USB. A mid-flash disconnect leaves the internal flash in an inconsistent state. Mask ROM is usually reachable afterwards (press BtnG0 alone on the back, or do the full BtnG0+BtnRST dance), so the recovery is just to re-run `m5-onboard go` — it's idempotent and will re-enter download mode, re-flash, re-push apps. Don't panic and don't start opening the case; the mask ROM is in silicon and survives a corrupted flash as long as the USB PHY is intact.
- **Baud rate is 460800 on UART bridges, 115200 with `--no-stub` on native USB.** Not 921600 on either. The CH9102 bridge loses sync on `erase_flash` at 921600 (not theoretical — it fails). Native USB's stub-baud-bump path produces "Lost connection" mid-flash; 115200 no-stub is counterintuitively faster end-to-end because it never fails.
- **NVS writes must use `set_str`, not `set_blob`** *(relevant to `install_apps.py`'s `boot_option` setter).* UIFlow's startup calls `nvs.get_str()` and ESP-IDF tags blob and string entries separately. A blob-tagged key returns `ESP_ERR_NVS_NOT_FOUND` to `get_str`, and the device boot-loops. If a prior attempt wrote a blob, call `nvs.erase_key(name)` before `set_str`.
- **REPL multi-line blocks need paste mode.** Sending `try:`/`except:` line-by-line makes the REPL accumulate indentation forever. Use Ctrl-E to enter paste mode, send the block, Ctrl-D to execute. `mpy_repl.py` wraps this.
- **Hard reset is DTR=False, RTS=True, 100ms, RTS=False — but only on UART-bridge devices.** On native-USB ESP32-S3 boards the DTR/RTS lines aren't wired to EN/GPIO0, so that pulse is a silent no-op. Use `mpy_repl.repl_reset()` (sends `machine.reset()` through the REPL) for post-install reboots on those devices — `install_apps.py` already does this. If you bypass `install_apps.py` and stitch your own flow, don't reach for DTR/RTS on a usbmodem port and expect a reboot; files will be on disk but the old code will still be running. That regression bit us once.
- **The idle heap-debug loop is normal.** UIFlow 2.0 prints asyncio diagnostics while waiting at the pairing screen. Don't interpret it as a hang.
- **Cardputer-Adv (ESP32-S3) BLE peripherals require NVS `boot_option=2` + a custom `main.py`.** UIFlow's default `boot_option=1` starts a background Flow-pairing BLE advertise that wedges the NimBLE controller — subsequent `gap_advertise(adv_data=...)` calls from user code hit OSError(-519) "Memory Capacity Exceeded" regardless of payload shape, and the device ends up advertising with empty AD fields that iOS and the desktop Claude Buddy app filter out. The bundle's `main.py` lives at `/flash/` and takes over the boot flow (showing a simple menu over `/flash/apps/`), never touches BLE itself, and leaves the controller pristine for whichever app the user picks. `install_apps.py` now sets `boot_option=2` automatically when the bundle ships a root `main.py` — don't regress that behavior.
## After provisioning (what the user sees on the device)
Once `m5-onboard go` finishes at the `DONE` banner, the device is ready to use on its own:
- **Power.** Slide the switch on the right edge of the Cardputer-Adv to turn it on. Same switch turns it off. The board runs off its internal LiPo when unplugged; USB-C charges it.
- **Boot.** A short boot log scrolls, then the launcher menu appears automatically. The menu lists every `.py` in `/flash/apps/` plus the top-level `/flash/*.py` entries.
- **Navigation.** Arrow keys (or the keyboard's trackpoint-style cursor keys) scroll the menu; Enter launches the highlighted app; ESC returns to the launcher from inside an app.
- **Event WiFi auto-connect.** The bundle's `main.py` connects to a hard-coded event WiFi (SSID `cardputer`) on every boot and shows the result on the LCD before the launcher menu appears. Credentials live in `buddy/device/wifi_event.py`; the connect is best-effort and the launcher always continues even if the connect fails. If you're using this bundle outside the event, edit `wifi_event.py` or remove the `_connect_wifi_with_splash()` call from `main.py`.
- **Claude Buddy over BLE.** First time only: in Claude Desktop, **Help → Troubleshooting → Enable Developer Tools** (one-time, persists across launches). Then **Developer menu → Hardware Buddy → Connect**. BLE works regardless of the WiFi state — the link to Claude.app is local.
- **Getting back to UIFlow.** The buddy bundle ships only a `main.py` at `/flash/` (no replacement `boot.py`), so the stock UIFlow `boot.py` is never touched and there's no `boot_uiflow.py` backup to restore. Revert by removing our `main.py` from the device REPL: `os.remove('/flash/main.py')` followed by `machine.reset()`. UIFlow's stock launcher takes over on the next boot. To start completely fresh including the firmware, re-run the skill without `--apps`.
## Files
- `scripts/onboard.py` — main orchestrator
- `scripts/detect.py` — port discovery + chip ID
- `scripts/fetch_firmware.py` — M5Burner API + download
- `scripts/flash.py` — esptool wrapper
- `scripts/install_apps.py` — push a directory of `.py` files into `/flash/` via paste-mode REPL; backs up `boot.py` as `boot_uiflow.py` before overwriting; also writes the `boot_option` NVS key when the bundle ships a root `main.py`
- `scripts/smoke_test.py` — I2C + LCD + speaker + buttons
- `scripts/mpy_repl.py` — shared serial/REPL helpers (paste mode, hard reset, boot-log capture)
- `references/hardware_signatures.md` — chip + I2C fingerprints → model → firmware
- `references/uiflow2_nvs.md` — NVS key reference with types and failure modes
## Dependencies
- `pyserial` — vendored at `onboard/scripts/vendor/serial/` (pinned 3.5, BSD-3-Clause).
- `esptool` — pip dependency, declared in `requirements.txt`. Importable check happens via `importlib.util.find_spec("esptool")`; binary backstop search covers `~/Library/Python/*/bin/` on macOS, `~/.local/bin/` on Linux, `%APPDATA%\Python\Python3XX\Scripts\` on Windows.
`onboard.py` runs a preflight check at startup: if `esptool` (or, in the rare prune-vendor case, `pyserial`) is missing, it lists what's needed and asks the user whether to install now. On `Y` (or Enter) it runs `python -m pip install --user <missing>` in the current interpreter, then verifies. Inside a venv the `--user` flag is dropped so the install lands in the venv's site-packages. Non-interactive callers (piped stdin) get a manual-install hint instead of a prompt.
Python itself has to exist before this skill can do anything — you can't bootstrap an interpreter from inside one. `git` is **not** required — the `/maker-setup` command falls back to downloading the GitHub tarball with `curl`+`tar` (both pre-installed on macOS, Linux, and Windows 10+) when `git --version` fails. Claude's responsible for detecting Python and installing it if missing *before* running any `scripts/*.py` invocation. Detection is just running `python3 --version` / `python --version` — if it fails, Claude fetches Python with the host's native package manager before anything else.
**Per-OS Python bootstrap (Claude's responsibility if missing):**
- **Windows** — `winget install -e --id Python.Python.3.13 --silent --accept-source-agreements --accept-package-agreements`. Takes ~30 seconds, no UI, gets PATH right. If the current shell can't see `python` afterwards, tell the user to close and reopen the terminal (Windows updates PATH only on new shells).
- **macOS** — Python 3 is usually pre-installed as `/usr/bin/python3` on any current macOS (shipped by Apple). If for some reason it isn't, `brew install python@3.13` via Homebrew is the go-to; if Homebrew itself is missing, offer to install it via `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` (but only if the user confirms — Homebrew is a larger commitment than winget).
- **Linux** — use the distro package manager. Debian/Ubuntu: `sudo apt-get update && sudo apt-get install -y python3 python3-pip`. Fedora: `sudo dnf install -y python3 python3-pip`. Arch: `sudo pacman -S --noconfirm python python-pip`. You may need to sudo and should surface the password prompt to the user if needed.
**pyserial — bundled with the skill:**
A pinned `pyserial 3.5` ships under `scripts/vendor/` (BSD-3-Clause, Apache-compatible). Every script that imports `serial` calls `vendor_path.ensure_on_syspath()` before the first third-party import, which prepends `scripts/vendor/` to `sys.path`, so the vendored copy resolves regardless of whatever the user has system-wide. Net effect: port enumeration and REPL I/O work on a fresh clone with zero pip step. ~500 KB, pure-Python, same tree on macOS / Linux / Windows.
**esptool — pip dependency, auto-installed on first run:**
`esptool` is GPLv2+ and is intentionally **not** vendored — keeping the repository cleanly Apache-2.0 means the GPL bits live in the user's pip-managed environment, not in the tree. The skill's preflight checks for an importable `esptool` and, if missing, prompts to install it (`python -m pip install --user esptool` — `--user` dropped inside a venv so it lands in site-packages). For subprocess calls we use `[sys.executable, "-m", "esptool", ...]`; the subprocess inherits user-site so the pip-installed module imports cleanly. `requirements.txt` declares this for explicit setup; the prompt path is the default for first-time attendees who haven't run pip yet.
Non-interactive callers (piped stdin, CI) skip the prompt and get a `python -m pip install --user esptool` hint instead.
**Fallback if someone prunes `scripts/vendor/`:**
The same preflight path also re-installs pyserial via pip if the vendor copy is gone. This handles the case where someone downloaded a source-only zip that excluded vendor, or manually trimmed the repo to save space.
**USB driver — Windows-specific, only for older boards:**
The CH9102 USB-UART driver is still a manual install on Windows — WCH doesn't publish a winget manifest. Only needed for UART-bridge boards (Basic, Fire, Core2, StickC). Native-USB ESP32-S3 boards (Cardputer, Cardputer-Adv, CoreS3) enumerate as composite USB-CDC devices using Windows' in-box drivers and need no extra install.
## Platform notes
The skill runs on macOS, Linux, and Windows. Non-obvious bits:
- **Port naming.** pyserial abstracts the lookup but what the user sees looks different per OS. Pass whichever form `detect.py` reports:
- macOS: `/dev/cu.usbmodem1101` (native USB) or `/dev/cu.usbserial-XXXX` (CH9102)
- Linux: `/dev/ttyACM0` (native USB) or `/dev/ttyUSB0` (UART bridge)
- Windows: `COM3`, `COM4`, etc. (Device Manager → Ports if unsure)
- **Linux permissions — read this before blaming hardware.** On most distros, accessing `/dev/ttyUSB*` / `/dev/ttyACM*` without sudo requires group membership (`dialout` on Debian/Ubuntu/Arch, `uucp` on Fedora). Symptom: `detect.py` finds the port, but the flash step fails with `Permission denied` or `Could not open port`. Fix once, long-term:
```bash
sudo usermod -aG dialout $USER
# log out / log back in — group change only takes effect for new sessions
```
`sudo python3 scripts/onboard.py ...` works as a one-off but adding the group membership is strictly better because pyserial's port-open in user mode succeeds cleanly from then on.
- **Windows PATH gotchas.** Python's `pip install --user esptool` lands the executable in `%APPDATA%\Python\Python3XX\Scripts\`. If that directory isn't on PATH, `pip` prints a warning and nothing else picks up the install. `detect.py` looks there directly as a backstop, so the skill still works even without PATH fixed. But if you're invoking esptool outside the skill (or hitting "esptool not found" errors from other tools), either:
- Re-run the Python installer and tick "Add Python to PATH" (the install's default), OR
- Add `%APPDATA%\Python\Python3XX\Scripts` to PATH via System Properties → Environment Variables, OR
- Use `python -m esptool ...` which always works regardless of PATH.
- **Windows Store Python.** Newer Windows 11 machines may have Python pre-installed via Microsoft Store. It works but has quirky PATH behavior (lives under `%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.*\`). `detect.py` checks that location too. If you have the choice, the `winget install Python.Python.3.13` version is more predictable.
- **Bundle path resolution.** `install_apps.py`'s `--src buddy` shorthand resolves in this order:
1. `$M5_BUDDY_DIR` if set — explicit override, always wins. Useful when you want to point at a fork or a customized bundle that isn't in this clone.
2. The `buddy/device/` directory inside this repo, found via `os.path.realpath(__file__)` walking up from `install_apps.py`. Works for any clone location, including symlinked skill installs at `~/.claude/skills/m5-onboard/`.
3. `~/Downloads/m5stack/buddy/device`.
4. `~/Desktop/m5stack/buddy/device`.
Most installs hit (2). Set `M5_BUDDY_DIR` only for the unusual case of pointing at a bundle outside this clone: `export M5_BUDDY_DIR=/path/to/buddy/device` (Unix) or `$env:M5_BUDDY_DIR="C:\path\to\buddy\device"` (PowerShell).
- **Firmware cache.** Downloaded firmware lands at `~/.cache/m5-onboard/` (or `$XDG_CACHE_HOME/m5-onboard/`), created at mode 0700 if missing. Cache files are MD5-verified at write time and re-verified on hit. Clearing the cache is safe; the next run re-downloads.
Источник: anthropics/claude-plugins-official / cwc-makers / m5-onboard ↗. Ссылка проверена 2026-10-10.