From f16460682878a15ca38854006772ea94d97801ec Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Thu, 10 Sep 2026 12:48:09 +0300 Subject: [PATCH] Add bounded X4 Bluetooth wake and record Android recovery proof --- .../07_INSTA360_X4_INSTALLATION_LEDGER.md | 215 +++++++++++++++++ .../09_INSTA360_X4_IMPLEMENTATION_STATUS.md | 47 +++- docs/node/10_INSTA360_X4_NEXT_STEPS.md | 28 +++ .../11_INSTA360_X4_AUTONOMY_AND_USB_POWER.md | 13 +- docs/node/13_INSTA360_X4_BLE_DIAGNOSTIC.md | 53 ++++- docs/node/14_INSTA360_X4_BLUETOOTH_WAKE.md | 79 +++++++ .../insta360-x4/packaging/ble_diagnostic.py | 1 + plugins/insta360-x4/packaging/ble_options.py | 12 +- plugins/insta360-x4/packaging/ble_wake.py | 216 ++++++++++++++++++ .../packaging/build_ble_diagnostic.py | 75 +++--- plugins/insta360-x4/packaging/build_deb.py | 3 +- .../packaging/check_package_entry.py | 2 +- plugins/insta360-x4/tests/test_ble_options.py | 24 ++ plugins/insta360-x4/tests/test_ble_wake.py | 64 ++++++ 14 files changed, 789 insertions(+), 43 deletions(-) create mode 100644 docs/node/14_INSTA360_X4_BLUETOOTH_WAKE.md create mode 100644 plugins/insta360-x4/packaging/ble_wake.py create mode 100644 plugins/insta360-x4/tests/test_ble_wake.py diff --git a/docs/node/07_INSTA360_X4_INSTALLATION_LEDGER.md b/docs/node/07_INSTA360_X4_INSTALLATION_LEDGER.md index 6e0ff9a..788191e 100644 --- a/docs/node/07_INSTA360_X4_INSTALLATION_LEDGER.md +++ b/docs/node/07_INSTA360_X4_INSTALLATION_LEDGER.md @@ -1044,3 +1044,218 @@ MISSIONCOR-77 синхронизирован через официальный O прежние блоки/checker, добавлены BLE01–03/B12/POWER03 и исправлен устаревший текст о невозможности SDK reopen. Предыдущие transport timeouts преодолены; автоматический wake/replug/preview checker остаётся открытым. + +### BLE04 — чтение настроек при включённой X4 вне Android, 10.09.2026 + +Владелец сообщил: Power-off Charging установлен в «Только зарядка», доступны +«Зарядка и включение» и «Зарядка и запись»; Wi-Fi Always On. Он нажал Bluetooth +Wakeup, но конечное значение переключателя ещё уточняется. После USB out/in +на дисплее осталось изображение с линз, Android не появился. Точное время +нажатий неизвестно; это новый owner report, не автоматически измеренный цикл. + +BLE04 intent: текущий diagnostic artifact a919d293c292935cfd636f0e, SHA-256 +bf1325b882d696314507f98fc627235ec579fa7d910035fb6155ce51f939b698, +входящий в исходники model0.1.3-4. На существующей Ubuntu после read-only Core +snapshot выполнить одну fresh20s scan/inspection для BLE03 кандидата с +--read-options и прежним expected device ID. USB ownership guard запрещает +Connect/GET при возвращении SDK USB. Иначе стандартные DIS reads и временная +BE82 CCCD subscription, затем только GetOptions8: полные serial15/model48, +после exact hash match95/97. Любая ошибка/timeout прекращает опыт без retries. +Нет setters, Pair, off/on, записи на SD, изменения USB, служб или установки. +Собственные notify/connection/filter снимаются в cleanup. Полный исходный USB +serial, BLE адрес, ответы и frame bytes сохраняются только в private evidence. + +Перед запуском владелец явно подтвердил Bluetooth Wakeup включён; затем +повторно подтвердил Power-off Charging = «Только зарядка». Никакого изменения +этого второго параметра ещё не было. Включение Wakeup выполнено владельцем на +самой камере; это отдельная camera-side предпосылка, не автоматическая настройка +из installer. Её программное чтение/установка пока не квалифицированы. + +Core snapshot09:11:57.852449 UTC, Mac monotonic451997.875703666: +Core operational, Rover006 online, D455 streaming, X4 offline/idle. +Private before.json SHA-256 +26510f559498e7d02f86b578e0d4c09e51df8ceb931432f768e80af01941198c. +BLE04 выполнен09:12:33.540701–09:12:59.133004 UTC, +Ubuntu monotonic329861.421788818–329887.014153528. Один fresh candidate, +GATT Connect/services resolution прошли; BE80 с BE81 read/write, BE82 notify, +BE83 read. DIS180A отсутствует. Один GetOptions8 с15/48 отправлен; пришёл +7-byte packet07000000050000 (total size7,type5). Текущий strict message reader +не допускает такой тип и завершил опыт Unknown packet framing, returncode1. +Это не подтверждение identity, не Options95/97 response и не доказательство +отказа чтения на firmware. Полную семантику type5/handshake нужно установить +до изменения reader или повторного опыта; ответов/команд наугад нет. +Vendor read-request writes1, CCCD subscriptions1, Pair0, setters0, +cleanup errors0, собственные notification/connection закрыты. Опыт автоматически +не повторялся. Private report.json SHA-256 +c422244ff00a77cd6b6a08aaf84581269e231cc2d0c2baa36b224dcca9707186. + +POWER04 intent: отдельный ручной тест штатного Power-off Charging. +Первичный источник, прочитан10.09: +https://onlinemanual.insta360.com/x5/en-us/troubleshooting/cannot-turn-on/auto-power-on +Scenario2 явно применим к X4/X5: Charge and power on включает камеру при +подключении питания; Android mode эта инструкция не гарантирует. +Владелец отсоединяет USB, выбирает «Зарядка и включение», штатно выключает +X4, затем подключает прежний USB к тому же Mini без нажатия Power. Результат +экрана/Android/SDK наблюдается отдельно. SD autostart «Зарядка и запись» не +используется. Если проверка не проходит, параметр можно вернуть в прежнее +«Только зарядка»; обязательность ручной настройки фиксируется явно. Ubuntu, +installer, порты, службы и SDK не меняются. Само намерение не является +подтверждением выполнения владельцем или приёмкой автономности. + +POWER04 observer: тот же a919d293c292935cfd636f0e, один --scan-seconds60 без +--address/--read-options. Только discovery/sysfs; никакого Connect или vendor +write. Запрос владельцу отправлен до старта observer, поэтому первое физическое +событие может предшествовать окну; задержка от нажатия не вычисляется без +совпавшей временной опоры. Отсутствие ответа владельца не считается тестом. + +POWER04 observer complete09:15:09.540660–09:16:09.711625 UTC, +Ubuntu monotonic330017.421746509–330077.592716198,58 USB samples, +vendor writes0/Pair0/CCCD0/cleanup errors0. В09:15:52.130612 появился Mass Storage +070a:4026/speed5000,09:15:57.238720 отсутствует,09:15:59.280009 появился SDK +2e1a:0002/speed5000. Private observer.json SHA-256 +b8f58d9c5d356082514ab693e53b0fcb26ee9d162384cc449441784dfa38f401. +Core readback09:16:46.738402 UTC, Mac monotonic452286.762178125: прежняя X4 +online/idle/preview0/recording0, новая session от09:15:59.915770 UTC, +Rover006 online, D455 streaming с прежней session, Core operational. +Private after.json SHA-256 +457bbec31bf2324161a7ee42a603ef0784c5a8504b1a632126e4eccff2f308fa. +Это измеренный возврат USB/SDK; владелец ещё не подтвердил выполнение всей +последовательности и отсутствие нажатия Power после подачи USB. До ответа +результат не отмечается как доказанное Charge and power on → Android. +Все собственные observers завершены; никакого BLE wake/off команды не было. + +POWER04 owner confirmation получен после observer: владелец сообщил фактический +порядок camera off → USB out → USB in, после подключения камера сама включилась +в Android и приложение нашло её. Это подтверждает один цикл запуска от подачи +USB в контексте предложенного Charge and power on, без отдельного включения +Power после подключения. Порядок отличается от первоначальной инструкции +(USB out до off), поэтому в результате фиксируется именно сообщённый порядок. +Программное чтение выбранного camera-side charging flag не выполнялось. +Warm replug включённой камеры и самостоятельное BLE off/on ещё не приняты. + +REPLUG04 intent: после POWER04 один warm USB out/in без выключения камеры +и без Power/выбора режима. Предполагается сохранённая настройка Charge and +power on; если владелец не менял её, это должно быть отражено в результате. +Тот же versioned a919 diagnostic, один scan-only60s; USB/SDK evidence отдельно +от owner report, никаких BLE Connect/CCCD/vendor commands. Существующий +X4 preview0/recording0; D455/Node/Core остаются рабочими. Успех POWER04 не +закрывает этот другой сценарий и не обосновывает автоматический SD start. + +REPLUG04 complete09:19:24.624291–09:20:24.792218 UTC, +Ubuntu monotonic330272.505379028–330332.673308194. В начале SDK2e1a:0002/5000, +09:19:27.846367 исчезла; до конца окна не вернулась. Владелец подтвердил +USB out/in без camera off: карточка пропала, камера не перешла в Android. +Warm replug не прошёл. Vendor writes0/Pair0/CCCD0/cleanup0; private observer +SHA-256aeec9c36ebebe98b377286b82ea36381f60c283886fe290754761cb155525b49. + +B13 / BLE05 intent: устраняется конкретная ошибка reader из BLE04. +В pinned SDK статически проверены CameraSession::Start0x5e9dc0 (регистрация +callback type5 в0x5e9e61) и callback0x5e94e0→OnHeartbeatHandler. Это доказывает +класс полученного пустого7-byte type5; SDK не загружался и не исполнялся. +Private disassembly SHA-256 +07c3a7223a4197cb67e740352a4f220c1a08512b17ba7040a4052a423258bcae. +Reader принимает только exact07000000050000, считает максимум64 heartbeat, +не считает их ответами Options и не посылает никаких ACK/новых команд. +Добавлены2 synthetic tests split/combined/invalid/control flood. Ошибочные +или неизвестные packets по-прежнему завершают опыт. Новый model0.1.3-5 +содержит правку; установленная0.1.3-3 и Node bundle не меняются. +Diagnostic c67351f42c6f4d6185ce4083,24184 bytes, +SHA-256d10d929539f30872c0af1e0b4d1752b07b69afa43dcf02932f818df67cd4c51a, +source SHA-25627e9dc92974225bf3d00b506298a4fafe56fadddd92d6d4a75f78a5681cc3f46. +Package build artifact2961928f986008d3a7980c74,58332445 bytes, +SHA-256bf5bec58b924d0d92c5ef5a79c41095897e81c12ecf3cec4f38c3f139316aad4. +Сначала штатная package qualification на существующей Ubuntu (1GiB/180CPU s/ +nice10, без SDK/device access/install). После успешной квалификации BLE05 — +один новый read-options опыт при отсутствии USB SDK owner, с прежней identity, +по тем же границам/cleanup BLE04. Это исправленная версия после сохранённого +отказа, не автоматический retry неизвестного результата setter; setters0. + +B13 complete09:23:27.614776–09:23:50.280562 UTC, +Ubuntu monotonic330515.495862456,duration22.665818s.55 tests passed; +package0.1.3-5 revisionc495a5bdc03c1121e10126ee,58204390 bytes, +SHA-256262fb2cd0587822ac9f0bdf590f1a891a48d916832ddb62b7802ea664c8ae55e. +Result archive SHA-256eb32de81df8950ebe5068ad31d574bba324a3dab566a41937cc8f8d35ebae414, +скачан, hash/entries/package проверены, не установлен. +BLE05 выполнен09:24:38.285204–09:24:58.488505 UTC, +Ubuntu monotonic330586.166297987–330606.369597165: fresh candidates0, +поэтому Connect/GET не выполнялись, vendor writes0/Pair0/CCCD0/cleanup0. +Report SHA-2565954d7c2aaf87e21a77fb34a8dc61f5fda75470e44e11fc7abf03fa36fdd2082. +Core snapshot09:26:24.785589 UTC, Mac monotonic452864.83879075: Core operational, +Rover006 online,D455 streaming,X4 offline/idle. Handoff SHA-256 +3ad8d2468c42e059935e34ca2189eb859c7fd36c8c9289f03eda4a5013abd3b5. +Владелец подтвердил тёмный дисплей, USB оставлен подключённым. Выключение +или screen sleep не различены. Никакого нового аппаратного GET после heartbeat +fix не получено; успешные55 tests остаются синтетической квалификацией. + +### B14 / WAKE01 — адресная wake advertisement, 10.09.2026 + +Intent: отдельный packaged ble_wake.py, не generic GATT remote. Один20s +connectable peripheral advertisement, Apple company0x004c отдельно от24-byte +ORBIT payload с6 ASCII bytes serial suffix. Полный serial приходит в private +stdin JSON и должен совпасть по hash с прежним instance; пользователь явно +подтвердил Bluetooth Wakeup On. Не записывает настройки, не выключает камеру, +не эмулирует CE80/GPS/buttons, не включает запись. До публикации guard запрещает +существующий USB SDK owner/target bond/connection; adapter должен быть powered, +не Pairable/Discoverable, без других advertising instances. Эти свойства +проверяются, но не меняются. Нужные bluez/dbus/gi уже объявлены model package. + +Основание: pinned X4 remote README7964f1133e5d0f2c7eb73aaaaf5d6ebdd2127199, +SHA-256371ec3c3de881445ac5c7c32802f0ff965b54e286c5e03260407e56989cbf714; +docs14 описывает BlueZ5.72 semantics и ограничения. Limited discoverable +instance20s при non-discoverable adapter формирует flags05; никакого изменения +глобальной discoverability. Запрошен interval100–150ms, BlueZ без experimental +может его игнорировать; фактический on-air interval не измерен, Experimental +не включается и systemd не меняется. No SDK return не доказывает невозможность +wake-up. Radio selector6 не заменяет полную идентичность возвращённого USB. + +Rollback scope: unregister только собственного advertisement path/private +bus (BlueZ удаляет его и при разрыве owner), закрыть только новое соединение +с ранее сопоставленным BLE address; чужие/new unknown connections сохраняются +и отражаются как cleanup error. Переход камеры в on — желаемый результат, +автоматического power-off rollback нет. Успех требует прежнего USB identity +в SDK mode, затем отдельного Core readback. Pair/CCCD/GATT vendor writes0; +сама wake-реклама — реальная команда пробуждения, не read-only операция. + +B14 package build artifact888a7ea1f4be8f4f2672368b,58336992 bytes, +SHA-256725631fe01a8966c70827950c4d35d6cbdb1fa748eba1ceaf2ec1ec60271598b. +Wake artifact58bf450b04bb7da8c24ce327,33262 bytes, +SHA-256522fea49ee253ba21a069fb646f09f65f8e788c7dcf3c19afccacddb6aac4556. +Wake source SHA-256317377d3ca43dd4fb85c379f56fa2b25ba14158d9308b9d2a03ce7ecfdc7e6a7. +Первоначальный локальный candidate3236/e836 уточнён до staging (добавлен +guard non-discoverable для точного flags05), на Ubuntu не запускался. +Сначала штатная package qualification0.1.3-6 через прежний bounded builder, +затем один WAKE01. Никакой установки новой модели/Node bundle не выполняется. + +B14 complete09:36:51.924266–09:37:14.790304 UTC, +Ubuntu monotonic331319.805353548,duration22.866067622962873s. +59 tests passed за4.832s; model0.1.3-6 revisionb28a02ee0b3c30a729cf1bd1, +58206332 bytes, SHA-256 +8d62ec9465f3083ab2811d41e2749932bfe85ec80a1c7f409db280ca1a734ced. +Result archive SHA-256 +bca9dc7418f0d8181222492540700b296963827f5fbe1c639eb31e68082e255f, +скачан, hashes/entries/package проверены; пакет не установлен. + +WAKE01 complete09:38:22.143427–09:38:38.276180 UTC, +Ubuntu monotonic331410.024515616–331426.157269888,returncode0. +Одна advertisement зарегистрирована09:38:22.454663. USB отсутствовал, +затем Mass Storage09:38:32.197518 → отсутствует09:38:36.434160 → +SDK2e1a:0002 с прежним полным serial hash09:38:38.274886. +От регистрации до sampled SDK15.820223s; это не video latency. +GATT vendor writes0,Pair0,CCCD0,cleanup0; advertisement снята, +ActiveInstances после cleanup0, новых BLE connections нет. +Private wake01/report.json SHA-256 +5065182d66ea8a69506687984be582cab97f09f871ecf128201c5fb4333295e1. +Private target/report содержат serial/address, исключены из Git. + +Core readback09:39:35.013180 UTC,Mac monotonic453655.039106375: +Core operational,Rover006 online,X4 online/idle,preview0/recording0; +новая SDK session09:38:40.287529+00:00, прежний instance. +D455 streaming, прежняя session от06:01:30.304205Z. +Private wake01/after.json SHA-256 +18be7e96afd663555a63abd8987fadce69d66101ca8b6ed16a832823816541ae. +Владелец явно подтвердил: камера включилась и перешла в Android. +Один адресный Bluetooth wake с оставленным USB принят. Automatic runtime +trigger/retry/preview restore ещё не реализованы; immediate warm replug +REPLUG04 не исправлен. Camera-side Wakeup On остаётся явной предпосылкой. +Installed Node0.8.19/X40.1.3-3 сохранены; clean Ubuntu и несколько циклов +ещё не квалифицированы. Новых OS packages/services/permissions не добавляли. diff --git a/docs/node/09_INSTA360_X4_IMPLEMENTATION_STATUS.md b/docs/node/09_INSTA360_X4_IMPLEMENTATION_STATUS.md index eafb075..5a5e9ee 100644 --- a/docs/node/09_INSTA360_X4_IMPLEMENTATION_STATUS.md +++ b/docs/node/09_INSTA360_X4_IMPLEMENTATION_STATUS.md @@ -1,5 +1,44 @@ # Insta360 X4 — состояние реализации 10.09.2026 +## WAKE01 — первый успешный Bluetooth wake с возвратом Android + +Ubuntu выполнила один адресный20s wake advertisement при оставленном USB. +Прежняя X4 вернулась через Mass Storage в SDK примерно через16s; Core открыл +новую сессию09:38:40 UTC без подготовки/перезапуска служб. Владелец подтвердил +включение и Android на дисплее. Core/Node online, D455 продолжила streaming. +Advertisement снята, cleanup0. Preview в этом опыте не запускался. + +[Модуль пробуждения](14_INSTA360_X4_BLUETOOTH_WAKE.md) включён в исходники +model0.1.3-6; B14 собран на существующей Ubuntu,59 tests passed. Новая модель +не установлена: Node0.8.19 и X40.1.3-3 сохранены. Следующая работа — подключить +bounded wake к восстановлению конкретного instance с сохранением намерения +оператора, затем штатно обновить bundle и проверить несколько циклов. +Это один успешный инженерный запуск, не готовый automatic runtime recovery. + +REPLUG04 подтвердил отказ обычного USB out/in включённой камеры. BLE05 при +тёмном дисплее не увидел fresh candidates и не отправлял GET. Reader теперь +распознаёт точный heartbeat07000000050000 по статическому SDK proof; чтение +Options95/97 на hardware не подтверждено. Bluetooth Wakeup On включён +владельцем и остаётся camera-side prerequisite. + +## BLE04 / POWER04 — чтение остановлено, один запуск от питания подтверждён + +Владелец включил Bluetooth Wakeup и подтвердил On; до POWER04 charging flag +был Charge only. После USB out/in включённая X4 снова не перешла в Android. +BLE04 установил GATT connection, выполнил временную CCCD subscription и один +GetOptions8 для identity15/48. Получен7-byte type5 packet, не поддержанный +strict reader; опыт остановлен и соединение закрыто, без повторов/setters. +Identity/Options95/97 не прочитаны. Это открытый transport вопрос, не firmware +отказ параметра. Следующий отдельный опыт POWER04 — штатный Charge and power on: +USB out → выбор настройки → выключение → USB in без кнопок. В09:15:52 +появился Mass Storage,09:15:59 — SDK USB и новая session прежней X4. Core/Node +online,D455 streaming. Владелец подтвердил фактический порядок camera off → +USB out → USB in и самостоятельное включение сразу в Android. Один цикл +запуска выключенной X4 от подачи USB подтверждён; это ещё не warm replug +включённой камеры. REPLUG04 отдельно подтвердил отказ такого переподключения. +Обе camera-side настройки должны учитываться как явные предпосылки, +их автоматическая установка из Core/Node не реализована. + ## POWER03/BLE03 — пробуждение кнопкой и первый BLE candidate Одиночное короткое Power при уже вставленном USB восстановило Android без @@ -11,8 +50,8 @@ BLE03 scan20s нашёл один свежий BE80 candidate с именем по USB serial suffix; полный USB serial hash совпал с прежним instance, USB2e1a:0002/speed5000. -GATT identity/auth не проверялись. Следующий шаг — состояние Bluetooth Wakeup -и отдельный адресный wake artifact; активный SDK owner не перехватывается. +GATT identity/auth в BLE03 не проверялись. Последующие BLE04/POWER04 описаны +выше; активный SDK owner не перехватывается. ## BLE01/BLE02 — первый модуль беспроводной диагностики @@ -21,7 +60,9 @@ discovery, стандартное GATT inspection и отдельное чтен Те же модули и OS dependencies включены в исходники model package0.1.3-4. На Ubuntu initial scan20s и последующий scan60s не обнаружили X4; владелец сообщил о тёмном экране. Позднее короткое Power подтверждено в POWER03 выше. -Read-options, pairing, setters и power-команды на hardware не запускались. +В BLE01/02 read-options не запускался; позднейший BLE04 описан выше. +В BLE01/02 pairing, setters и power-команды на hardware не выполнялись; +позднейший активный wake advertisement принят отдельно в WAKE01 выше. Installed Node0.8.19 / X40.1.3-3 сохранены; bundled Node profile ещё не обновлялся. Автовозврат Android остаётся открытым; нельзя считать успешную сборку его приёмкой. diff --git a/docs/node/10_INSTA360_X4_NEXT_STEPS.md b/docs/node/10_INSTA360_X4_NEXT_STEPS.md index 0b2e650..0b7391b 100644 --- a/docs/node/10_INSTA360_X4_NEXT_STEPS.md +++ b/docs/node/10_INSTA360_X4_NEXT_STEPS.md @@ -30,6 +30,34 @@ Node0.8.19 bundle пока остаётся0.1.3-3; его обновление ## Очередь реализации +**Актуальный результат WAKE01:** один адресный Bluetooth wake с Ubuntu +разбудил X4 при подключённом USB; камера сама вернулась в Android/SDK, +владелец подтвердил экран. Node открыл прежний instance, D455 сохранила поток. +Модуль упакован в0.1.3-6,59 Ubuntu tests passed, новая модель не установлена. +REPLUG04 без wake остался неуспешным. Ближайший increment: + +1. Сохранять полную identity и явное намерение восстановления отдельно для + каждого instance; не будить намеренно выключенную оператором камеру. +2. Подключить bounded wake и сериализацию adapter ownership; отменять при + появлении SDK, ограничивать retries, не повторять SD-команды при unknown. +3. Доставить через Node bundle и штатную подготовку из Core/Node, затем + проверить возврат SDK/preview, несколько циклов и независимость камер. + +Camera-side Bluetooth Wakeup On остаётся явной предпосылкой. Wake при тёмном +дисплее не доказывает recovery зависшей включённой камеры или clean Ubuntu. +Следующий абзац сохраняет порядок предыдущих опытов; их исходы уточнены ниже. + +Уточнение BLE04/POWER04: Bluetooth Wakeup теперь включён владельцем. GATT +Connect прошёл, но первый identity GetOptions получил неподдержанный7-byte +type5 packet, поэтому без retries/setters закрыт; это отдельный вопрос framing. +Обнаруженный штатный Power-off Charging → Charge and power on проверяется +раньше следующей BLE-команды. Observer зафиксировал возврат Mass Storage→SDK +и новой session; владелец подтвердил самостоятельный Android после camera off +→ USB out → USB in. Один запуск выключенной X4 от подачи питания подтверждён. +REPLUG04 включённой X4 завершился отказом; успех POWER04 его не закрывает. +Ручные camera-side flags фиксируются как prerequisites, не скрытая доработка +Ubuntu. «Зарядка и запись» не используется для операторского preview. + **Уточнение владельца10.09 после NET03:** ближайший приоритет после применения B11 — автономность подключения и питание. Проверить сохранение Android-режима, replug/cold start, автоматическое восстановление SDK и просмотра; определить diff --git a/docs/node/11_INSTA360_X4_AUTONOMY_AND_USB_POWER.md b/docs/node/11_INSTA360_X4_AUTONOMY_AND_USB_POWER.md index 27b8c21..d0e4c18 100644 --- a/docs/node/11_INSTA360_X4_AUTONOMY_AND_USB_POWER.md +++ b/docs/node/11_INSTA360_X4_AUTONOMY_AND_USB_POWER.md @@ -6,8 +6,19 @@ существующего Ubuntu Mac mini. Ниже разделены выполненные проверки и оставшаяся приёмка; успешная полная автономность пока не доказана. +**Результат WAKE01:** один адресный Bluetooth wake с Ubuntu при оставленном +USB включил прежнюю X4 и вернул Android/SDK примерно за 16 секунд. Владелец +подтвердил экран, Core открыл новую сессию без подготовки, D455 сохранила поток. +Модуль включён в model 0.1.3-6, B14 прошёл 59 Ubuntu tests; новая модель ещё +не установлена. [Реализация и границы](14_INSTA360_X4_BLUETOOTH_WAKE.md). +Обычный warm replug REPLUG04 остался неуспешным. Теперь ближайший шаг — +подключить wake к bounded recovery конкретного instance с сохранением намерения +оператора, затем штатно доставить bundle и проверить несколько циклов/preview. +Bluetooth Wakeup On остаётся явной camera-side предпосылкой; ниже сохранена +история исследований, а не утверждение готовности автоматического recovery. + **Уточнение RESEARCH01:** [исследование Android reconnect](12_INSTA360_X4_ANDROID_RECONNECT_RESEARCH.md) -выявило внутренний udisk_mode и независимый BLE remote off/on. Ближайший шаг — +выявило внутренний udisk_mode и независимый BLE remote off/on. На том этапе — проверка беспроводного управления и чтение настройки у точной X4 через versioned artifact. Отсутствие VBUS switching в PORT01 не доказывает невозможность программного восстановления камеры; внешний хаб остаётся альтернативной веткой. diff --git a/docs/node/13_INSTA360_X4_BLE_DIAGNOSTIC.md b/docs/node/13_INSTA360_X4_BLE_DIAGNOSTIC.md index 6bba7a1..b79f507 100644 --- a/docs/node/13_INSTA360_X4_BLE_DIAGNOSTIC.md +++ b/docs/node/13_INSTA360_X4_BLE_DIAGNOSTIC.md @@ -4,6 +4,15 @@ [исследования reconnect](12_INSTA360_X4_ANDROID_RECONNECT_RESEARCH.md). Автоматический возврат Android ещё не принят. +Дополнение BLE04/BLE05/B14: первый GATT GetOptions получил только точный +heartbeat `07000000050000`; reader теперь распознаёт его по статическому +подтверждению SDK, не считает ответом Options и не отправляет ACK. B13 прошёл +55 tests. BLE05 при тёмном экране не нашёл свежую X4, поэтому GET не выполнялся; +аппаратное чтение identity/95/97 остаётся открытым. [Отдельный WAKE01](14_INSTA360_X4_BLUETOOTH_WAKE.md) +успешно разбудил камеру и вернул Android/SDK. Это активная advertisement-команда, +а не GATT GET. B14 model 0.1.3-6 включает все три модуля и прошёл 59 tests. +Runtime automatic trigger и новая модель ещё не развёрнуты. + Финальный B12-R2: пакет 0.1.3-4 собран на Ubuntu, 53 tests passed, включая 15 BLE cases. Проверены реальные содержимое .deb, зависимости, cold imports и статическая ELF closure. SHA-256 пакета: @@ -16,8 +25,9 @@ отдельное GATT inspection и явный режим чтения настроек. - `ble_options.py`: только GET_OPTIONS, строгие packet/protobuf bounds и разбор ответов. Нет generic send, SET_OPTIONS, capture, reboot или power-off. -- `build_ble_diagnostic.py`: артефакт с hashes обоих модулей; тот же код - включён в model Debian package 0.1.3-4 через `build_deb.py`. +- `build_ble_diagnostic.py`: артефакт с hashes трёх модулей; diagnostic — + обычный entrypoint, wake выбирается явно через `--wake`. Тот же код + включён в model Debian package 0.1.3-6 через `build_deb.py`. - `check_package_entry.py`: проверяет наличие/совпадение модулей в реальном .deb, объявленные зависимости и synthetic tests. @@ -126,8 +136,9 @@ GetOptions не выполнялся. Vendor commands0, pairing0, CCCD subscript wake artifact. [Официальная X4 FAQ](https://onlinemanual.insta360.com/x4/en-us/operating-tutorials/connect/connect-app) требует включённую настройку Settings → General → Bluetooth Wakeup (если General отсутствует — непосредственно в Settings). Описанная там поддержка -приложения iOS не является приёмкой Linux эмуляции. Значение настройки нашей -камеры пока unknown. Ни выключение, ни wake advertisement не выполнялись. +приложения iOS не является приёмкой Linux эмуляции. Позднее владелец включил +Bluetooth Wakeup и явно подтвердил On. Это camera-side ручная предпосылка, +не действие Ubuntu installer. Wake advertisement агентом ещё не выполнялся. Исходную точную USB identity использовать из private evidence; не объединять камеры по suffix. GetOptions — отдельная ветка без активного USB SDK owner. @@ -137,3 +148,37 @@ Android setter. Если уже ANDROID=1, запись того же значе реализованного allowlist. BLE remote off/on требует отдельного артефакта и проверенного wake-up до выключения камеры. Ни один из этих исходов пока не установлен. Raw reports/hashes/UTC/monotonic — в installation ledger. + +## BLE04 и следующий контроль Power-off Charging + +После выхода владельца в настройки и USB out/in X4 осталась включённой +с изображением на дисплее, USB SDK отсутствовал. BLE04 выполнил Connect, +GATT service discovery, временный StartNotify и один GetOptions8 для15/48. +DIS180A отсутствовал. Пришёл packet `07000000050000`, length7/type5; +текущий reader ожидает message type4 и прекратил работу с Unknown packet framing. +Это фактические bytes, но их семантика/handshake пока не установлены. +Нельзя считать это успешным чтением, авторизацией или firmware отказом Options95. +До95/97 дело не дошло, serial через GATT не подтверждён. Повторных записей не +было; собственные notify/connection закрыты, cleanup0, Pair0, setters0. + +Владелец обнаружил штатную настройку Power-off Charging со значениями Charge +only / Charge and power on / Charge and record; выбрано Charge only. +[Официальная инструкция, Scenario2 X4/X5](https://onlinemanual.insta360.com/x5/en-us/troubleshooting/cannot-turn-on/auto-power-on) +подтверждает включение камеры при подаче питания для Charge and power on, +но не обещает переход в Android. POWER04 выделен как отдельный ручной контроль: +USB out → Charge and power on → штатное выключение → USB in без кнопок. +Charge and record не используется. Дождаться фактического результата владельца +и USB/SDK evidence; изменённая camera-side настройка должна быть явно описана +как prerequisite первичной настройки устройства. При необходимости rollback — +прежнее Charge only. Ни новый Ubuntu prerequisite, ни автоматическая настройка +камеры из Core/Node этим не заявляются. + +В POWER04 observer09:15:09–09:16:09 UTC зафиксированы Mass Storage09:15:52, +SDK2e1a:0002/USB5000 в09:15:59 и новая SDK session09:15:59.915770. +Core snapshot09:16:46 подтверждает прежний X4 instance online/idle, +preview0/recording0, Node online и D455 streaming. Владелец затем подтвердил +фактический порядок camera off → USB out → USB in и самостоятельный Android +после подключения. Принят один запуск от подачи USB к выключенной X4 в +контексте предложенного charging flag; его программного readback нет. +Warm replug включённой X4 проверяется отдельно как REPLUG04. BLE wake +advertisements и power commands агентом не выполнялись. diff --git a/docs/node/14_INSTA360_X4_BLUETOOTH_WAKE.md b/docs/node/14_INSTA360_X4_BLUETOOTH_WAKE.md new file mode 100644 index 0000000..fb17bfa --- /dev/null +++ b/docs/node/14_INSTA360_X4_BLUETOOTH_WAKE.md @@ -0,0 +1,79 @@ +# X4: ограниченная проверка пробуждения с Ubuntu + +WAKE01/B14,10.09.2026. Предпосылки: владелец включил Bluetooth Wakeup; +полный USB serial прежнего instance сохранён в private evidence, USB подключён, +экран тёмный, USB SDK отсутствует. Sleep и power-off до опыта не различены. + +## Реализация и упаковка + +`plugins/insta360-x4/packaging/ble_wake.py` входит в model0.1.3-6 вместе с +зависимостями BlueZ/dbus/gi. `build_ble_diagnostic.py --wake` создаёт versioned +artifact с hash всех трёх модулей. Builder квалифицирует .deb на существующей +Ubuntu до hardware запуска; установка, службы/права и Node bundle не меняются. +Код ещё не подключён к автоматическому runtime recovery или UI. + +Private stdin target содержит полный serial, ожидаемый device ID, ранее +сопоставленный Bluetooth address и подтверждённый wakeup_enabled. Hash serial +проверяется до Bluetooth; одного suffix недостаточно. Возврат SDK требует +2e1a:0002 и совпадение полного USB serial hash, затем Core readback. + +## Команда, основание и границы + +[Первичный разбор физической X4](https://github.com/TheAngryRaven/insta360-ble-gps-spec/tree/7964f1133e5d0f2c7eb73aaaaf5d6ebdd2127199) +описывает пробуждение по manufacturer advertisement до соединения с пультом. +Company0x004c передаётся BlueZ отдельно; payload: +`02 15 09 4f 52 42 49 54 09 ff 0f 00 00 00 00 00 e4 01`. +24 bytes + company2 + AD length/type2 + flags3 =31 bytes. Имя, appearance, +service UUIDs и secondary PHY не добавляются. CE80/GPS/button события не +эмулируются. Нет pairing, GATT writes, записи/фото, shutdown или настройки USB. +Wake advertisement сама является активной командой; ноль GATT writes не +означает отсутствие воздействия на камеру. + +[BlueZ Advertising API](https://bluez.readthedocs.io/en/latest/advertising-api/) +используется через private D-Bus owner и собственный object path. Асинхронный +RegisterAdvertisement позволяет обрабатывать GetAll; deadline ограничен. +По [BlueZ5.72 source](https://github.com/bluez/bluez/blob/5.72/src/advertising.c) +DiscoverableTimeout задаёт limited mode; non-discoverable adapter добавляет +BR/EDR-not-supported, ожидаемые flags05. Существующие adapter flags не меняются. +Занятый advertiser, Pairable/Discoverable adapter или USB SDK owner прекращают +опыт. Timeout20s и явный unregister ограничивают собственную рекламу; потеря +owner bus также удаляет instance. + +Min/MaxInterval100/150ms запрошены, но без experimental BlueZ может их +игнорировать. Фактический radio interval/полный on-air packet в этом опыте не +измерены; systemd Experimental не включается. Отрицательный результат не +закрывает все возможные BLE wake transports. + +Новые соединения с прежним сопоставленным адресом X4 закрываются; ранее +существующие не принимаются в опыт. Неопознанные новые соединения сохраняются +и отмечаются как cleanup error. Снятие advertisement не выполняет camera +power-off: включённая камера — желаемый результат. Нет автоматических retries. + +## Приёмка + +Принять только фактическое появление прежней X4 в USB SDK и новую сессию Core +без Power/выбора Android, при сохранении Node/D455. Registered advertisement +не равна доставленному wake; SDK USB не равен движущемуся изображению. +Пакет/тесты/UTC/monotonic/hash/raw и фактический результат — в ledger07. +Автоматическая доставка camera-side настройки Bluetooth Wakeup через installer +ещё не реализована. Clean Ubuntu acceptance и несколько камер остаются открыты. + +## Фактический результат WAKE01 + +10.09.2026,09:38:22–09:38:38 UTC: одна advertisement, returncode0, cleanup0. +После регистрации09:38:22.454663 USB появился как Mass Storage09:38:32.197518, +затем SDK2e1a:0002 с прежним полным serial hash09:38:38.274886. Это15.820223s +до sampled SDK, не задержка изображения. Advertisement снята, ActiveInstances0, +Pair/CCCD/GATT vendor writes0, новых BLE connections нет. + +Core readback09:39:35 подтвердил прежний X4 instance online/idle и новую SDK +session09:38:40.287529. Node online, D455 streaming с прежней сессией. +Владелец явно подтвердил: камера включилась и перешла в Android. B14 .deb +прошёл59 tests; artifact/UTC/monotonic/SHA и private evidence записаны в ledger07. +Установленные Node0.8.19 и X40.1.3-3 сохранены; preview в WAKE01 не запускался. + +Принят один контролируемый wake с оставленным USB. Автоматическое включение +этого модуля в runtime, сохранение identity/намерения восстановления для каждого +instance, ограничение повторов, восстановление preview и повторяемость остаются +следующим increment. Wake не доказывает немедленное восстановление включённой +зависшей камеры: обычный warm replug REPLUG04 отдельно завершился отказом. diff --git a/plugins/insta360-x4/packaging/ble_diagnostic.py b/plugins/insta360-x4/packaging/ble_diagnostic.py index 266d603..baf694f 100644 --- a/plugins/insta360-x4/packaging/ble_diagnostic.py +++ b/plugins/insta360-x4/packaging/ble_diagnostic.py @@ -283,6 +283,7 @@ class Inspector: self.report["notifications"].append({"hex": raw.hex(), **timestamp()}) try: received.extend(reader.feed(raw)) + self.report["heartbeat_packets"] = reader.heartbeats except ValueError as error: errors.append(str(error)) diff --git a/plugins/insta360-x4/packaging/ble_options.py b/plugins/insta360-x4/packaging/ble_options.py index f946854..06d7456 100644 --- a/plugins/insta360-x4/packaging/ble_options.py +++ b/plugins/insta360-x4/packaging/ble_options.py @@ -83,6 +83,7 @@ class PacketReader: def __init__(self): self.buffer = bytearray() + self.heartbeats = 0 def feed(self, chunk): if not chunk or len(self.buffer) + len(chunk) > MAX_PACKET * 2: @@ -91,12 +92,21 @@ class PacketReader: result = [] while len(self.buffer) >= 4: size = struct.unpack_from(" 64: + raise ValueError("Heartbeat bound exceeded") + continue _, kind, reserved, code, content, ident, tail = struct.unpack(" 4096: + raise ValueError("Private wake target exceeds limit") + run(json.loads(raw), report) + report["state"] = "complete" if report.get("sdk_usb_returned") else "no_sdk_return" + except Exception as error: + report.update(state="error", error=str(error)) + if report.get("cleanup_errors"): + report["state"] = "error" + report["finished"] = timestamp() + print(json.dumps(report), flush=True) + return 0 if report["state"] == "complete" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/insta360-x4/packaging/build_ble_diagnostic.py b/plugins/insta360-x4/packaging/build_ble_diagnostic.py index 431a017..3c10424 100644 --- a/plugins/insta360-x4/packaging/build_ble_diagnostic.py +++ b/plugins/insta360-x4/packaging/build_ble_diagnostic.py @@ -1,53 +1,62 @@ -"""Package the exact diagnostic entry point; never install host prerequisites.""" +"""Package exact model diagnostics; never install host prerequisites.""" +import argparse import hashlib import json import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -ENTRY = """import hashlib, json, sys, zipfile +ENTRY = """import hashlib, json, sys, types, zipfile from pathlib import Path with zipfile.ZipFile(sys.argv[0]) as archive: - manifest = json.loads(archive.read("manifest.json")) - source = archive.read("ble_diagnostic.py") - codec = archive.read("ble_options.py") -if hashlib.sha256(source).hexdigest() != manifest["source_sha256"]: - raise RuntimeError("Diagnostic source hash mismatch") -if hashlib.sha256(codec).hexdigest() != manifest["codec_sha256"]: - raise RuntimeError("Diagnostic codec hash mismatch") -ident = hashlib.sha256(source + b"\\0" + codec).hexdigest()[:24] -if Path(sys.argv[0]).name != "mission-core-x4-ble-" + ident + ".pyz": + raw = archive.read("manifest.json") + manifest = json.loads(raw) + names = ("ble_options", "ble_diagnostic", "ble_wake") + modules = {name: archive.read(name + ".py") for name in names} +ident = hashlib.sha256(raw).hexdigest()[:24] +entry = manifest["entrypoint"] +if entry not in {"ble_diagnostic", "ble_wake"}: + raise RuntimeError("Unknown diagnostic entry point") +prefix = "mission-core-x4-wake-" if entry == "ble_wake" else "mission-core-x4-ble-" +if Path(sys.argv[0]).name != prefix + ident + ".pyz": raise RuntimeError("Diagnostic artifact name mismatch") -import types -module = types.ModuleType("ble_options") -exec(compile(codec, "ble_options.py", "exec"), module.__dict__) -sys.modules["ble_options"] = module -exec(compile(source, "ble_diagnostic.py", "exec"), {"__name__": "__main__"}) +for name, source in modules.items(): + if hashlib.sha256(source).hexdigest() != manifest["modules"][name]: + raise RuntimeError("Diagnostic source hash mismatch") +for name, source in modules.items(): + module = types.ModuleType(name) + sys.modules[name] = module + exec(compile(source, name + ".py", "exec"), module.__dict__) +raise SystemExit(sys.modules[entry].main()) """ -def build(): - source = (ROOT / "packaging/ble_diagnostic.py").read_bytes() - codec = (ROOT / "packaging/ble_options.py").read_bytes() - sha = hashlib.sha256(source).hexdigest() +def build(wake=False): + modules = { + name: (ROOT / "packaging" / (name + ".py")).read_bytes() + for name in ("ble_options", "ble_diagnostic", "ble_wake") + } manifest = { - "schema": "missioncore.insta360.ble-diagnostic-artifact/v1", - "source_sha256": sha, - "codec_sha256": hashlib.sha256(codec).hexdigest(), + "schema": "missioncore.insta360.ble-diagnostic-artifact/v2", + "entrypoint": "ble_wake" if wake else "ble_diagnostic", + "modules": {name: hashlib.sha256(data).hexdigest() for name, data in modules.items()}, "dependencies": ["bluez", "python3-dbus", "python3-gi"], "persistent_system_changes": False, - "vendor_commands": [8], - "scope": "bounded-discovery-and-explicit-identity-options-read", + "vendor_commands": [] if wake else [8], + "scope": "single-addressed-wake-advertisement" + if wake + else "bounded-discovery-and-explicit-identity-options-read", } - ident = hashlib.sha256(source + b"\0" + codec).hexdigest()[:24] - output = ROOT / "build" / ("mission-core-x4-ble-" + ident + ".pyz") + raw = json.dumps(manifest, sort_keys=True).encode() + ident = hashlib.sha256(raw).hexdigest()[:24] + prefix = "mission-core-x4-wake-" if wake else "mission-core-x4-ble-" + output = ROOT / "build" / (prefix + ident + ".pyz") with output.open("xb") as stream, zipfile.ZipFile(stream, "w") as archive: for name, data in { "__main__.py": ENTRY.encode(), - "ble_diagnostic.py": source, - "ble_options.py": codec, - "manifest.json": json.dumps(manifest, sort_keys=True).encode(), + "manifest.json": raw, + **{name + ".py": data for name, data in modules.items()}, }.items(): info = zipfile.ZipInfo(name, date_time=(2026, 9, 10, 0, 0, 0)) info.external_attr = 0o600 << 16 @@ -56,10 +65,12 @@ def build(): return { "artifact": str(output), "sha256": hashlib.sha256(output.read_bytes()).hexdigest(), - "source_sha256": sha, "bytes": output.stat().st_size, + "modules": manifest["modules"], } if __name__ == "__main__": - print(json.dumps(build())) + parser = argparse.ArgumentParser() + parser.add_argument("--wake", action="store_true") + print(json.dumps(build(parser.parse_args().wake))) diff --git a/plugins/insta360-x4/packaging/build_deb.py b/plugins/insta360-x4/packaging/build_deb.py index 86824dc..0b8804d 100644 --- a/plugins/insta360-x4/packaging/build_deb.py +++ b/plugins/insta360-x4/packaging/build_deb.py @@ -16,7 +16,7 @@ sys.path.insert(0, str(REPOSITORY / "scripts/packaging")) from debian import package # noqa: E402 from fetch_sdk import verify # noqa: E402 -VERSION = "0.1.3-4" +VERSION = "0.1.3-6" WHEELS = { "aiohappyeyeballs", "aiohttp", @@ -159,6 +159,7 @@ def build(output): "prepare.py", "ble_diagnostic.py", "ble_options.py", + "ble_wake.py", ): files.append( ("usr/lib/mission-core-node/insta360/" + name, (PACKAGING / name).read_bytes(), 0o644) diff --git a/plugins/insta360-x4/packaging/check_package_entry.py b/plugins/insta360-x4/packaging/check_package_entry.py index 17bc2c3..b577594 100644 --- a/plugins/insta360-x4/packaging/check_package_entry.py +++ b/plugins/insta360-x4/packaging/check_package_entry.py @@ -23,7 +23,7 @@ data = subprocess.check_output(["/usr/bin/dpkg-deb", "--fsys-tarfile", str(packa with tarfile.open(fileobj=io.BytesIO(data)) as archive: payload = archive.extractfile("usr/share/mission-core-node/insta360/payload.zip").read() bundle = json.load(archive.extractfile("usr/share/mission-core-node/insta360/bundle.json")) - for name in ("ble_diagnostic.py", "ble_options.py"): + for name in ("ble_diagnostic.py", "ble_options.py", "ble_wake.py"): packaged = archive.extractfile("usr/lib/mission-core-node/insta360/" + name).read() if packaged != (ROOT / "packaging" / name).read_bytes(): raise ValueError("Packaged BLE module differs from the qualified source") diff --git a/plugins/insta360-x4/tests/test_ble_options.py b/plugins/insta360-x4/tests/test_ble_options.py index 3f13f67..eb5cc28 100644 --- a/plugins/insta360-x4/tests/test_ble_options.py +++ b/plugins/insta360-x4/tests/test_ble_options.py @@ -53,6 +53,30 @@ class OptionsTests(unittest.TestCase): {"acknowledged": True, "present": False, "value": None}, ) + def test_captured_heartbeat_is_not_an_options_response(self): + heartbeat = bytes.fromhex("07000000050000") + raw = reply(field(1, 95) + field(2, field(95, 1))) + reader = codec.PacketReader() + self.assertEqual(reader.feed(heartbeat[:4]), []) + self.assertEqual(reader.feed(heartbeat[4:]), []) + self.assertEqual(reader.heartbeats, 1) + packets = reader.feed(heartbeat + raw[:9]) + self.assertEqual(packets, []) + packets = reader.feed(raw[9:] + heartbeat) + self.assertEqual(len(packets), 1) + self.assertEqual(reader.heartbeats, 3) + self.assertEqual(codec.decode_options_response(packets[0], 1, [95])[95]["value"], 1) + + def test_short_control_packets_are_exact_and_bounded(self): + for raw in ("07000000040000", "07000000050100", "0800000005000000"): + with self.assertRaises(ValueError): + codec.PacketReader().feed(bytes.fromhex(raw)) + reader = codec.PacketReader() + for _ in range(64): + self.assertEqual(reader.feed(bytes.fromhex("07000000050000")), []) + with self.assertRaises(ValueError): + reader.feed(bytes.fromhex("07000000050000")) + def test_explicit_zero_and_packed_option_list(self): packet = codec.PacketReader().feed(reply(field(1, b"\x5f") + field(2, field(95, 0))))[0] self.assertEqual(codec.decode_options_response(packet, 1, [95])[95]["value"], 0) diff --git a/plugins/insta360-x4/tests/test_ble_wake.py b/plugins/insta360-x4/tests/test_ble_wake.py new file mode 100644 index 0000000..2ae234d --- /dev/null +++ b/plugins/insta360-x4/tests/test_ble_wake.py @@ -0,0 +1,64 @@ +"""Synthetic identity and exact wake payload; no host Bluetooth access.""" + +import hashlib +import importlib.util +import sys +import unittest +from pathlib import Path +from unittest.mock import patch + +directory = Path(__file__).resolve().parents[1] / "packaging" +sys.path.insert(0, str(directory)) +spec = importlib.util.spec_from_file_location("wake_under_test", directory / "ble_wake.py") +wake = importlib.util.module_from_spec(spec) +spec.loader.exec_module(wake) +sys.path.pop(0) + + +def target(serial="SYNTHETIC34UQG5"): + return { + "serial": serial, + "device_id": "instax4_" + hashlib.sha256(serial.encode()).hexdigest()[:32], + "bluetooth_address": "02:00:00:00:00:01", + "wakeup_enabled": True, + } + + +class WakeTests(unittest.TestCase): + def test_spec_vector_has_one_company_id_and_fits_legacy_advertisement(self): + data = wake.wake_payload(target()) + self.assertEqual(data.hex(), "0215094f5242495409ff0f0033345551473500000000e401") + self.assertEqual(len(data) + 7, 31) + + def test_unknown_wakeup_suffix_only_and_wrong_identity_are_rejected(self): + for value in ( + target("34UQG5"), + dict(target(), device_id="instax4_" + "0" * 32), + dict(target(), wakeup_enabled=False), + dict(target(), bluetooth_address="unknown"), + ): + with self.assertRaises(ValueError): + wake.wake_payload(value) + + def test_sdk_identity_requires_full_serial_and_correct_mode(self): + value = target() + device = {"idVendor": "2e1a", "idProduct": "0002", "serial": value["serial"]} + self.assertTrue(wake.matching_sdk({"devices": [device]}, value["device_id"])) + for changed in ( + dict(device, serial="34UQG5"), + dict(device, idVendor="070a", idProduct="4026"), + ): + self.assertFalse(wake.matching_sdk({"devices": [changed]}, value["device_id"])) + + def test_present_sdk_owner_blocks_before_any_bluetooth_access(self): + with ( + patch.object(wake, "assert_no_usb_owner", side_effect=RuntimeError("owned")), + patch.object(wake, "Inspector") as inspector, + ): + with self.assertRaisesRegex(RuntimeError, "owned"): + wake.run(target(), {}) + inspector.assert_not_called() + + +if __name__ == "__main__": + unittest.main()