istrain-public: from-scratch build of a passive RF train detector

Public community release. The complete working system — DSP + decoders (scripts/),
web dashboard + API (dashboard/), container stack (docker/), config templates (config/) —
plus APOCALYPSE-EDITION.md, the full build guide for a human or a coding agent. GPLv3
(PyEOT dependency). Scrubbed of all secrets, internal IPs, hostnames, and location detail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
istrain
2026-07-24 23:19:16 -04:00
commit 559caead36
41 changed files with 6740 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyEOT End-of-Train Device Decoder
Copyright (c) 2018 Eric Reuter
This source file is subject of the GNU general public license.
history: 2018-08-09 Initial Version
purpose: Class to parse EOT packet and generate BCH checkbits
for verification.
Requires helpers.py
"""
import helpers
class EOT_decode():
def __init__(self, buffer):
self.packet = buffer[0:74]
self.frame_sync = self.packet[0:11]
self.data_block = self.packet[11:56]
self.batt_cond = (self.packet[13:15][::-1])
self.message_type = self.packet[15:18]
self.unit_addr = int((self.packet[18:35][::-1]), 2)
self.pressure = int((self.packet[35:42][::-1]), 2)
self.batt_charge = \
("{}%".format(int(int((self.packet[42:49][::-1]), 2) / 127 * 100)))
self.spare = self.packet[49]
self.valve_ckt_stat = self.packet[50]
self.conf_ind = self.packet[51]
self.turbine = self.packet[52]
self.motion = self.packet[53]
self.mkr_batt = self.packet[54]
self.mkr_light = self.packet[55]
self.checkbitsRx = self.packet[56:74]
self.batt_cond_dict = {"11": "OK",
"10": "Low",
"01": "Very Low",
"00": "Not Monitored"}
self.batt_cond_text = self.batt_cond_dict[self.batt_cond]
if (self.message_type == "111"):
if (self.conf_ind == "0"):
self.arm_status = "Arming"
else:
self.arm_status = "Armed"
else:
self.arm_status = "Normal"
self.generator = '1111001101000001111' # BCH generator polynomial
self.cipher_key = '101011011101110000' # XOR cipher key
self.data_block = helpers.reverse(self.data_block)
self.checkbits = helpers.checkbits(self.data_block, self.generator)
self.checkbits_cipher = helpers.xor(self.checkbits, self.cipher_key)
self.valid = (self.checkbits_cipher == self.checkbitsRx) # a match?
def get_packet(self):
return ''.join(self.packet)