Skip to content

Development Guide

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

Development Guide

This page covers everything you need to hack on ankerctl itself: prerequisites, code generation, testing, coding conventions, and the contribution workflow.

Prerequisites

  • Python 3.10 or newer
  • git (and optionally transwarp for code generation)
  • ffmpeg (only required if you want to test the timelapse feature)
  • A real or simulated AnkerMake M5 printer (for end-to-end testing)

Getting started

git clone https://github.com/Django1982/ankermake-m5-protocol.git
cd ankermake-m5-protocol

# Runtime dependencies
pip install -r requirements.txt

# Development extras (testing, lint helpers)
pip install -r requirements-dev.txt

# Optional — required only if you regenerate protocol code
make install-tools

The codebase has no enforced formatter or linter — keep diffs minimal and follow the surrounding style.

Repository tour

See Architecture → Repository tour for the annotated tree. Key folders:

Folder Contents
cli/ CLI command implementations (Click)
web/ Flask app, routes, services
libflagship/ MQTT, PPPP, HTTP protocol clients
static/ HTML/JS/CSS frontend
specification/ .stf protocol specs (input to codegen)
templates/ Jinja2 codegen templates
tests/ pytest test suite
examples/ Standalone protocol experiments

Build and run

ankerctl is pure Python — no compilation step. Run it directly:

./ankerctl.py webserver run
./ankerctl.py mqtt monitor
./ankerctl.py pppp lan-search

For Docker development:

docker build \
    --build-arg UID=$(id -u) \
    --build-arg GID=$(id -g) \
    -t django01982/ankerctl:local .

docker compose up -d --force-recreate

--force-recreate is needed after a local rebuild — Compose otherwise keeps the previous container.

Code generation

Files marked "DO NOT EDIT MANUALLY" are auto-generated by transwarp from .stf specs:

  • libflagship/mqtt.py
  • libflagship/pppp.py
  • libflagship/amtypes.py
  • static/libflagship.js

To modify them, edit the source instead:

