The converter is where vendor PDFs turn into NMEA2000 decode tables. That work happens on a laptop or a phone, on a real screen, because reviewing a bit-field offset on a 3.5" panel is not a thing anyone should do. So the usage pattern is: tap the Tables tile on the Pi, walk to the bench, do the review in a browser, close the laptop, forget about it.
That last step is the design problem. A Flask app with an upload endpoint,
bound to 0.0.0.0 so the laptop can reach it, is not something you want
sitting there for the next six weeks because a person got distracted. And
asking the person to come back and press Stop is not a design, it is a
wish.
So the shutdown does not live in kilodash at all. kilodash/tableconv.py
runs a small watchdog thread that wakes every 15 s, asks one object how long
the app has been idle, and calls os._exit(0) once that passes the
--idle-min window — 15 minutes by default. The app hangs up on itself. The
front panel is not involved and does not need to be running.
Two things count as "busy", not one
The obvious idle clock is "time since the last HTTP request", and it is wrong. A big vendor PDF can take three minutes to parse in the extraction subprocess, and during those three minutes there are no requests at all — just a browser waiting on one that hasn't returned. An HTTP-only clock kills the job it is waiting on.
So the Activity object tracks two things behind a lock: the last touch
time, and a count of in-flight jobs. idle_secs() returns a flat 0.0
while any job is running, whatever the clock says. An upload calls
job_start() before extraction and job_end() in a finally, so a parse
that crashes still releases the counter.
There is a second, smaller trap in the same place. The Tables tile polls
/status every 5 s to draw its countdown — and if that poll counted as
activity, the app would never idle out as long as anyone was looking at the
tile. So /status is excluded from the touch, by path, in the
before_request hook. The observer does not get to change what it observes.
The unit has to agree
setup/kilodash-tables.service is Restart=no, and that line is
load-bearing. A clean idle exit is exit code 0 — with a restart policy in
place, systemd would dutifully bring the app back up, and the two halves of
the design would spend all night fighting each other. On-demand start,
self-managed stop, no supervisor second-guessing either.
Being killable at any instant is what makes the whole arrangement safe. Every write into the table store is tmp-file plus atomic rename, so there is no moment where the timer firing leaves a half-written table behind. The timeout does not need to be polite because the storage layer does not need it to be.
The tile mirrors, it never computes
The Tables screen is deliberately thin. It starts the service, and it never
stops it on leave — navigating away from the tile must not kill a review
session in progress on the laptop. What it shows is read back from the
service's own /status: table count, and the idle clock rendered as
running · idle-3:42, so the countdown to shutdown is visible rather than a
surprise. Below that, the URL for the advertised address, and a QR code of
the same, because typing an IP into a phone is the small friction this whole
project exists to delete.
Next log: the step inside that web app we have not talked about — the side-by-side review page, and why extraction is assistive but approval is always human.
Scottsky
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.