-
-
Notifications
You must be signed in to change notification settings - Fork 5
Protocol Details
ankerctl talks to AnkerMake M5 printers over three protocols. This page documents each one at the level needed to build clients, debug live traffic, or extend the codebase. For the complete generated message-type reference, also see documentation/MQTT_COMMANDS.md.
| Protocol | Transport | Direction | Used for |
|---|---|---|---|
| MQTT | TCP/TLS over the Anker cloud broker (port 8789) |
Bidirectional | Status events, G-code commands, print control |
| PPPP | UDP, peer-to-peer over LAN | Bidirectional | File transfer, camera stream, light control |
| HTTPS | TCP/TLS to Anker cloud APIs | Client → Server | Login (ECDH), printer list, security codes |
- Broker: Anker's MQTT broker, region-dependent (resolved from config — US / EU / CN)
-
Port:
8789(TLS) -
Client ID:
user_idfrom config -
Credentials:
user_idas username,auth_tokenas password -
Custom CA: pin via
--mqtt-ca-cert/ANKERCTL_MQTT_CA_CERT(used by the offline / self-hosted Mosquitto stack)
| Direction | Topic | Purpose |
|---|---|---|
| Subscribe | /phone/maker/{SN}/notice |
Printer status events (most traffic) |
| Subscribe | /phone/maker/{SN}/command/reply |
Command responses |
| Subscribe | /phone/maker/{SN}/query/reply |
Query responses |
| Publish | /device/maker/{SN}/command |
Send commands |
| Publish | /device/maker/{SN}/query |
Send queries |
{SN} is the printer serial number (e.g. AK7ABC0123401234).
Note The broker ACL prevents subscribing to
/device/maker/{SN}/commanddirectly, so you cannot observe what the official mobile app sends.
Each MQTT message body is:
- AES-256-CBC encrypted JSON payload
- XOR checksum appended after encryption
Encryption parameters:
| Field | Value |
|---|---|
| Algorithm | AES-256-CBC |
| Key | 32-byte per-printer key (mqtt_key in default.json, hex-encoded) |
| IV | Fixed string 3DPrintAnkerMake (16 bytes) |
| Padding | PKCS7 |
The fixed IV is a protocol requirement, not a security choice.
For the full enum see libflagship/mqtt.py (auto-generated from specification/mqtt.stf). Highlights:
| Constant | ct |
Direction | Description |
|---|---|---|---|
ZZ_MQTT_CMD_GCODE_COMMAND |
1043 | Command | Send raw G-code |
ZZ_MQTT_CMD_PRINT_CONTROL |
1008 | Command | Pause/Resume/Stop/Restart |
ZZ_MQTT_CMD_AUTO_LEVELING |
1007 | Command | Start G29 |
ZZ_MQTT_CMD_FIRMWARE_VERSION |
1002 | Query | Read firmware (currVer field in reply) |
Empirically verified:
| Value | Action |
|---|---|
0 |
Restart print from beginning (not stop!) |
1 |
Invalid (returns reply=6) |
2 |
Pause |
3 |
Resume |
4 |
Stop / cancel |
5 |
Untested |
reply=0 means "command received" — not necessarily executed. reply=6 means "invalid command".
ct |
Field of interest | Meaning |
|---|---|---|
| 1000 | value |
State machine (see below) |
| 1001 |
progress (0–10000), realSpeed, time, filename
|
Print progress |
| 1003 |
currentTemp, targetTemp (1/100 °C) |
Nozzle temperature |
| 1004 |
currentTemp, targetTemp (1/100 °C) |
Bed temperature |
| 1006 | value |
Fan speed |
| 1007 | value |
Auto-level probe progress (50 total: 1 center + 7×7) |
| 1026 | — |
move_zero (G28 home sequence event) |
| 1044 | path |
Filename at print start |
| 1052 |
real_print_layer, total_layer
|
Layer counts (do not use for % progress) |
value |
Meaning |
|---|---|
| 0 | Print ended (normal) / Idle |
| 1 | Printing (heartbeat every ~3 s) |
| 2 | Print paused (also confirmed by ct=1008 value=2) |
| 4 | Status query reply (Idle) |
| 8 | G28 / calibration phase (pre-print sequence) or touchscreen abort |
ct=1001 progress is on a 0–10000 scale.
- The web frontend divides by 100 to render 0–100% — no server-side normalization.
- The Apprise notifier uses
_normalize_progress()to compute event triggers. -
APPRISE_PROGRESS_MAX=0(default) auto-detects the scale; set to100or10000if your printer firmware deviates.
ct=1052 provides real_print_layer and total_layer. Many slicers under-report layer count due to Z-move-based layer detection vs. the printer's variable-layer-height counting.
ankerctl extracts the slicer-declared layer count from the G-code header at upload time (extract_layer_count() in cli/util.py) and stores it as MqttQueue._gcode_layer_count. When ct=1052 arrives, total_layer is overridden before forwarding to the WebSocket. Supported header formats:
- OrcaSlicer:
;LAYER_COUNT:N - Bambu Studio:
; total layer number: N - PrusaSlicer: count of
;LAYER_CHANGElines
PPPP (Peer-to-Peer Protocol) is an asymmetric UDP protocol for LAN communication. A single UDP socket carries 8 logical channels for control, file transfer, video, and light control.
-
Discovery: broadcast
LanSearchto UDP255.255.255.255:32108 -
Handshake: printer replies with
PunchPkt; the API returnsConnectedonce the punch succeeds -
Session: all subsequent traffic flows over the same socket on UDP
32108for LAN mode (32100for cloud relay)
Key identifiers:
- DUID — Device Unique ID (e.g.
EUPRAKM-001234-ABCDE) - Seed — derived from DUID prefix (e.g.
EUPRAKM)
ankerctl binds the LAN sockets to a fixed local UDP port 32108 before the first sendto, so the printer's UDP responses reach a predictable port. This makes a single static ufw rule sufficient.
| Socket | Created by | Local bind | Remote target |
|---|---|---|---|
| LAN session | open_lan |
32108 |
<printer-ip>:32108 |
| LAN discovery | open_broadcast |
32108 |
255.255.255.255:32108 |
| WAN / cloud | open_wan |
(ephemeral) | <cloud-relay>:32100 |
The WAN socket stays ephemeral so it does not collide with the LAN socket and so cloud NAT traversal continues to work. See Firewall ufw for the full background.
| Channel | Purpose |
|---|---|
| 0 | Control (Xzyh frames: JSON commands, G-code responses) |
| 1 | File transfer (Aabb frames: G-code upload with CRC-32) |
| 2-7 | Reserved |
Xzyh — 16-byte header + payload, used on channel 0/1 for:
- Video stream (H.264 NAL units)
- JSON command / response
- Light control
Aabb — file transfer frames with CRC-32 integrity check:
- G-code file upload
- Progress callback
Data uses a sliding window protocol:
- In-flight window: max 64 packets
- Retransmission timeout: 0.5 seconds
- Sequence numbers: 16-bit wraparound (
CyclicU16)
PPPP uses its own encryption layer separate from MQTT (see libflagship/megajank.py):
-
crypto_curse/crypto_decurse— shuffle-table-based obfuscation -
simple_encrypt/simple_decrypt— XOR-based with seedSSD@cs2-network. - Initstring decoder for connection parameters
./ankerctl.py pppp lan-searchOutput (truncated):
DUID: EUPRAKM-001234-ABCDE
IP: 192.168.1.42
Signal: excellent
Firmware: V3.3.20_3.1.25
If discovery hangs, check Firewall ufw.
Login uses Elliptic Curve Diffie-Hellman key exchange:
- Generate an ephemeral EC keypair (curve secp256r1)
- Derive a shared secret using Anker's public key
- Encrypt the password with the derived AES key
- Send the login request with the encrypted password and the public key
Anker EC public key (secp256r1):
X: C5C00C4F8D1197CC7C3167C52BF7ACB054D722F0EF08DCD7E0883236E0D72A38
Y: 68D9750CB47FA4619248F3D83F0F662671DADC6E2D31C2F41DB0161651C7C076
| Class | Purpose |
|---|---|
PassportApiV1 |
User profile |
PassportApiV2 |
Login (ECDH) |
AppApiV1 |
Printer list, DSK keys |
HubApiV1 / HubApiV2
|
Device info, OTA, P2P connect |
-
Gtoken— MD5 hash ofuser_id - Standard auth headers with
auth_token
The client measures TCP connect time to multiple regional API hosts (US, EU, CN) and selects the fastest one. Re-running config import or config login updates the stored region.
-
Never log
auth_token,mqtt_key, orapi_key. Theconfig showcommand redacts them. - The MQTT IV is fixed by protocol — not a key-management choice. Treat the per-printer
mqtt_keyas the only secret. - PPPP is LAN-only by design. The
open_wancloud-relay path exists but is rarely needed. - All crypto operations use constant-time comparisons where applicable.
- Config files contain sensitive tokens — never commit
default.jsonorlogin.json. - The
--insecure/-kflag disables TLS verification. Use only for debugging, never in production.
# Capture all PPPP traffic for offline analysis
./ankerctl.py --pppp-dump pppp.bin pppp lan-search
python examples/demo-pppp.py pppp.bin
# Watch decrypted MQTT traffic
./ankerctl.py mqtt monitor
# Send a single G-code and collect the full ring-buffer response
./ankerctl.py mqtt gcode-dump --window 5 "M420 V"For example scripts that exercise individual protocol pieces, see examples/ (mqtt-connect.py, demo-pppp.py, web_login_test.py, probe_pppp_cmds.py).
- Tested firmware:
V3.3.20_3.1.25(queried viaZZ_MQTT_CMD_FIRMWARE_VERSION) - The Anker firmware is a fork of Marlin with custom commands stripped:
-
M115,M119— disabled (return "Unknown command") -
M503— returns onlyok(EEPROM dump removed) -
M2001/M2002— Anker-custom, may answer on a different MQTT channel
-
- Ring buffer:
+ringbuf:WRITE_POS,328,READ_FLAG— 328-byte circular buffer appended to every G-code response -
resLenis variable, not always 32 bytes.M420 VreturnsresLen=320.