Files
2026-10-11 20:37:14 -04:00

124 lines
5.6 KiB
Markdown

# LBMS
Laser gun monitoring system: listens on a serial port for XBee packets from 44
tracked guns, decodes fire counts and "seen" beacons, estimates on-time (battery
use), and displays a live grid of gun tiles. Includes a history dialog
(per-gun stats, battery-change log) and a simulate mode that replays captured
log files.
This repository contains:
- `LBMS/` — the original VB.NET WinForms project (reference, unchanged)
- `lbms/`, `main.py` — the **Python port** (documented below)
## Python port
### Requirements
- Python 3.8+
- `tkinter` (standard library; on Debian/Ubuntu: `sudo apt install python3-tk`)
- [`pyserial`](https://pypi.org/project/pyserial/)
```sh
pip install -r requirements.txt
```
### Running
```sh
python3 main.py [options]
```
Options (all optional, `key=value` pairs):
| Option | Default | Description |
|-----------|---------|----------------------------------------------------------|
| `com` | — | Serial port to open automatically on start (e.g. `/dev/ttyUSB0`). The main window is minimized when this is used. |
| `admin` | `false` | `admin=true` unlocks the SetFired/SetTime controls in the history dialog. |
| `fired` | `11000` | Fire-count threshold; reaching it flags the gun for a battery change. |
| `battery` | `660` | On-time (minutes) threshold; reaching it flags the gun for a battery change. |
If no port is given, enter one in the text box (e.g. `COM4` / `/dev/ttyUSB0`)
and click **Button1** to open/close it. Status shows in `Label1` (Open/Closed).
### Interface
- **LBMS Console** (main window) — port controls, live decode stats
(lines read, valid packets, queue/decode lengths, raw hex), and a scrolling
per-gun summary.
- **LBMS** (gun grid) — one tile per gun, 8x6 grid. Tile color:
- green — seen in the last second
- orange — seen in the last second but flagged for battery change
- fading green — seen within the last 30 seconds
- default grey — no recent contact
- gun icon appears for ~3 s after a fire event; battery icon shows
estimated remaining time/shots; both flash when a battery change is due.
- Click a tile to open its **History** dialog: current + historical
fired/on-time totals, change log, and **Change Batteries** (rolls current
totals into history and zeroes them). Admin mode adds SetFired/SetTime.
- Double-click the grid window to exit the app (with confirmation).
- **Simulate** — pick a capture log (`timestamp,hex` per line, e.g. the files
inside `LBMS/220619.zip`), and packets are replayed through the normal
decode pipeline at their original timing.
### Serial protocol
The port runs 115200, 8N1. Data is uppercased to hex and fed into a queue;
the 75 ms main timer drains it into a decode buffer and scans for:
| Marker | Length (hex chars) | Layout | Action |
|-----------|--------------------|---------------------------------------------------------------|-------------------------------|
| `7E002480`| 80 | head(8) + MAC(16) + payload(34) + fire count(4) + tail(18) | add fire count, mark seen |
| `7E002A80`| 92 | head(8) + MAC(16) + payload(36) + tail(32) | mark seen |
Fire count is big-endian (`hi*256 + lo`). When a count ever decreases (gun
rebooted), the previous count is added to the gun's overall total and battery
time is rolled over. Every time a known gun is seen and the last count is
older than `CountInterval` (10 min), that interval is added to its on-time —
which is how battery life is estimated. Junk data: if no packet is found and
the buffer exceeds 200 chars, the first half is discarded.
### Persistence
State is saved to `./items/<gun_id>.cfg` (one JSON file per gun) on close and
every ~61 s. On start, if `./items/` exists it is loaded; otherwise all 44
guns are created fresh from the MAC list in `lbms/main_window.py`
(`_create_id_list`).
### Project layout
```
main.py # entry point
requirements.txt
lbms/
├── globals.py # shared state: gun list, message queue, helpers
├── gun_object.py # GunObject model + JSON save/load
├── cls_com_ports.py # ComPorts serial wrapper (pyserial)
├── resources.py # PNG image loader (lbms/resources/)
├── gun_button.py # one gun tile (text, colors, battery/gun icons)
├── frm_guns.py # gun grid window
├── frm_history.py # per-gun history dialog
└── main_window.py # console window: timers, serial reader, packet decoder
```
### Threading model
- A daemon **reader thread** polls the serial port and enqueues hex strings
into `lbms.globals.message_queue`.
- All decoding and UI updates happen on the tkinter thread via `after()`
timers (75 ms main tick, 5 ms simulate, 1000 ms summary, 250/500 ms per
tile) — the same architecture as the original WinForms event/timer design.
- Unrecoverable decode errors are appended to `err.log` and the app exits
(matching the VB `End` behavior).
## Differences from the VB.NET original
- GUI: WinForms → tkinter; serial: `System.IO.Ports` → pyserial.
- `./items/*.cfg` files are plain **JSON** instead of base64-wrapped
`BinaryFormatter` blobs, so .NET-era `.cfg` files are not readable by the
Python port (state starts fresh on first run).
- The VB `Form1.vb` referenced a `Button2` missing from its designer file; the
port implements it as the **Button2** "save now" button next to Button1.
- The simulate log format `YYYY-MM-DD HH:MM:SS.mmm,<hex>` is parsed with
`datetime.fromisoformat`.