Add bounded X4 Bluetooth wake and record Android recovery proof
This commit is contained in:
@@ -1044,3 +1044,218 @@ MISSIONCOR-77 синхронизирован через официальный O
|
|||||||
прежние блоки/checker, добавлены BLE01–03/B12/POWER03 и исправлен устаревший
|
прежние блоки/checker, добавлены BLE01–03/B12/POWER03 и исправлен устаревший
|
||||||
текст о невозможности SDK reopen. Предыдущие transport timeouts преодолены;
|
текст о невозможности SDK reopen. Предыдущие transport timeouts преодолены;
|
||||||
автоматический wake/replug/preview checker остаётся открытым.
|
автоматический 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 не добавляли.
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
# Insta360 X4 — состояние реализации 10.09.2026
|
# 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
|
## POWER03/BLE03 — пробуждение кнопкой и первый BLE candidate
|
||||||
|
|
||||||
Одиночное короткое Power при уже вставленном USB восстановило Android без
|
Одиночное короткое Power при уже вставленном USB восстановило Android без
|
||||||
@@ -11,8 +50,8 @@
|
|||||||
|
|
||||||
BLE03 scan20s нашёл один свежий BE80 candidate с именем по USB serial suffix;
|
BLE03 scan20s нашёл один свежий BE80 candidate с именем по USB serial suffix;
|
||||||
полный USB serial hash совпал с прежним instance, USB2e1a:0002/speed5000.
|
полный USB serial hash совпал с прежним instance, USB2e1a:0002/speed5000.
|
||||||
GATT identity/auth не проверялись. Следующий шаг — состояние Bluetooth Wakeup
|
GATT identity/auth в BLE03 не проверялись. Последующие BLE04/POWER04 описаны
|
||||||
и отдельный адресный wake artifact; активный SDK owner не перехватывается.
|
выше; активный SDK owner не перехватывается.
|
||||||
|
|
||||||
## BLE01/BLE02 — первый модуль беспроводной диагностики
|
## BLE01/BLE02 — первый модуль беспроводной диагностики
|
||||||
|
|
||||||
@@ -21,7 +60,9 @@ discovery, стандартное GATT inspection и отдельное чтен
|
|||||||
Те же модули и OS dependencies включены в исходники model package0.1.3-4.
|
Те же модули и OS dependencies включены в исходники model package0.1.3-4.
|
||||||
На Ubuntu initial scan20s и последующий scan60s не обнаружили X4; владелец
|
На Ubuntu initial scan20s и последующий scan60s не обнаружили X4; владелец
|
||||||
сообщил о тёмном экране. Позднее короткое Power подтверждено в POWER03 выше.
|
сообщил о тёмном экране. Позднее короткое 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 ещё не обновлялся.
|
Installed Node0.8.19 / X40.1.3-3 сохранены; bundled Node profile ещё не обновлялся.
|
||||||
Автовозврат Android остаётся открытым; нельзя считать успешную сборку его приёмкой.
|
Автовозврат Android остаётся открытым; нельзя считать успешную сборку его приёмкой.
|
||||||
|
|
||||||
|
|||||||
@@ -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:** ближайший приоритет после применения
|
**Уточнение владельца10.09 после NET03:** ближайший приоритет после применения
|
||||||
B11 — автономность подключения и питание. Проверить сохранение Android-режима,
|
B11 — автономность подключения и питание. Проверить сохранение Android-режима,
|
||||||
replug/cold start, автоматическое восстановление SDK и просмотра; определить
|
replug/cold start, автоматическое восстановление SDK и просмотра; определить
|
||||||
|
|||||||
@@ -6,8 +6,19 @@
|
|||||||
существующего Ubuntu Mac mini. Ниже разделены выполненные проверки и оставшаяся
|
существующего 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)
|
**Уточнение RESEARCH01:** [исследование Android reconnect](12_INSTA360_X4_ANDROID_RECONNECT_RESEARCH.md)
|
||||||
выявило внутренний udisk_mode и независимый BLE remote off/on. Ближайший шаг —
|
выявило внутренний udisk_mode и независимый BLE remote off/on. На том этапе —
|
||||||
проверка беспроводного управления и чтение настройки у точной X4 через versioned
|
проверка беспроводного управления и чтение настройки у точной X4 через versioned
|
||||||
artifact. Отсутствие VBUS switching в PORT01 не доказывает невозможность
|
artifact. Отсутствие VBUS switching в PORT01 не доказывает невозможность
|
||||||
программного восстановления камеры; внешний хаб остаётся альтернативной веткой.
|
программного восстановления камеры; внешний хаб остаётся альтернативной веткой.
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
[исследования reconnect](12_INSTA360_X4_ANDROID_RECONNECT_RESEARCH.md).
|
[исследования reconnect](12_INSTA360_X4_ANDROID_RECONNECT_RESEARCH.md).
|
||||||
Автоматический возврат Android ещё не принят.
|
Автоматический возврат 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, включая
|
Финальный B12-R2: пакет 0.1.3-4 собран на Ubuntu, 53 tests passed, включая
|
||||||
15 BLE cases. Проверены реальные содержимое .deb, зависимости, cold imports
|
15 BLE cases. Проверены реальные содержимое .deb, зависимости, cold imports
|
||||||
и статическая ELF closure. SHA-256 пакета:
|
и статическая ELF closure. SHA-256 пакета:
|
||||||
@@ -16,8 +25,9 @@
|
|||||||
отдельное GATT inspection и явный режим чтения настроек.
|
отдельное GATT inspection и явный режим чтения настроек.
|
||||||
- `ble_options.py`: только GET_OPTIONS, строгие packet/protobuf bounds и
|
- `ble_options.py`: только GET_OPTIONS, строгие packet/protobuf bounds и
|
||||||
разбор ответов. Нет generic send, SET_OPTIONS, capture, reboot или power-off.
|
разбор ответов. Нет generic send, SET_OPTIONS, capture, reboot или power-off.
|
||||||
- `build_ble_diagnostic.py`: артефакт с hashes обоих модулей; тот же код
|
- `build_ble_diagnostic.py`: артефакт с hashes трёх модулей; diagnostic —
|
||||||
включён в model Debian package 0.1.3-4 через `build_deb.py`.
|
обычный entrypoint, wake выбирается явно через `--wake`. Тот же код
|
||||||
|
включён в model Debian package 0.1.3-6 через `build_deb.py`.
|
||||||
- `check_package_entry.py`: проверяет наличие/совпадение модулей в реальном
|
- `check_package_entry.py`: проверяет наличие/совпадение модулей в реальном
|
||||||
.deb, объявленные зависимости и synthetic tests.
|
.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)
|
wake artifact. [Официальная X4 FAQ](https://onlinemanual.insta360.com/x4/en-us/operating-tutorials/connect/connect-app)
|
||||||
требует включённую настройку Settings → General → Bluetooth Wakeup (если
|
требует включённую настройку Settings → General → Bluetooth Wakeup (если
|
||||||
General отсутствует — непосредственно в Settings). Описанная там поддержка
|
General отсутствует — непосредственно в Settings). Описанная там поддержка
|
||||||
приложения iOS не является приёмкой Linux эмуляции. Значение настройки нашей
|
приложения iOS не является приёмкой Linux эмуляции. Позднее владелец включил
|
||||||
камеры пока unknown. Ни выключение, ни wake advertisement не выполнялись.
|
Bluetooth Wakeup и явно подтвердил On. Это camera-side ручная предпосылка,
|
||||||
|
не действие Ubuntu installer. Wake advertisement агентом ещё не выполнялся.
|
||||||
Исходную точную USB identity использовать из private evidence; не объединять
|
Исходную точную USB identity использовать из private evidence; не объединять
|
||||||
камеры по suffix. GetOptions — отдельная ветка без активного USB SDK owner.
|
камеры по suffix. GetOptions — отдельная ветка без активного USB SDK owner.
|
||||||
|
|
||||||
@@ -137,3 +148,37 @@ Android setter. Если уже ANDROID=1, запись того же значе
|
|||||||
реализованного allowlist. BLE remote off/on требует отдельного артефакта и
|
реализованного allowlist. BLE remote off/on требует отдельного артефакта и
|
||||||
проверенного wake-up до выключения камеры. Ни один из этих исходов пока
|
проверенного wake-up до выключения камеры. Ни один из этих исходов пока
|
||||||
не установлен. Raw reports/hashes/UTC/monotonic — в installation ledger.
|
не установлен. 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 агентом не выполнялись.
|
||||||
|
|||||||
@@ -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: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 отдельно завершился отказом.
|
||||||
@@ -283,6 +283,7 @@ class Inspector:
|
|||||||
self.report["notifications"].append({"hex": raw.hex(), **timestamp()})
|
self.report["notifications"].append({"hex": raw.hex(), **timestamp()})
|
||||||
try:
|
try:
|
||||||
received.extend(reader.feed(raw))
|
received.extend(reader.feed(raw))
|
||||||
|
self.report["heartbeat_packets"] = reader.heartbeats
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
errors.append(str(error))
|
errors.append(str(error))
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class PacketReader:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.buffer = bytearray()
|
self.buffer = bytearray()
|
||||||
|
self.heartbeats = 0
|
||||||
|
|
||||||
def feed(self, chunk):
|
def feed(self, chunk):
|
||||||
if not chunk or len(self.buffer) + len(chunk) > MAX_PACKET * 2:
|
if not chunk or len(self.buffer) + len(chunk) > MAX_PACKET * 2:
|
||||||
@@ -91,12 +92,21 @@ class PacketReader:
|
|||||||
result = []
|
result = []
|
||||||
while len(self.buffer) >= 4:
|
while len(self.buffer) >= 4:
|
||||||
size = struct.unpack_from("<I", self.buffer)[0]
|
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")
|
raise ValueError("Unknown packet framing")
|
||||||
if len(self.buffer) < size:
|
if len(self.buffer) < size:
|
||||||
break
|
break
|
||||||
raw = bytes(self.buffer[:size])
|
raw = bytes(self.buffer[:size])
|
||||||
del 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])
|
_, 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:
|
if kind != 4 or reserved or tail or content != 2 or ident & 0xC0000000 != 0xC0000000:
|
||||||
raise ValueError("Unsupported message header/direction/fragmentation")
|
raise ValueError("Unsupported message header/direction/fragmentation")
|
||||||
|
|||||||
@@ -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 hashlib
|
||||||
import json
|
import json
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
ENTRY = """import hashlib, json, sys, zipfile
|
ENTRY = """import hashlib, json, sys, types, zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
with zipfile.ZipFile(sys.argv[0]) as archive:
|
with zipfile.ZipFile(sys.argv[0]) as archive:
|
||||||
manifest = json.loads(archive.read("manifest.json"))
|
raw = archive.read("manifest.json")
|
||||||
source = archive.read("ble_diagnostic.py")
|
manifest = json.loads(raw)
|
||||||
codec = archive.read("ble_options.py")
|
names = ("ble_options", "ble_diagnostic", "ble_wake")
|
||||||
if hashlib.sha256(source).hexdigest() != manifest["source_sha256"]:
|
modules = {name: archive.read(name + ".py") for name in names}
|
||||||
raise RuntimeError("Diagnostic source hash mismatch")
|
ident = hashlib.sha256(raw).hexdigest()[:24]
|
||||||
if hashlib.sha256(codec).hexdigest() != manifest["codec_sha256"]:
|
entry = manifest["entrypoint"]
|
||||||
raise RuntimeError("Diagnostic codec hash mismatch")
|
if entry not in {"ble_diagnostic", "ble_wake"}:
|
||||||
ident = hashlib.sha256(source + b"\\0" + codec).hexdigest()[:24]
|
raise RuntimeError("Unknown diagnostic entry point")
|
||||||
if Path(sys.argv[0]).name != "mission-core-x4-ble-" + ident + ".pyz":
|
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")
|
raise RuntimeError("Diagnostic artifact name mismatch")
|
||||||
import types
|
for name, source in modules.items():
|
||||||
module = types.ModuleType("ble_options")
|
if hashlib.sha256(source).hexdigest() != manifest["modules"][name]:
|
||||||
exec(compile(codec, "ble_options.py", "exec"), module.__dict__)
|
raise RuntimeError("Diagnostic source hash mismatch")
|
||||||
sys.modules["ble_options"] = module
|
for name, source in modules.items():
|
||||||
exec(compile(source, "ble_diagnostic.py", "exec"), {"__name__": "__main__"})
|
module = types.ModuleType(name)
|
||||||
|
sys.modules[name] = module
|
||||||
|
exec(compile(source, name + ".py", "exec"), module.__dict__)
|
||||||
|
raise SystemExit(sys.modules[entry].main())
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def build():
|
def build(wake=False):
|
||||||
source = (ROOT / "packaging/ble_diagnostic.py").read_bytes()
|
modules = {
|
||||||
codec = (ROOT / "packaging/ble_options.py").read_bytes()
|
name: (ROOT / "packaging" / (name + ".py")).read_bytes()
|
||||||
sha = hashlib.sha256(source).hexdigest()
|
for name in ("ble_options", "ble_diagnostic", "ble_wake")
|
||||||
|
}
|
||||||
manifest = {
|
manifest = {
|
||||||
"schema": "missioncore.insta360.ble-diagnostic-artifact/v1",
|
"schema": "missioncore.insta360.ble-diagnostic-artifact/v2",
|
||||||
"source_sha256": sha,
|
"entrypoint": "ble_wake" if wake else "ble_diagnostic",
|
||||||
"codec_sha256": hashlib.sha256(codec).hexdigest(),
|
"modules": {name: hashlib.sha256(data).hexdigest() for name, data in modules.items()},
|
||||||
"dependencies": ["bluez", "python3-dbus", "python3-gi"],
|
"dependencies": ["bluez", "python3-dbus", "python3-gi"],
|
||||||
"persistent_system_changes": False,
|
"persistent_system_changes": False,
|
||||||
"vendor_commands": [8],
|
"vendor_commands": [] if wake else [8],
|
||||||
"scope": "bounded-discovery-and-explicit-identity-options-read",
|
"scope": "single-addressed-wake-advertisement"
|
||||||
|
if wake
|
||||||
|
else "bounded-discovery-and-explicit-identity-options-read",
|
||||||
}
|
}
|
||||||
ident = hashlib.sha256(source + b"\0" + codec).hexdigest()[:24]
|
raw = json.dumps(manifest, sort_keys=True).encode()
|
||||||
output = ROOT / "build" / ("mission-core-x4-ble-" + ident + ".pyz")
|
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:
|
with output.open("xb") as stream, zipfile.ZipFile(stream, "w") as archive:
|
||||||
for name, data in {
|
for name, data in {
|
||||||
"__main__.py": ENTRY.encode(),
|
"__main__.py": ENTRY.encode(),
|
||||||
"ble_diagnostic.py": source,
|
"manifest.json": raw,
|
||||||
"ble_options.py": codec,
|
**{name + ".py": data for name, data in modules.items()},
|
||||||
"manifest.json": json.dumps(manifest, sort_keys=True).encode(),
|
|
||||||
}.items():
|
}.items():
|
||||||
info = zipfile.ZipInfo(name, date_time=(2026, 9, 10, 0, 0, 0))
|
info = zipfile.ZipInfo(name, date_time=(2026, 9, 10, 0, 0, 0))
|
||||||
info.external_attr = 0o600 << 16
|
info.external_attr = 0o600 << 16
|
||||||
@@ -56,10 +65,12 @@ def build():
|
|||||||
return {
|
return {
|
||||||
"artifact": str(output),
|
"artifact": str(output),
|
||||||
"sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
|
"sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
|
||||||
"source_sha256": sha,
|
|
||||||
"bytes": output.stat().st_size,
|
"bytes": output.stat().st_size,
|
||||||
|
"modules": manifest["modules"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ sys.path.insert(0, str(REPOSITORY / "scripts/packaging"))
|
|||||||
from debian import package # noqa: E402
|
from debian import package # noqa: E402
|
||||||
from fetch_sdk import verify # noqa: E402
|
from fetch_sdk import verify # noqa: E402
|
||||||
|
|
||||||
VERSION = "0.1.3-4"
|
VERSION = "0.1.3-6"
|
||||||
WHEELS = {
|
WHEELS = {
|
||||||
"aiohappyeyeballs",
|
"aiohappyeyeballs",
|
||||||
"aiohttp",
|
"aiohttp",
|
||||||
@@ -159,6 +159,7 @@ def build(output):
|
|||||||
"prepare.py",
|
"prepare.py",
|
||||||
"ble_diagnostic.py",
|
"ble_diagnostic.py",
|
||||||
"ble_options.py",
|
"ble_options.py",
|
||||||
|
"ble_wake.py",
|
||||||
):
|
):
|
||||||
files.append(
|
files.append(
|
||||||
("usr/lib/mission-core-node/insta360/" + name, (PACKAGING / name).read_bytes(), 0o644)
|
("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:
|
with tarfile.open(fileobj=io.BytesIO(data)) as archive:
|
||||||
payload = archive.extractfile("usr/share/mission-core-node/insta360/payload.zip").read()
|
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"))
|
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()
|
packaged = archive.extractfile("usr/lib/mission-core-node/insta360/" + name).read()
|
||||||
if packaged != (ROOT / "packaging" / name).read_bytes():
|
if packaged != (ROOT / "packaging" / name).read_bytes():
|
||||||
raise ValueError("Packaged BLE module differs from the qualified source")
|
raise ValueError("Packaged BLE module differs from the qualified source")
|
||||||
|
|||||||
@@ -53,6 +53,30 @@ class OptionsTests(unittest.TestCase):
|
|||||||
{"acknowledged": True, "present": False, "value": None},
|
{"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):
|
def test_explicit_zero_and_packed_option_list(self):
|
||||||
packet = codec.PacketReader().feed(reply(field(1, b"\x5f") + field(2, field(95, 0))))[0]
|
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)
|
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()
|
||||||
Reference in New Issue
Block a user