feat: prove and decode K1 realtime MQTT streams

This commit is contained in:
DCCONSTRUCTIONS
2026-07-15 19:10:41 +03:00
parent faa442eefc
commit 6b22e5a1d2
30 changed files with 4375 additions and 41 deletions
+34 -24
View File
@@ -1,14 +1,19 @@
# Technical audit
Status: pre-production planning, 2026-07-15.
Status: live feasibility validated, 2026-07-15.
## Executive finding
The project is feasible as a disciplined black-box investigation, but the
existence of a complete K1-to-Mac realtime link is not yet established. The
first hard problem is not decoding points or images. It is bootstrapping the K1
from BLE availability into Wi-Fi association and then opening the proprietary
application data session without LixelGO.
The K1-to-Mac realtime link is feasible and has been demonstrated without
LixelGO. The tested Mac provisioned firmware 3.0.2 over BLE, confirmed K1 LAN
association, opened the device's MQTT 3.1.1 service on TCP 1883, captured a
physical-button scan and decoded both point-cloud and pose reports.
All 1,140 captured `lio_pcl` frames decoded as raw-LZ4 protobuf blocks, yielding
4,165,862 points. All 1,215 `lio_pose` messages decoded, and the resulting
approximately 1.566 m displacement matched the controlled movement. The
remaining feasibility question is the panoramic camera branch, not the core
realtime LiDAR/pose path.
The supplied Bible is useful as an OSINT dossier. It is not an executable plan
for the actual stand because many experiments assume a phone and LixelGO. The
@@ -31,16 +36,16 @@ writes.
Each of these remains scoped to the actual firmware/hardware state observed in
the lab. The physical unit is authoritative.
### Strong hypotheses
### Resolved hypotheses
- BLE is the bootstrap/control plane and Wi-Fi is the likely high-rate data
plane.
- The user-provided SSID and PSK are transported inside a vendor-defined GATT
protocol.
- The application may need a second token, certificate, handshake, or stream
subscription after ordinary Wi-Fi association.
- The externally exposed point cloud is more likely a processed/downsampled
preview than raw LiDAR packets.
- BLE is the bootstrap/provisioning plane and Wi-Fi/MQTT is the high-rate data
plane on the tested firmware.
- SSID and PSK use a verified fixed 99-byte GATT frame; the hidden credential
path never persists the password.
- The report subscriptions require no second MQTT credential, certificate or
application publish handshake on the direct LAN path.
- The external `lio_pcl` stream is a processed point cloud rather than raw
LiDAR packets.
XGRIDS provides stronger product evidence for an external point-cloud path than
the local LAS alone: current LixelStudio materials describe K1 remote control
@@ -49,18 +54,18 @@ and this does not disclose or guarantee access to the protocol from macOS. It
does, however, justify keeping realtime point cloud as the primary stream target.
No comparable official evidence proves an exportable raw panorama/camera stream.
### Unsupported until measured
### Still unsupported until measured
- K1 remembers an existing Wi-Fi profile.
- K1 exposes its own access point.
- Provisioning fields are plain UTF-8, JSON, TLV, CBOR, or protobuf.
- A writable GATT characteristic can be used without bonding or an app token.
- Local `map.las` layout resembles the network stream.
- Live pose or camera frames leave the device.
- A point stream contains simple float32 XYZ tuples.
- Network payloads are unencrypted.
- Raw or stitched panoramic camera frames leave the device.
- The upper 24 bits of point `rgbi` are usable packed RGB.
- MQTT application commands can be published safely without first reproducing
device/session header state and response handling.
## Major corrections to the source plan
## Initial corrections to the source plan
These corrections governed the safe experiment. The resolved outcomes above
and the verified profiles now supersede their pre-lab uncertainty.
1. Experiments requiring LixelGO are removed from the active critical path.
There will be no app-session capture, provisioning diff, app-start comparison,
@@ -142,6 +147,11 @@ device baseline
-> point cloud / pose / status / optional camera decoders
```
The chain through point-cloud/pose decoding is now complete. See the
[MQTT stream profile](05_K1_MQTT_STREAM_PROFILE.md) and
[redacted lab report](lab/001_K1_LIVE_MQTT_20260715.redacted.md) for measured
formats, counts and artifact hashes.
## Verified primary references
- [XGRIDS K1 firmware and release notes](https://www.xgrids.com/intl/support/download?page=K1)
+18
View File
@@ -3,6 +3,24 @@
This plan supersedes the app-dependent experiment order in the reference Bible.
Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
## Live checkpoint — 2026-07-15
| Stage | Result |
| --- | --- |
| Stage 0 host/repository | GO — isolated Python 3.12 environment and private Git |
| Gate 1 physical operation | GO — autonomous double-click start/stop verified |
| Stage 1 BLE discovery | GO — repeatable advertisement and GATT profile |
| Stage 2 BLE provisioning | GO — reviewed 99-byte profile, LAN association confirmed |
| Stage 3 application session | GO — MQTT 3.1.1 on confirmed K1 TCP 1883 |
| Stage 4 artifacts/flows | GO — bounded capture, hashes and negative control |
| Stage 5 point cloud | GO — raw-LZ4 protobuf, 1,140 live frames decoded |
| Stage 5 pose | GO — 1,215 live frames decoded and motion-correlated |
| Stage 5 camera | PAUSE — no independent frame/video stream observed |
USB project copying remains optional ground truth rather than a blocker for the
now-verified network path. MQTT control publishing remains deliberately deferred
because the physical button is a known-safe start/stop mechanism.
## Stage 0 — repository and host baseline
Deliverables:
+107
View File
@@ -0,0 +1,107 @@
# Reviewed K1 Wi-Fi provisioning profile
Profile ID: `xgrids-k1-fw3-wifi-v1`
Status: reviewed for one controlled write against the owner-controlled K1. This
profile is not a generic XGRIDS protocol claim and must not be used for fuzzing.
## Evidence and target
- Observed device firmware: 3.0.2.
- Live metadata-only GATT discovery exposes service `7f00`, read/write
characteristic `7f01`, and read characteristic `7f02`.
- Static analysis of the official Android application's production path maps
its service field to `7f00`, discovered write field to `7f01`, and status/read
field to `7f02`.
- The application sends operator-supplied router credentials; it does not derive
them from the K1 and it does not require the password of the K1's own AP.
Full UUIDs:
- service: `00007f00-0000-1000-8000-00805f9b34fb`;
- write: `00007f01-0000-1000-8000-00805f9b34fb`;
- status/read: `00007f02-0000-1000-8000-00805f9b34fb`.
## Request frame
The application writes exactly 99 bytes:
| Offset | Length | Meaning |
| --- | ---: | --- |
| 0 | 1 | SSID UTF-8 byte length, 1 through 32 |
| 1 | 32 | SSID UTF-8 bytes followed by zero padding |
| 33 | 1 | password UTF-8 byte length, 1 through 64 |
| 34 | 64 | password UTF-8 bytes followed by zero padding |
| 98 | 1 | zero |
There is no checksum, nonce, token, certificate, signature, or separate commit
command in this production call path. The implementation must never print,
persist, or accept the password as a command-line argument.
The Android application unequivocally requests a write without response and
negotiates MTU 120, making the 99-byte frame one ATT command. A live read-only
CoreBluetooth check reports MTU 256 and a maximum write-without-response size of
253, so the same frame also fits one command on this Mac.
CoreBluetooth metadata advertises `write`, not `write-without-response`, despite
the official application forcing the latter. The macOS tool exposes that
discrepancy: `auto` follows advertised properties, while the explicit
`without_response` mode mirrors the application only after checking the live
maximum is at least 99. The first controlled experiment uses that explicit
mode. It never fragments or retries the payload automatically.
## Expected transition and evidence of acceptance
A completed GATT write only proves transport completion. It does not prove that
the K1 joined Wi-Fi. The application polls `7f02`; the observed response frame
contains a fixed-width mode slot, an address slot, and a status byte at offset
50. The current AP baseline reports mode `WIFI_AP` and address `192.168.56.1`.
For the controlled experiment, acceptance required at least one of:
1. `7f02` reports a non-AP IPv4 address;
2. the same address appears as a new router/ARP client after the write;
3. a statically evidenced K1 application endpoint is reached at that confirmed
address.
Do not infer success from a write callback alone.
## Safety, recovery and stop conditions
- Perform one write per explicitly named attempt, using credentials for the LAN
already used by the Mac. Never retry automatically.
- Do not alter Deco settings, scan the subnet, or guess any credential.
- If the status does not change, do not retry automatically.
- If the supplied credentials are wrong, reconnect over BLE and overwrite them
with corrected credentials using the same reviewed profile.
- A single normal power cycle is allowed as a recovery check. It is not claimed
to erase stored credentials. Factory reset and firmware actions are prohibited.
- Stop on repeated disconnects, an unexpected GATT layout, overheating, an
activation prompt, or any device fault indication.
The exact factory-return command is not known. That limitation is accepted for
the first write because BLE remains the documented configuration transport and
the operator controls the intended LAN credentials.
## Live transport attempts
Attempt 01 mirrored the Android application: one 99-byte write without response,
with a live CoreBluetooth maximum of 253 bytes. For 60 seconds afterward, `7f02`
remained at the AP baseline and the existing LAN neighbor table did not change.
There was no automatic retry.
Attempt 02 was separately reviewed because `7f01` explicitly advertises ordinary
write-with-response to CoreBluetooth. The identical 99-byte payload was sent
once using that advertised transport. This tested the transport mismatch only;
it did not change credential values or application framing.
Attempt 02 succeeded immediately. `7f02` reported the operator-selected LAN,
status code 1 and a non-AP private IPv4 address. The existing neighbor table then
confirmed the same new address on the Mac's Wi-Fi interface. No third
provisioning write was made.
Offline analysis of the owned application and a targeted check against only that
confirmed address identified plain MQTT on TCP 1883 as the production
application endpoint. Port 8008 belongs to a separate QuickLink/AP WebSocket
branch and was correctly refused in the bridge/LAN topology. The successful
MQTT result is documented in `05_K1_MQTT_STREAM_PROFILE.md`.
+164
View File
@@ -0,0 +1,164 @@
# Verified K1 MQTT stream profile
Status: verified against one owner-controlled LixelKity K1 running firmware
3.0.2. This is a capture and decoder profile, not a claim of vendor support or a
stable public API.
## Transport
After BLE provisioning, the K1 joins the operator's LAN and exposes:
| Property | Verified value |
| --- | --- |
| Transport | TCP |
| Port | 1883 |
| Application protocol | MQTT 3.1.1 |
| TLS | not used by this direct-LAN path |
| Username/password | not used by the observed client setup |
| K1 address | DHCP/private IPv4, retained only in the ignored session |
The direct route for the confirmed K1 address used the Mac Wi-Fi interface even
while the Mac's default route was a VPN tunnel. Commands must resolve the route
for the confirmed K1 address and must not scan the surrounding subnet.
The repository's capture client is subscribe-only. It sends the minimum MQTT
CONNECT, SUBSCRIBE, keepalive and DISCONNECT protocol packets; it has no reason
to publish an application command.
## Verified report topics
The firmware-3 stream set is:
```text
lixel/application/report/heartbeat
lixel/application/report/device_status
lixel/application/report/lio_pcl
lixel/application/report/lio_pose
lixel/application/report/modeling
```
Additional report topics exist for system errors, modeling status, PGO,
control points and upgrades. The safe discovery subscription is restricted to
`lixel/application/report/#`. It does not subscribe to request topics.
The application also retains compatibility subscribers for:
```text
RealtimePointcloud
RealtimePath
DeviceStatus
```
No legacy stream was emitted by the tested firmware during the live run.
## Firmware-3 point cloud
`lixel/application/report/lio_pcl` contains a protobuf compression envelope:
```proto
message MqttCompressMsg {
Header header = 1;
CompressionType compression = 2; // 0=LZ4, 1=Zstd
uint32 compressed_size = 3; // expected decoded byte count
bytes compressed_data = 4;
}
```
The verified value is compression enum 0. `compressed_data` is a raw LZ4 block,
not an LZ4 frame. The decoder must supply `compressed_size`, enforce the exact
decoded length and reject enum 1 until a bounded Zstd path is reviewed.
The decoded protobuf is:
```proto
message LioPclReport {
Header header = 1;
repeated Point points = 2;
}
message Point {
sint64 x = 1;
sint64 y = 2;
sint64 z = 3;
uint32 rgbi = 4;
}
```
Coordinates are ZigZag `sint64` values. Sensor-native metric coordinates are
`(x/header.scaler, y/header.scaler, z/header.scaler)`; a zero scaler is invalid.
The tested frames consistently used scaler 1000.
The application uses only `rgbi & 0xff` as intensity/alpha. The connector keeps
the complete `uint32`; interpreting the upper 24 bits as RGB remains unverified.
Raw coordinates are preserved. Any Y/Z swap belongs in a derived visualization,
not in capture storage.
## Firmware-3 pose
`lixel/application/report/lio_pose` is an uncompressed protobuf:
```proto
message LioPoseReport {
Header header = 1;
PoseStamped pose = 2;
float distance = 3;
float pose_accuracy = 4;
}
message PoseStamped { sint64 stamp = 1; Pose pose = 2; }
message Pose { Point position = 1; Quaternion orientation = 2; }
message Point { double x = 1; double y = 2; double z = 3; }
message Quaternion { double x = 1; double y = 2; double z = 3; double w = 4; }
```
Storage keeps position `(x,y,z)` and quaternion `(x,y,z,w)` exactly as received.
Non-finite values are rejected. Quaternion normalization, coordinate transforms
and sensor-to-vehicle extrinsics are derived operations and must not overwrite
raw values.
## Legacy formats
`RealtimePointcloud` starts with a 12-byte envelope. Little-endian uint32 at
offset 0 is the point stride. Records contain float32 `x,y,z` at offsets 0,4,8,
RGB bytes at 12,13,14 and, when stride is at least 16, intensity at 15. Envelope
bytes 4 through 11 remain uninterpreted and are retained.
`RealtimePath` contains float32 position `x,y,z` at offsets 0,4,8; offset 12 is
unknown; quaternion wire order is `w,x,y,z` at offsets 16,20,24,28. The decoded
orientation is exposed as `(x,y,z,w)`. Unknown tail fields are retained.
`PrePathArray` is exactly 16 little-endian float64 values.
## Application control boundary
Static analysis identified the application start/stop topic as
`lixel/application/request/modeling`, QoS 2, with a protobuf
`ModelingRequest`. Start action is 1 and stop action is 2. A valid request also
contains a device/session/OpenAPI header and, for start, project/record/scan/mount
settings.
Publishing is deliberately not implemented. The physical double-click provides
a verified autonomous start/stop path and avoids inventing session headers or
changing scan settings. A future publisher requires a separate reviewed profile,
explicit confirmation, response handling and rollback.
## Decoder and capture bounds
Default defensive limits are applied before allocation or iteration:
- 2 MiB maximum MQTT payload;
- 1 MiB maximum compressed block;
- 8 MiB maximum decoded block;
- 64:1 maximum claimed expansion ratio;
- 250,000 points per frame;
- bounded protobuf field counts and exact LZ4 decoded length.
Per-frame failures do not justify firmware writes or speculative recovery.
Always preserve the raw MQTT record and report the decoder error separately.
## Current camera result
No independent camera, panorama, JPEG, H.264 or RTSP topic was observed in this
run. `lio_pcl` may carry intensity or packed color information, but that does not
prove access to the two raw panoramic camera streams. Camera discovery remains a
separate evidence gate.
@@ -0,0 +1,114 @@
{
"schema_version": 1,
"session_id": "20260715T122850Z_live_power_cycle",
"experiment": "BLE provisioning and physical-button MQTT stream capture",
"tool": {
"version": "0.1.0",
"git_commit": "faa442e+working-tree-lab-tooling"
},
"host": {
"macos_version": "26.5.1",
"machine": "arm64",
"python_version": "3.12.13",
"wifi_interface": "en0",
"vpn_active": true
},
"device": {
"model": "XGRIDS/LixelKity K1",
"serial_suffix": null,
"activation_state": "active",
"firmware": "3.0.2"
},
"network": {
"topology": "current_lan",
"k1_address_redacted": "RFC1918 private IPv4"
},
"timeline": [
{
"event": "MQTT standby baseline begins",
"monotonic_seconds": 0.0,
"utc": "2026-07-15T14:05:15.186Z",
"note": "heartbeat and device_status only"
},
{
"event": "first live pose report",
"monotonic_seconds": 57.778,
"utc": "2026-07-15T14:06:12.964Z",
"note": "physical double-click scan start"
},
{
"event": "first live point-cloud report",
"monotonic_seconds": 57.795,
"utc": "2026-07-15T14:06:12.981Z",
"note": "firmware-3 lio_pcl topic"
},
{
"event": "full-payload capture window ends",
"monotonic_seconds": 179.166,
"utc": "2026-07-15T14:08:14.352Z",
"note": "180-second subscriber timeout"
},
{
"event": "post-stop negative control begins",
"monotonic_seconds": 369.254,
"utc": "2026-07-15T14:11:24.440Z",
"note": "solid-green standby; no lio_pcl/lio_pose/modeling reports"
},
{
"event": "repository-native standby smoke capture",
"monotonic_seconds": 2443.564,
"utc": "2026-07-15T14:45:58.750Z",
"note": "5-second fixed-topic subscriber; 11 heartbeat/status frames"
}
],
"artifacts": [
{
"path": "reference/LG_i18n_125_20250820.apk",
"size_bytes": 118234630,
"sha256": "c83736f782981282b933cd4737c4f9fa46cbd7f651660c1a9057580f7312361f",
"classification": "sensitive"
},
{
"path": "captures/wifi_provisioning_attempt_01.json",
"size_bytes": 1258,
"sha256": "cc73705c185315286f4009b6619f82a7953b38528a919f7fd2b18ecd788c7637",
"classification": "sensitive"
},
{
"path": "captures/wifi_provisioning_attempt_02.json",
"size_bytes": 1247,
"sha256": "4acf432dd8797802a0033efb7c89f3c11827f681bbf152249c02cccb41d9f873",
"classification": "sensitive"
},
{
"path": "captures/mqtt_scan_full_payloads_03.tsv",
"size_bytes": 79937918,
"sha256": "6b2a4a66d24a89ac048a2be11c5ab45923ee60d4ef07ad3ed3fa3cc508bf8500",
"classification": "sensitive"
},
{
"path": "captures/mqtt_post_stop_metadata_04.tsv",
"size_bytes": 1488,
"sha256": "677835db8175ab6001bec6ecc703413c07795062bb9b4e86c187c77c3e6c73eb",
"classification": "sensitive"
},
{
"path": "captures/native_mqtt_standby_05/mqtt.raw.k1mqtt",
"size_bytes": 1924,
"sha256": "1ff2984d7f305e41f80c13b820405b9b18c8c79c15efe698d51a02fffa22ca82",
"classification": "sensitive"
},
{
"path": "analysis/native_mqtt_standby_05.summary.json",
"size_bytes": 1481,
"sha256": "da72847c9454bd69915c65d49101cb93261d034ec1c1db329ead0b20a6dcbb0e",
"classification": "sensitive"
}
],
"decision": {
"status": "GO",
"gate": "Stage 5 point-cloud and pose decoding",
"reason": "All captured firmware-3 point-cloud and pose frames decoded and correlated with controlled physical motion.",
"next_smallest_experiment": "Longer repository-native capture on verified power bank; keep camera discovery separate."
}
}
@@ -0,0 +1,117 @@
# K1 Lab Report 001 — redacted
Date: 2026-07-15
Decision: **GO** for BLE provisioning, LAN association, MQTT capture, firmware-3
point-cloud decoding and live pose decoding. Camera stream remains unproven.
## Stand
- owner-controlled XGRIDS/LixelKity K1;
- observed firmware 3.0.2;
- Apple Silicon MacBook;
- existing TP-Link mesh LAN;
- no LixelGO, phone, SDK, router reconfiguration or firmware change;
- device identity, SSID, password, IP and MAC retained only in ignored artifacts.
## Physical result
- power-on: slow blue to solid green standby;
- autonomous scan start: physical double-click from standby;
- start transition: fast green, LiDAR rotation, then scanning green;
- initialization: device held still for at least 20 seconds;
- controlled movement: approximately one metre plus a turn;
- stop: physical double-click, fast green while saving, LiDAR stopped, solid green;
- single-click is not the scan-start action;
- a depleted battery caused one clean power loss; the battery was replaced before
the successful run;
- later transfer from battery/USB connection to a verified power bank did not
reset the device or change the standby LED.
## BLE and Wi-Fi result
The reviewed 99-byte firmware-3 provisioning frame was tested twice:
1. write without response, mirroring the Android application: transport
completed but K1 remained in AP mode;
2. write with response, matching the live CoreBluetooth property: immediate
success, status code 1 and a LAN IPv4 address.
There was no automatic retry and no third provisioning write. The password was
entered in a hidden local macOS dialog and never appeared in arguments, logs,
artifacts or Git.
## MQTT result
Targeted connection to the confirmed K1 address accepted plain MQTT 3.1.1 on
TCP 1883 with no username/password. A subscribe-only client received standby
heartbeat and status at approximately 1 Hz.
The 180-second scan capture contained 2,836 valid records and no framing/declared
length errors:
| Topic | Messages | MQTT payload bytes |
| --- | ---: | ---: |
| `device_status` | 180 | 31,830 |
| `heartbeat` | 180 | 17,997 |
| `lio_pcl` | 1,140 | 39,617,777 |
| `lio_pose` | 1,215 | 204,370 |
| `modeling` | 121 | 10,867 |
After physical stop, a separate 12-second negative-control subscription received
only 12 heartbeat and 12 status messages. It received no `lio_pcl`, `lio_pose` or
`modeling` reports.
After implementation, the repository-native `k1link net mqtt-capture` command
was smoke-tested for 5 seconds in standby. It completed a fixed allowlisted
subscription, saved 11 messages in the length-framed `.k1mqtt` format and exited
on duration. `k1link analyze mqtt-streams` then verified the raw capture hash and
all frame boundaries; as expected for standby, it found no PCL or pose frames.
## Decoder result
All 1,140 point-cloud MQTT payloads decoded successfully:
- compression: raw LZ4 block, enum 0 in every frame;
- decoded protobuf size: 37,083 through 55,111 bytes per frame;
- scaler: 1000 in every frame;
- 2,703 through 4,065 points per frame;
- total decoded points: 4,165,862;
- decode failures: 0.
All 1,215 pose payloads decoded successfully:
- protobuf position and quaternion values were finite;
- first-to-last pose displacement was approximately 1.566 m, consistent with
the operator's controlled movement;
- reported pose accuracy was approximately 0.001 throughout;
- decode failures: 0.
Scene bounds, trajectory coordinates and raw payloads remain private and ignored.
## Artifact integrity
The raw files are under the ignored session
`20260715T122850Z_live_power_cycle`. Only redacted facts and hashes are committed.
| Artifact | SHA-256 |
| --- | --- |
| owned reference APK | `c83736f782981282b933cd4737c4f9fa46cbd7f651660c1a9057580f7312361f` |
| provisioning attempt 01 | `cc73705c185315286f4009b6619f82a7953b38528a919f7fd2b18ecd788c7637` |
| provisioning attempt 02 | `4acf432dd8797802a0033efb7c89f3c11827f681bbf152249c02cccb41d9f873` |
| full MQTT scan capture | `6b2a4a66d24a89ac048a2be11c5ab45923ee60d4ef07ad3ed3fa3cc508bf8500` |
| post-stop metadata control | `677835db8175ab6001bec6ecc703413c07795062bb9b4e86c187c77c3e6c73eb` |
| native standby `.k1mqtt` smoke capture | `1ff2984d7f305e41f80c13b820405b9b18c8c79c15efe698d51a02fffa22ca82` |
| native aggregate analysis | `da72847c9454bd69915c65d49101cb93261d034ec1c1db329ead0b20a6dcbb0e` |
## Remaining gates
1. Integrate the bounded stream decoder with the repository-native binary MQTT
capture format.
2. Run a longer power-bank capture and measure packet loss, CPU, disk and thermal
behavior.
3. Establish K1-to-vehicle/airframe extrinsics and time synchronization before
using pose on a robot or UAV.
4. Determine whether usable RGB is packed into `rgbi` or delivered elsewhere.
5. Treat raw panoramic camera access as unproven until a scan-correlated stream
or endpoint is observed.