Skip to content

Protocol Details

Daniel Heinen edited this page May 9, 2026 · 1 revision

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 overview

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

MQTT

Connection

  • Broker: Anker's MQTT broker, region-dependent (resolved from config — US / EU / CN)
  • Port: 8789 (TLS)
  • Client ID: user_id from config
  • Credentials: user_id as username, auth_token as password
  • Custom CA: pin via --mqtt-ca-cert / ANKERCTL_MQTT_CA_CERT (used by the offline / self-hosted Mosquitto stack)

Topics

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}/command directly, so you cannot observe what the official mobile app sends.

Packet structure

Each MQTT message body is:

  1. AES-256-CBC encrypted JSON payload
  2. 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.

Most-used command types

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)

Print control values (ct=1008)

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".

Notification types

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)

State machine (ct=1000)

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

Progress scale

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 to 100 or 10000 if your printer firmware deviates.

Layer count

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_CHANGE lines

PPPP

Overview

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.

Connection sequence

  1. Discovery: broadcast LanSearch to UDP 255.255.255.255:32108
  2. Handshake: printer replies with PunchPkt; the API returns Connected once the punch succeeds
  3. Session: all subsequent traffic flows over the same socket on UDP 32108 for LAN mode (32100 for cloud relay)

Key identifiers:

  • DUID — Device Unique ID (e.g. EUPRAKM-001234-ABCDE)
  • Seed — derived from DUID prefix (e.g. EUPRAKM)

Local socket binding (since v1.10.9)

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.

Channels

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

Frame types

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

DRW pipelining

Data uses a sliding window protocol:

  • In-flight window: max 64 packets
  • Retransmission timeout: 0.5 seconds
  • Sequence numbers: 16-bit wraparound (CyclicU16)

PPPP crypto

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 seed SSD@cs2-network.
  • Initstring decoder for connection parameters

LAN search example

./ankerctl.py pppp lan-search

Output (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.

HTTP API (Anker Cloud)

Authentication (ECDH)

Login uses Elliptic Curve Diffie-Hellman key exchange:

  1. Generate an ephemeral EC keypair (curve secp256r1)
  2. Derive a shared secret using Anker's public key
  3. Encrypt the password with the derived AES key
  4. Send the login request with the encrypted password and the public key

Anker EC public key (secp256r1):

X: C5C00C4F8D1197CC7C3167C52BF7ACB054D722F0EF08DCD7E0883236E0D72A38
Y: 68D9750CB47FA4619248F3D83F0F662671DADC6E2D31C2F41DB0161651C7C076

API classes

Class Purpose
PassportApiV1 User profile
PassportApiV2 Login (ECDH)
AppApiV1 Printer list, DSK keys
HubApiV1 / HubApiV2 Device info, OTA, P2P connect

Headers

  • Gtoken — MD5 hash of user_id
  • Standard auth headers with auth_token

Region detection

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.

Security considerations

  • Never log auth_token, mqtt_key, or api_key. The config show command redacts them.
  • The MQTT IV is fixed by protocol — not a key-management choice. Treat the per-printer mqtt_key as the only secret.
  • PPPP is LAN-only by design. The open_wan cloud-relay path exists but is rarely needed.
  • All crypto operations use constant-time comparisons where applicable.
  • Config files contain sensitive tokens — never commit default.json or login.json.
  • The --insecure / -k flag disables TLS verification. Use only for debugging, never in production.

Live debugging

# 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).

Firmware notes

  • Tested firmware: V3.3.20_3.1.25 (queried via ZZ_MQTT_CMD_FIRMWARE_VERSION)
  • The Anker firmware is a fork of Marlin with custom commands stripped:
    • M115, M119 — disabled (return "Unknown command")
    • M503 — returns only ok (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
  • resLen is variable, not always 32 bytes. M420 V returns resLen=320.

Clone this wiki locally