File to edit Affects
specification/mqtt.stf MQTT message types
specification/pppp.stf PPPP packet types
specification/amtypes.stf Common AnkerMake types
templates/python/*.j2 Python code generation
templates/javascript/*.j2 JavaScript code generation

Then run:

make diff       # preview changes
make update     # apply generated changes
make clean      # remove __pycache__ etc.

make install-tools clones the transwarp git submodule and pip installs it.

Tests

The test suite uses pytest and lives in tests/. Settings come from pyproject.toml.

# Run all tests
pytest

# Run a single file
pytest tests/test_protocol_pppp.py

# Run a single test by name
pytest -k test_open_lan_binds_fixed_port

# With coverage
pytest --cov=cli --cov=libflagship --cov=web

# Verbose output
pytest -v

Test files cover:

  • tests/test_protocol_*.py — MQTT, PPPP, crypto
  • tests/test_cli_*.py — Click CLI commands
  • tests/test_*_service.py — background services
  • tests/test_*_api.py — REST endpoints
  • tests/test_filament_*.py, tests/test_print_history.py — SQLite stores
  • tests/conftest.py — shared fixtures (e.g. FakeSocket)

Note The frontend (static/) has no automated tests — use the web UI for manual validation.

Coding style

Python

Topic Convention
Indentation 4 spaces
Naming snake_case for functions/variables, CapWords for classes
Imports Stdlib first, then third-party, then local
Logging log = logging.getLogger(__name__); use log.info/warning/error/critical. Named loggers (mqtt, web, history, timelapse, homeassistant) write to separate files when ANKERCTL_LOG_DIR is set.
CLI Click decorators; follow patterns in ankerctl.py

JavaScript

  • Follow existing style in static/ankersrv.js
  • Use Cash.js (lightweight jQuery alternative) for DOM manipulation
  • Avoid introducing new frameworks

General

  • No enforced formatter / linter — keep diffs minimal and readable
  • Prefer editing existing files over creating new ones
  • Match the surrounding module layout

Common patterns

Adding a CLI command

# In cli/<group>.py
@<group>.command("subcommand")
@click.argument("arg", required=True)
@click.option("--flag", "-f", is_flag=True, help="Description")
@pass_env
def <group>_subcommand(env, arg, flag):
    """Docstring becomes help text."""
    # Implementation

@pass_env injects the shared env object: env.config, env.printer_index, env.insecure, env.pppp_dump.

Sending an MQTT command

client = cli.mqtt.mqtt_open(env.config, env.printer_index, env.insecure)
cmd = {
    "commandType": MqttMsgType.ZZ_MQTT_CMD_GCODE_COMMAND.value,
    "cmdData": "G28",
    "cmdLen": 3,
}
client.command(cmd)
response = client.await_response(MqttMsgType.ZZ_MQTT_CMD_GCODE_COMMAND)

Long G-code responses

# Collects all response packets within a time window
msgs = cli.mqtt.mqtt_gcode_dump(client, "M420 V", collect_window=4.0)
combined = "\n".join(msg.get("resData", "") for msg in msgs)

PPPP file transfer

api = cli.pppp.pppp_open(env.config, env.printer_index, dumpfile=env.pppp_dump)
fui = FileUploadInfo.from_data(data, filename, user_name="ankerctl", ...)
cli.pppp.pppp_send_file(api, fui, data, rate_limit_mbps=rate_limit_mbps)
api.aabb_request(b"", frametype=FileTransfer.END)   # start the print
api.stop()

Adding a Flask route

  1. Add the function in web/__init__.py
  2. If it modifies state → use POST / DELETE (auth enforced automatically)
  3. If it is a GET that should require auth → add the path to _PROTECTED_GET_PATHS
  4. If it is a debug-only route → add inside the ANKERCTL_DEV_MODE block at the bottom

Adding a background service

  1. Create web/service/<name>.py inheriting from Service (in web/lib/service.py)
  2. Implement worker_init(), worker_start(), worker_run(timeout), worker_stop()
  3. Register via app.svc.register("<name>", MyService()) in register_services()
  4. Access from routes with with app.svc.borrow("<name>") as svc:

Modifying protocol definitions

  1. Edit specification/*.stf
  2. Modify templates/ if structural changes are needed
  3. Run make diff to preview
  4. Run make update to regenerate
  5. Test with CLI and web UI

Git workflow

  • Branches: create a feature branch (feat/..., fix/..., docs/...) from master
  • Commits: short, descriptive, sentence case; mention the affected area (e.g. Fix PPPP file upload reply handling)
  • Issue refs: include #NN when relevant
  • Atomic: one logical change per commit

Pull requests

PRs should include:

  • A brief summary
  • Testing notes (commands run, manual UI checks)
  • Screenshots for any UI change
  • A CHANGELOG entry for user-facing changes

Release process

A release tag triggers the GitHub Actions workflow that builds and publishes the multi-arch Docker image. CHANGELOG.md must be updated before tagging — the workflow exits with sys.exit(1) if the version section is missing.

Security requirements

  • Never log auth_token, mqtt_key, or api_key
  • Use --insecure / -k only for debugging. Never in production.
  • Never commit login.json or default.json — they contain sensitive tokens
  • The RECOVER_FACTORY MQTT command requires --force as a safety guard
  • Log viewer (/api/debug/logs/<filename>) has path-traversal protection (rejects /, \, ..)
  • External camera URLs are validated against an allowlist (http, https, rtsp, rtmp)
  • Filament text fields are HTML-escaped before storage

Smoke tests

Quick manual validation:

./ankerctl.py mqtt monitor       # MQTT cloud connectivity
./ankerctl.py pppp lan-search    # local network / PPPP discovery
./ankerctl.py webserver run      # web UI on http://localhost:4470

Example scripts in examples/:

python examples/mqtt-connect.py        # standalone MQTT test
python examples/demo-pppp.py           # PPPP packet parsing
python examples/web_login_test.py      # web auth flow
python examples/probe_pppp_cmds.py     # probe undocumented PPPP commands

Diagnosing live issues

Diagnose against a running container, not by reading the source — it saves time and tokens:

docker exec -it ankerctl bash
docker exec ankerctl ./ankerctl.py mqtt gcode-dump "M114"
docker exec ankerctl tail -f /logs/timelapse.log

Available log files in /logs/ (when ANKERCTL_LOG_DIR is set):

  • ankerctl.log (root logger + everything via stdout)
  • mqtt.log, web.log, history.log, timelapse.log, homeassistant.log
  • bed_leveling/YYYYMMDD_HHMMSS.bed — saved bed-grid snapshots

Reference: where to look in the source

For a guided reading order:

  1. ankerctl.py — top-level Click groups
  2. cli/config.py — config file format and login flow
  3. web/__init__.py — every REST and WebSocket route
  4. web/service/mqtt.pyMqttQueue (the largest service)
  5. libflagship/mqttapi.py — MQTT client and encryption
  6. libflagship/ppppapi.py — PPPP UDP framing
  7. documentation/MQTT_COMMANDS.md — message type reference

For the complete annotated index see CLAUDE.md and .claude/agent-memory/INDEX.md.

Contributing

Contributions are welcome — open an issue or PR. For larger features, please discuss the approach in an issue first so we agree on the direction before you invest time in implementation.

Clone this wiki locally