- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| items | ||
| lbms | ||
| .gitignore | ||
| LICENSE | ||
| main.py | ||
| README.md | ||
| requirements.txt | ||
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
pip install -r requirements.txt
Running
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,hexper line, e.g. the files insideLBMS/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.logand the app exits (matching the VBEndbehavior).
Differences from the VB.NET original
- GUI: WinForms → tkinter; serial:
System.IO.Ports→ pyserial. ./items/*.cfgfiles are plain JSON instead of base64-wrappedBinaryFormatterblobs, so .NET-era.cfgfiles are not readable by the Python port (state starts fresh on first run).- The VB
Form1.vbreferenced aButton2missing 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 withdatetime.fromisoformat.