Add bounded X4 Bluetooth wake and record Android recovery proof

This commit is contained in:
DCCONSTRUCTIONS
2026-09-10 12:48:09 +03:00
parent 556e376a62
commit f164606828
14 changed files with 789 additions and 43 deletions
@@ -1044,3 +1044,218 @@ MISSIONCOR-77 синхронизирован через официальный O
прежние блоки/checker, добавлены BLE0103/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.54070109:12:59.133004 UTC,
Ubuntu monotonic329861.421788818329887.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.54066009:16:09.711625 UTC,
Ubuntu monotonic330017.421746509330077.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.62429109:20:24.792218 UTC,
Ubuntu monotonic330272.505379028330332.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.61477609: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.28520409:24:58.488505 UTC,
Ubuntu monotonic330586.166297987330606.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. Запрошен interval100150ms, 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.92426609: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.14342709:38:38.276180 UTC,
Ubuntu monotonic331410.024515616331426.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 не добавляли.
@@ -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 остаётся открытым; нельзя считать успешную сборку его приёмкой.
+28
View File
@@ -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 и просмотра; определить
@@ -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 не доказывает невозможность
программного восстановления камеры; внешний хаб остаётся альтернативной веткой.
+49 -4
View File
@@ -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:0909: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 агентом не выполнялись.
@@ -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 <suffix:6 ASCII> 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:2209: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 отдельно завершился отказом.
@@ -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))
+11 -1
View File
@@ -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("<I", self.buffer)[0]
if not 16 <= size <= MAX_PACKET:
if size != 7 and not 16 <= size <= MAX_PACKET:
raise ValueError("Unknown packet framing")
if len(self.buffer) < size:
break
raw = bytes(self.buffer[:size])
del self.buffer[:size]
if size == 7:
# X4 BLE04 capture; SDK CameraSession::Start registers type5
# with OnHeartbeatHandler. This is not an Options response.
if raw != b"\x07\x00\x00\x00\x05\x00\x00":
raise ValueError("Unsupported short control packet")
self.heartbeats += 1
if self.heartbeats > 64:
raise ValueError("Heartbeat bound exceeded")
continue
_, kind, reserved, code, content, ident, tail = struct.unpack("<IBHHBIH", raw[:16])
if kind != 4 or reserved or tail or content != 2 or ident & 0xC0000000 != 0xC0000000:
raise ValueError("Unsupported message header/direction/fragmentation")
+216
View File
@@ -0,0 +1,216 @@
"""One bounded X4 wake advertisement; no camera GATT writes or power-off."""
import hashlib
import json
import re
import sys
from pathlib import Path
if "__file__" in globals():
sys.path.insert(0, str(Path(__file__).resolve().parent))
from ble_diagnostic import ( # noqa: E402
ADAPTER,
DEVICE,
PROPS,
Inspector,
assert_no_usb_owner,
plain,
timestamp,
usb_snapshot,
)
ADV = "org.bluez.LEAdvertisement1"
ADV_MANAGER = "org.bluez.LEAdvertisingManager1"
def wake_payload(target):
if set(target) != {"device_id", "serial", "bluetooth_address", "wakeup_enabled"}:
raise ValueError("Exact private target fields are required")
serial = target["serial"]
if not isinstance(serial, str) or not re.fullmatch(r"[A-Za-z0-9]{7,64}", serial):
raise ValueError("Full USB serial is required, never a suffix alone")
ident = "instax4_" + hashlib.sha256(serial.encode()).hexdigest()[:32]
if target["device_id"] != ident or target["wakeup_enabled"] is not True:
raise ValueError("USB identity and enabled camera wakeup must be confirmed")
if not re.fullmatch(r"(?:[0-9A-F]{2}:){5}[0-9A-F]{2}", target["bluetooth_address"]):
raise ValueError("Prior correlated Bluetooth address is required")
# Company ID 0x004c is passed separately to BlueZ. X4 remote spec section1.
return (
bytes.fromhex("0215094f5242495409ff0f00")
+ serial[-6:].encode()
+ bytes.fromhex("00000000e401")
)
def matching_sdk(samples, expected):
return any(
d.get("idVendor") == "2e1a"
and d.get("idProduct") == "0002"
and "instax4_" + hashlib.sha256(d.get("serial", "").encode()).hexdigest()[:32] == expected
for d in samples["devices"]
)
def run(target, report):
payload = wake_payload(target)
assert_no_usb_owner(target["device_id"])
inspector = Inspector(report)
advertisement = None
manager = None
registered = False
attempted = False
own_path = "/org/missioncore/x4/wake"
initial = {}
cleanup = []
try:
objects = inspector.objects()
adapters = [
(p, x)
for p, x in objects.items()
if x.get(ADAPTER, {}).get("Powered") and ADV_MANAGER in x
]
if len(adapters) != 1:
raise RuntimeError("Exactly one powered advertising adapter is required")
adapter_path, adapter = adapters[0]
if (
adapter[ADAPTER].get("Pairable", True)
or adapter[ADAPTER].get("Discoverable", True)
or adapter[ADV_MANAGER].get("ActiveInstances", -1) != 0
):
raise RuntimeError(
"Require non-pairable/non-discoverable adapter with no advertisements"
)
if not adapter[ADV_MANAGER].get("SupportedInstances"):
raise RuntimeError("No advertising instance is available")
initial = {p: plain(x[DEVICE]) for p, x in objects.items() if DEVICE in x}
for device in initial.values():
if device.get("Address") == target["bluetooth_address"] and any(
device.get(k) for k in ("Connected", "Paired", "Bonded")
):
raise RuntimeError("Preserve existing target connection/bond")
report["adapter_before"] = plain(adapter)
manager = inspector.iface(adapter_path, ADV_MANAGER)
import dbus.service
class Advertisement(dbus.service.Object):
released = False
@dbus.service.method(PROPS, in_signature="s", out_signature="a{sv}")
def GetAll(self, interface):
if interface != ADV:
return dbus.Dictionary({}, signature="sv")
return dbus.Dictionary(
{
"Type": dbus.String("peripheral"),
"ManufacturerData": dbus.Dictionary(
{dbus.UInt16(0x004C): dbus.Array(payload, signature="y")},
signature="qv",
),
"Discoverable": dbus.Boolean(True),
"DiscoverableTimeout": dbus.UInt16(20),
"Timeout": dbus.UInt16(20),
# Honored only with BlueZ experimental features. Never enable
# that setting here; actual on-air interval is not asserted.
"MinInterval": dbus.UInt32(100),
"MaxInterval": dbus.UInt32(150),
},
signature="sv",
)
@dbus.service.method(ADV, in_signature="", out_signature="")
def Release(self):
self.released = True
report["advertisement_released"] = timestamp()
advertisement = Advertisement(inspector.bus, own_path)
result, errors = [], []
assert_no_usb_owner(target["device_id"])
report["advertisement_intent"] = {
"manufacturer_id": 0x004C,
"bytes": len(payload),
"timeout_seconds": 20,
**timestamp(),
}
attempted = True
manager.RegisterAdvertisement(
own_path,
dbus.Dictionary({}, signature="sv"),
reply_handler=lambda: result.append(True),
error_handler=lambda e: errors.append(str(e)),
timeout=5,
)
inspector.pump(6, lambda: bool(result or errors))
if errors or not result:
raise RuntimeError(errors[0] if errors else "Advertisement registration timed out")
registered = True
report["advertisement_registered"] = timestamp()
inspector.pump(20, lambda: matching_sdk(usb_snapshot(), target["device_id"]))
report["sdk_usb_returned"] = matching_sdk(usb_snapshot(), target["device_id"])
finally:
if attempted and advertisement and not advertisement.released:
done, errors = [], []
try:
manager.UnregisterAdvertisement(
own_path,
reply_handler=lambda: done.append(True),
error_handler=lambda e: errors.append(str(e)),
timeout=5,
)
inspector.pump(6, lambda: bool(done or errors or advertisement.released))
if (errors and registered) or not (done or errors or advertisement.released):
cleanup.append("Advertisement removal did not complete")
except Exception as error:
cleanup.append(str(error))
# A wake beacon is connectable; disconnect only the prior correlated
# X4 address when it acquired a connection during our advertisement.
try:
current = {p: plain(x[DEVICE]) for p, x in inspector.objects().items() if DEVICE in x}
for path, device in current.items():
if device.get("Connected") and not initial.get(path, {}).get("Connected"):
if attempted and device.get("Address") == target["bluetooth_address"]:
inspector.iface(path, DEVICE).Disconnect(timeout=5)
report["wake_connection_closed"] = True
else:
cleanup.append("New uncorrelated Bluetooth connection preserved")
if manager:
report["adapter_after"] = plain(inspector.objects().get(adapter_path, {}))
except Exception as error:
cleanup.append(str(error))
try:
if advertisement:
advertisement.remove_from_connection()
except Exception as error:
cleanup.append(str(error))
finally:
# Private bus owner disappearance also removes its advertisement.
inspector.close()
report["cleanup_errors"].extend(cleanup)
def main():
report = {
"schema": "missioncore.insta360.ble-wake/v1",
"started": timestamp(),
"vendor_writes": 0,
"pair_calls": 0,
"notify_subscriptions": 0,
"state": "running",
}
try:
raw = sys.stdin.buffer.read(4097)
if len(raw) > 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())
@@ -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)))
+2 -1
View File
@@ -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)
@@ -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")
@@ -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)
@@ -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()