A dashboard and CLI for keeping a collection of AsteroidOS smartwatches charged and healthy while they sit in a drawer.
If you are an AsteroidOS user you tend to accumulate watches, and their 2014–2018 batteries don't store well. Powered off, a worn cell drains flat within weeks (one "powered off at 100%" is empty months later), and a flat cell often gets stuck in the low-charge reboot loop. Left on a powered dock instead, it sits pegged at 100%, which is also hard on the cell.
This keeps them docked on smart USB hubs and cycles each battery to stay
around 40–80%. Port power is switched by writing the kernel's per-port
disable attribute in sysfs, which stays fast even on deep hub cascades
(uhubctl is used for discovery and as a
fallback).
What it does:
- A web dashboard for the fleet: port power, connection state, OS detection, battery, and running operations, and it tracks watches you move between ports. When a watch drops off the bus its last-known readings stay on screen — marked stale, with a "last live" age — instead of blanking. Every row is compact by design: a battery gauge that fills by charge level, a row of status dots, and a single menu hold all the actions. Click a watch to open a Control Center that reads its telemetry and controls its hardware (WiFi, Bluetooth, screen, buzz-to-find, clock sync, screenshots).
- Works over ADB or SSH. Every watch feature works the same whether a
watch is on ADB or in SSH/developer USB mode — the tool picks whichever wire
answers. You can switch a watch's USB mode from the dashboard, run several
watches on SSH at once (each gets its own address, so they never collide on
the default
192.168.2.15), and set a fleet-wide prefer ADB / prefer SSH policy that quietly keeps stray watches on the mode you chose. - Watch identity, exactly: each row shows the watch's product photo and its
precise hardware codename — even for variants that share a factory image (a
TicWatch E2 reads as
tunny, not theskipjackimage it actually ships), resolved from the screen resolution. Click the photo to see the watch with its live screen composited inside it. - Battery care: charge one or several watches at once to a target rather than on a timer, a sustain job every 12 hours, and a workbench mode that keeps a watch in-band while you work on it over WiFi/SSH.
- Battery triage: standby drain tests with saved history, surfaced as a wearability dot in each row, to see which watches still hold a day of standby and which are battery-swap candidates worth keeping only as dock-powered dev units.
- Fleet flashing: fetch, checksum, and flash AsteroidOS nightlies to every docked watch at once.
- Watches are physically connected to smart USB hubs that support per-port power control.
- The
asteroid-docking-bayCLI switches hub port power (via sysfs, as above;uhubctlfor discovery and as a fallback) and talks to the watches overadb. - A systemd user timer fires every 12 hours, powers each watch on, checks the battery over ADB, charges it to 80% if below 40%, then powers the port back off.
Battery level is read from the watch's power_supply class over ADB shell,
preferring the named hardware fuel gauge (nanohub_fuelgauge-*/capacity)
over the generic battery node where present — on some watches the generic
node is a separate, miscalibrated source. This is the standard Linux
power-supply class; dumpsys and getprop are Android-only and not
available on AsteroidOS.
| Tool | Version | Notes |
|---|---|---|
| Python 3 | ≥ 3.9 | stdlib only; python-systemd optional for journald |
| uhubctl | any recent | for hub power control |
| adb | any | android-tools or android-sdk-platform-tools |
| fastboot | any | required for flash; same package as adb |
| wget | any | required for flash nightly downloads |
| bottle | any recent | optional; required for serve (web UI) |
Arch Linux
sudo pacman -S android-tools
yay -S uhubctl # AUR
sudo pacman -S python-bottle # optional, for the web UIDebian / Ubuntu
sudo apt install uhubctl android-tools-adb
pip install bottle # optional, for the web UIgit clone https://github.com/moWerk/asteroid-docking-bay.git
cd asteroid-docking-bay
./install.shThe binary is installed to ~/.local/bin/asteroid-docking-bay. The systemd
user units go to ~/.config/systemd/user/.
~/.local/bin must be on your PATH for the short form to work. Most
distros add it automatically; if yours doesn't (the installer will warn you):
export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc or ~/.zshrcPrefer containers? See Running in a container for the podman split (unprivileged frontend + host-touching backend).
By default uhubctl requires root. To run without sudo:
-
Find your hub's USB vendor ID:
lsusb | grep -i hub # or uhubctl -l
-
Edit
udev/70-asteroid-docking-bay.rulesto uncomment the line matching your hub's vendor ID. Lenovo (17ef) and Realtek RTS5411 (0bda, common in 16-port hubs) are enabled by default and confirmed to do true per-port VBUS switching. -
Install the rules:
sudo cp udev/70-asteroid-docking-bay.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules && sudo udevadm trigger --action=add # verify (no sudo): uhubctl -l should now list your hub
The rules grant access via
TAG+="uaccess"(active local login seat) andGROUP="users"(for SSH/headless access). If your user is not in theusersgroup, add yourself withsudo usermod -aG users $USERand log back in, or change the group in the rules file to one you are in. Earlier versions usedplugdev, which many distros (Arch, EndeavourOS, Fedora) do not create, so the rule silently did nothing;usersis the safer default.If a hub that is already plugged in does not pick up access after the trigger, unplug and replug its upstream cable once.
The same rules file also grants your
usersgroup write access to each hub port's sysfsdisableattribute, which is what enables near-instant port switching (otherwise the tool falls back to slowuhubctl). The sameudevadm triggerabove applies it. Verify:ls -l /sys/bus/usb/devices/*:1.0/*-port*/disable # group 'users', -rw-rw-r--
On startup the tool logs
Port switching: sysfs (instant)when this is active, oruhubctl fallback (slow)if the attribute is still read-only. -
For ADB access (if not already configured):
# Arch: sudo pacman -S android-udev # Other: copy udev/70-asteroid-docking-bay.rules and uncomment the ADB line
# Step 1: one-time hub setup — discovers hubs and tests per-port power switching
asteroid-docking-bay map
# Step 2: verify everything looks right
asteroid-docking-bay status
# Step 3: enable the automatic charging timer
systemctl --user enable --now asteroid-docking-bay-charge.timerAfter the initial map, the web UI handles everything else — plugging in a
new watch, moving a watch to a different port, or adding a watch to a previously
empty slot. Click Refresh on any empty port row to detect and configure the
watch automatically (reads the codename from the device, tests PPPS, updates
config). No CLI intervention needed for day-to-day use.
asteroid-docking-bay status
Show all configured watches with hub port, power state, ADB reachability, and battery percentage.
asteroid-docking-bay on <codename|all>
asteroid-docking-bay off <codename|all>
Power a port on or off. off all asks for confirmation unless --force is
passed.
asteroid-docking-bay cycle <codename|all> [--wait SEC]
Power off a port, wait (default: 5 s), power back on. Useful to hard-reset a stuck watch.
asteroid-docking-bay charge <codename|all> [--duration MIN]
Manual one-time charge cycle: power on → wait for ADB → charge to
high_threshold (polling the battery, capped by charge_max_minutes) →
power off. --duration MIN forces a timed charge instead; watches whose
battery can't be read fall back to charge_duration_minutes.
asteroid-docking-bay check-charge
Periodic charge logic (same as what the timer runs). Safe to run manually for
testing. For each configured watch: wakes it, checks battery, and if below
low_threshold charges it to high_threshold before powering off.
With adaptive_cadence enabled (default), a watch is only woken once its
observed standby drain — learned from past readings — projects it near
low_threshold, so a watch that barely self-discharges gets checked rarely
while a leaky one gets checked often. Watches with no drain history yet are
always checked. Run the timer more frequently (e.g. hourly) to let this
adapt; watches that are not due are skipped cheaply without being powered on.
asteroid-docking-bay map
One-time hub setup wizard:
- Discovers all connected smart hubs.
- Powers each port on in sequence and identifies the watch via ADB.
- Tests per-port power switching (PPPS) with a live toggle (up to ~30 s
per port — see
test-portsbelow for what is verified). - Saves hub topology, port assignments, and serial numbers to config.
Run once when you first connect a hub, and again only if the hub itself
moves to a different USB port on the host. Adding or moving watches between
ports does not require re-running map — use Refresh in the web UI instead.
The switching test can be run independently with test-ports.
asteroid-docking-bay test-ports [codename]
Re-test per-port power switching for all configured ports (or a specific watch).
Updates the config and reports the verdict per port. Run this after moving a
watch to a different hub port, or if you skipped the test during map.
The test verifies that VBUS is actually cut, not just that the hub's status register toggles (many hubs ACK the command while power stays live). Evidence, strongest first: the device dropping off ADB within seconds of the cut; if it keeps responding, the watch itself is asked whether it still sees external power. Verdicts are three-way:
SMART— VBUS cut confirmed by a live deviceNOT SMART— the device demonstrably kept external power through "off"UNVERIFIED— no device on the port to test against (empty port, or a watch that can't enumerate); re-run once a working watch is attached
A definitive verdict therefore requires a booted, ADB-visible watch on the port. Expect up to ~30 s per port.
asteroid-docking-bay discover
Scan for ADB-connected watches and print their codename and serial. Useful for finding serials after a new watch is connected.
asteroid-docking-bay serve [--host HOST] [--port PORT]
Start the web UI (requires pip install bottle). Serves a live status page at
http://HOST:PORT/ (default: http://127.0.0.1:8080/).
Each configured watch is shown as a table row. Empty port rows (no watch assigned) show a Refresh button that triggers a full per-port remap: powers the port on, waits for a watch, reads its codename, tests PPPS, and updates config — all streamed live into an inline log below the row.
Mapped watch rows read left to right in the order state actually flows — the port, its controls, the connection, the watch, its stats, battery, and actions. Almost everything lives in the row:
- Port — the socket/port label (
s1 p1) with the ON / OFF power toggle in front of it. The toggle shows an animated EXEC state while a switch is in flight and settles into the confirmed state. - Smart — the port's per-port-power-switching verdict as a pill:
pppswhen the port can switch its own VBUS,NO!when it can't, or a ⟳ cycle button when it hasn't been tested — clicking it power-cycles the port and fills in the verdict (the cycle is the test). - Connection — an ADB or SSH badge (each carries the watch's
address and opens the Network Center on click), a fastboot badge in
the bootloader, or an honest off-the-bus state:
no link,shelved(a confirmed graceful power-down),booting up/boot failed?after a boot attempt, orreconnectingafter a power cycle. - Watch — the product photo and codename; a disconnected watch's name dims so the connected ones stand out. Click the codename for the Control Center, the photo for the composited live screen.
- Stats — a row of dots: a power dot (green = powered, grey = shelved, orange = ambiguous) that also opens a short power menu, a wearability dot from the last drain test (click for drain-test / wear actions), a battery-graph dot, and a conditional charge dot (charging / full / discharging). The battery-related dots all open the same Battery Info panel; a "last live" age trails as plain text when the watch is off the bus.
- Battery — a fixed-width gauge that fills by charge level. The outline is grey; the fill is coloured (red / amber / green) only when the watch is connected and the charge state is real — offline it shows the last level in grey, a level without a colour claim. Click it for Battery Info.
- Actions — one menu pill, opening a single panel with every action grouped and labelled: Wear, Power (Charge / Drain test / Power off / Reboot / Bootloader), Flashing (Backup / Restore / Fastboot report / Flash nightly / 2.1 / 2.0), Workbench, and Refresh (re-identify / power on). A watch in the bootloader gets the fastboot-appropriate power group instead.
Empty port rows keep an Onboard button (the streamed remap above).
The charge, drain and workbench operations in detail:
- Charge — charges to
high_threshold(shown live as64% → 80%), polling the battery every 2 minutes and stopping exactly at the target (capped bycharge_max_minutes). Watches whose battery can't be read fall back to a fixedcharge_duration_minutescountdown. If a charging watch starts losing charge — dirty contacts, bad cable, failing port — the battery cell flags it instead of pretending to charge. Stop charge is available while it runs. - Drain test — standby battery drain measurement: powers the port off and
lets the watch run on battery, waking it every 30 minutes for a reading until
it reaches 15% or you press Stop test. The battery cell shows the current
level, drain rate (%/h) and estimated time to the floor. Results are saved as
JSON to
~/.local/share/asteroid-docking-bay/drain-tests/, and the drain history link in the page header lists every recorded test — per-watch drain rate, estimated 100→15% standby life, and a wearability verdict. A rate that rises across months means battery wear. Each watch's latest verdict shows permanently as its Stats wearability dot: wearable when the estimate is at leastwearable_min_hours(default 24 h) of standby, a battery-swap candidate otherwise; grey until the watch has ever been tested. - Workbench — check a watch out for hands-on work. The rig powers it up and
holds its battery in the low–high band for the whole session: charge to
high_threshold, rest with the port off, re-check everyworkbench_poll_minutes, charge again atlow_threshold— instead of a powered dock pegging the watch at 100% while you work. Do your work over WiFi/SSH (the USB link drops during rest phases); the rig's brief battery reads don't interfere. If the battery can't be read (USB switched to SSH mode), it falls back to a blind duty cycle ofworkbench_blind_charge_minutesof power per rest period. Return puts the watch back into the normal fleet, already inside the band. Charge, drain and flash actions refuse while a watch is checked out. - Flash nightly — full nightly flash streamed live into the inline log.
Control Center, Battery Info, Network Center. Click a watch's codename
for the Control Center — a live, host-side mirror of the watch's About page
and quick settings, read in a single batch: system (kernel, Qt, SoC, CPU clock,
uptime, boot reason, memory, storage, screen resolution, and the flashed
machine image, which names the real image behind a shared-image variant),
plus quick controls. From it you can toggle WiFi / Bluetooth, Buzz the
watch (vibrate to find it in a full dock), force its Screen on, grab a
Screenshot, and Sync its clock + timezone from the host. Session actions
run in the watch's ceres user session, not as root.
Two details have their own panels, reachable straight from the row:
- Battery Info — the battery gauge, or any of the Stats battery dots. Charge, health, technology, voltage, real charge/discharge current in mA, temperature, cycle count, and USB-input voltage (confirms the port is actually delivering power, not just switched on), with the battery-history chart at its foot.
- Network Center — the ADB / SSH badge. WiFi, IP, traffic, Bluetooth, the
connected companion phone, the watch's USB IP, and the USB-mode switch.
It also carries set up WiFi to <SSID>, which lends this watch a network
another watch already joined:
Backupstores/var/lib/connman, so the moment one watch is on the WiFi the rig holds a working credential for it, and the button copies that credential over instead of you typing a passphrase on a 320px touchscreen once per watch. The credential is re-keyed on the way in — connman identifies a saved network by the MAC of the interface that saved it, in both the directory name and the file, so a verbatim copy would be ignored by the receiving watch. The source watch's DHCP lease is dropped with it. The button appears only when a backup actually holds an AP, and never for the network the watch is already on.
Any wire — ADB or SSH. Every one of these — the Control Center, the
toggles, screenshots, time-sync, even a graceful power-off — works whether the
watch is on ADB or in SSH/developer USB mode; the tool reaches it over
whichever link answers. Switch a watch between modes from the Network Center (or
the workbench menu). Because every developer-mode watch defaults to the same
192.168.2.15, the tool hands each watch its own sticky SSH address (starting at
192.168.13.37), set over ADB before the switch, so several watches can run SSH
at once without colliding. A top-bar prefer ADB / prefer SSH toggle sets the
fleet policy: a watch that self-enumerates in the "wrong" mode on the shared
address is quietly corrected — returned to ADB, or relocated to its own SSH IP.
The Connection column carries the AsteroidOS logo inside the ADB / SSH
badge when the watch is running AsteroidOS; other watches show their detected OS
instead (e.g. WearOS) — handy for mixed collections that dock non-AsteroidOS
watches for battery care.
Physical moves are followed automatically for booted watches. Every status refresh compares each ADB-online watch's real hub port (from sysfs) against the config; when a watch has demonstrably moved — relocated to another port, or swapped with another watch — the mapping updates itself within one refresh cycle. Only booted, ADB-visible watches can be followed: relocating a powered-off watch still needs a click on Refresh at its new port. Each port also remembers the exact serial last seen there, so two units of the same codename never answer for each other.
Charge, drain and workbench state live in the server, not the browser —
reloading the page (or opening it from another machine) picks up running
operations and their countdowns. They also survive a service restart, crash
or reboot: each running operation is persisted to
~/.local/state/asteroid-docking-bay/tasks/ and resumed automatically when
the service comes back (a drain test keeps its readings and start time, a
charge continues toward its target). On ports recorded as not power-switchable
the power toggle and the Charge / Drain menu items are disabled (Refresh, the
watch actions and Flash still work — they only need a data connection).
The page auto-refreshes every 15 seconds. --host 0.0.0.0 makes it
reachable from other machines on the network — if your distro runs a
firewall, open the port first (firewalld:
sudo firewall-cmd --add-port=8080/tcp --permanent && sudo firewall-cmd --reload).
If the port is taken, --port picks another; the systemd unit is overridden
via systemctl --user edit asteroid-docking-bay-web.service.
For persistent background operation, use the included systemd service:
# One-time setup
pip install bottle
systemctl --user enable --now asteroid-docking-bay-web.service
# → http://127.0.0.1:8080/asteroid-docking-bay flash [codename|all] [--local DIR] [--dry-run]
[--force-download] [--download-dir DIR]
Flash AsteroidOS nightlies to all configured watches (or a single codename) in sequence, fully automated:
- Downloads
asteroid-{codename}-boot.imgandasteroid-image-{codename}.rootfs.ext4fromrelease.asteroidos.org/nightlies/{codename}/and verifies SHA512. Already- downloaded files that pass the checksum are reused without re-downloading. - Powers on the hub port.
- Waits for ADB. If the watch answers at
192.168.2.15instead (SSH/RNDIS mode rather than ADB mode), it prints a prompt to switch to ADB mode in Settings → USB on the watch, then waits again. adb reboot bootloader- Waits for a fastboot device to appear.
fastboot flash userdata+fastboot flash boot+fastboot continue- Removes stale
192.168.2.15andwatchentries from~/.ssh/known_hostsso the next SSH session isn't blocked by a key mismatch.
--dry-run prints the fastboot commands without executing them.
--local DIR uses pre-built images from a local directory instead of
downloading.
--force-download re-downloads even if the cached copy passes SHA512.
--download-dir DIR overrides the default cache location
(~/.local/share/asteroid-docking-bay/nightlies/).
WATCH PORT POWER SMART ADB BATTERY
nemo 1-1:p1 ON yes device 67%
sparrow 1-1:p2 OFF yes -- --
dory 1-1:p3 ON NO! unauthorized --
beluga 2-3.4:p2 ON ? offline --
SMART column values:
yes— per-port power switching confirmed by live testNO!— confirmed NOT switchable; on/off/cycle/charge have no effect?— not yet tested; runtest-portsto check
ADB states:
device— fully authorized and onlineunauthorized— authorize the ADB connection on the watch screenoffline— USB connected but ADB not yet ready (still booting, or sleeping)--— port is powered off or watch not recognized
Config is stored at ~/.config/asteroid-docking-bay/config.json.
See config.example.json in this repo for a fully-annotated example.
| Key | Default | Description |
|---|---|---|
low_threshold |
40 |
Charge if battery is below this % |
high_threshold |
80 |
Power off if battery is at or above this % |
charge_duration_minutes |
30 |
Blind-mode charge duration (battery unreadable) |
charge_max_minutes |
240 |
Hard cap for a charge-to-target cycle |
wearable_min_hours |
24 |
Estimated standby (100→15%) above which a watch counts as wearable |
max_powered_ports |
null |
Refuse to power on more than this many switchable ports at once (off by default). Non-PPPS hubs are never counted — their ports are permanently live and no policy can give them back |
xhci_max_slots |
null |
Override the USB controller's device-slot count (defaults to 32, the Intel 8-Series figure). Every device on an xHCI bus takes one, hubs included; past the limit devices enumerate but are never configured |
workbench_poll_minutes |
30 |
Workbench rest-phase re-check interval |
workbench_blind_charge_minutes |
15 |
Workbench charge burst per rest period when the battery is unreadable |
adb_wait_seconds |
15 |
Seconds between ADB availability retries |
adb_wait_retries |
8 |
Max retries (total wait: wait_seconds × retries) |
check_interval_hours |
12 |
Documentation only — actual interval is set in the systemd timer |
adaptive_cadence |
true |
Skip waking a watch during check-charge until its observed standby drain projects it near low_threshold; watches with no drain history are always checked |
adaptive_margin_pct |
10 |
Adaptive cadence wakes a watch when projected to reach low_threshold + this |
adaptive_max_interval_days |
14 |
Adaptive cadence never skips a watch longer than this |
flash.nightly_url |
https://release.asteroidos.org/nightlies |
Base URL for nightly image downloads |
flash.download_dir |
~/.local/share/asteroid-docking-bay/nightlies |
Local cache for downloaded images |
The check_interval_hours field does not drive scheduling. Edit
~/.config/systemd/user/asteroid-docking-bay-charge.timer to change the
interval, then reload: systemctl --user daemon-reload.
# Enable and start
systemctl --user enable --now asteroid-docking-bay-charge.timer
# Check status / last run
systemctl --user status asteroid-docking-bay-charge.timer
systemctl --user status asteroid-docking-bay-charge.service
# View logs
journalctl --user -u asteroid-docking-bay-charge.service -f
# Disable
systemctl --user disable --now asteroid-docking-bay-charge.timerpip install bottle # one-time
# Enable and start (serves at http://127.0.0.1:8080/)
systemctl --user enable --now asteroid-docking-bay-web.service
# View logs
journalctl --user -u asteroid-docking-bay-web.service -f
# Disable
systemctl --user disable --now asteroid-docking-bay-web.serviceTo change the port, override the service:
systemctl --user edit asteroid-docking-bay-web.service
# add:
# [Service]
# ExecStart=
# ExecStart=%h/.local/bin/asteroid-docking-bay serve --port 9090Optional. The single-process serve above is the default and is what the
bare-metal install runs; you only need this for a network-exposed
deployment.
There, the web UI can run as an unprivileged frontend container talking to a
separate, host-touching backend over a token-gated socket — so a compromise of
the exposed HTTP surface cannot reach the USB devices or the config. The design
and threat model are in docs/CONTAINERS.md; the pieces
live under containers/ (Containerfiles + podman quadlets).
containers/build.sh # build both images
python3 -c 'import secrets; print(secrets.token_urlsafe(32))' \
| podman secret create adb-token - # shared token
cp containers/adb-*.container containers/adb-*.network \
~/.config/containers/systemd/ # install quadlets
systemctl --user daemon-reload && systemctl --user start adb-frontendThe backend needs USB + sysfs access and the host's adb server; the exact rootless-podman device/group directives are noted in the quadlet and may need tuning per host.
Status: experimental. The split landed in 0.5 and the entrypoints are kept in step with the CLI, but it is not exercised by CI and not part of the hardware testing the rest of the tool gets — everything that touches a hub or a watch is verified on real hardware, and this path has not been through that recently. Treat it as a starting point rather than a supported deployment, and expect the device/group directives to need work on your host.
Some AsteroidOS watches auto-boot when USB power is applied. Others require a physical power button press. This tool cannot automate the latter — it will power on the port, then wait for ADB, and log a warning with instructions if the watch doesn't appear. You will need to press the button manually.
Known behavior by codename is not tracked here; check your watch's hardware documentation or the AsteroidOS porting guide.
uhubctl marks a hub as ppps (per-port power switching) if it responds
to power commands. There are two very different hardware behaviours hiding
behind this label:
True VBUS switching — the hub physically gates the 5 V supply rail. The watch loses both USB data and charging current. This is required for battery management (the charge timer is useless without it).
Data-line disconnect only — the hub disconnects D+/D− so the device
stops enumerating on USB (adb devices drops, port shows 0000 off), but
VBUS stays live and the watch keeps charging. This is what many cheap hubs
do, including ALCOR 05e3:0606 units.
test-ports distinguishes these automatically: it requires the device to
actually drop off the bus (or to report loss of external power over ADB)
before recording a port as smart — a toggling status register alone is not
accepted as evidence. To check manually: switch a port off with uhubctl,
then check whether the watch is still charging. If it is, the hub only
controls data lines.
Data-line-only hubs are still useful for ADB operations (flash, reboot, bootloader) since those require a live USB data connection. They cannot stop or control charging.
The uhubctl compatible devices list notes which hubs do true VBUS switching.
After uhubctl -l shows your hub location (e.g. 1-1), plug in one watch at
a time and use asteroid-docking-bay discover to identify which serial
appeared. Then use asteroid-docking-bay map to assign the codename.
Alternatively, uhubctl -l 1-1 -p 2 -a off powers off port 2 on hub 1-1;
if a watch disappears from adb devices, you have the right port.
uhubctl identifies hubs by their USB location string (e.g. 1-1, 2-3.4).
These strings are stable as long as you plug hubs into the same physical ports.
If you rearrange cables, re-run asteroid-docking-bay map.
The code lives in the asteroid_docking_bay/ package;
bin/asteroid-docking-bay is a thin launcher.
docs/ARCHITECTURE.md maps the modules, the
dependency rules, and which parts are classes. There is a ~290-test suite:
the pure logic (parsers, drain math, the charge alarm, the transport and op
tables), plus a headless-DOM harness that runs the web page's own JavaScript
under Node so column order, click wiring and each pill/dot/gauge is checked
without a browser. New behaviour is planted-bug validated — a test is only
trusted after it has failed against a deliberately reintroduced copy of the bug
it guards.
pytestAnything that touches a hub or a watch is verified on real hardware instead — see the release notes for what that means in practice.
./install.sh --uninstallThis removes the binary and systemd units. Config and serial mappings at
~/.config/asteroid-docking-bay/ are preserved. Remove manually with
rm -rf ~/.config/asteroid-docking-bay/.
GPL-3.0-only — see LICENSE.
Copyright (C) 2026 Timo Könnecke (moWerk) <mo@mowerk.net>
Copyright (C) 2023 Ed Beroset <beroset@ieee.org>
The flashing sequence in flash is based on
beroset/asteroid-hosttools
(flashy), which is also GPL-3.0-only.