radiacode-webui/rc_read2.py

197 lines
7.4 KiB
Python

import time
import datetime
import sqlite3
import json
import os
from radiacode import RadiaCode # Assuming this is your wrapper
# Paths on your mounted NFS share
DB_PATH = "./radiacode_data.db"
JSON_OUTPUT_PATH = "/mnt/Share/radiation/live_spectrum.json"
def init_db():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# Table for 5-minute spectrum snapshots
cursor.execute('''CREATE TABLE IF NOT EXISTS spectrums (
timestamp TEXT PRIMARY KEY,
duration_secs REAL,
channels_json TEXT)''')
# Table for instantaneous dose rate snapshots
cursor.execute('''CREATE TABLE IF NOT EXISTS rates (
timestamp TEXT PRIMARY KEY,
count_rate REAL,
dose_rate REAL)''')
conn.commit()
conn.close()
def calculate_channels(a0, a1, a2, counts):
"""Calculates keV for each channel and pairs it with the count."""
spectrum_data = []
for channel_num, count in enumerate(counts):
# Formula: a0 + a1*ch + a2*ch^2
kev = a0 + (a1 * channel_num) + (a2 * (channel_num ** 2))
spectrum_data.append({"kev": round(kev, 2), "count": count})
return spectrum_data
def update_web_json():
"""Generates a small JSON file containing the latest state for Apache."""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# Define the 24-hour lookback window
yesterday = (datetime.datetime.now() - datetime.timedelta(hours=24)).isoformat()
# 1. Fetch real-time count and dose rate history for the timeline chart
cursor.execute("SELECT timestamp, count_rate, dose_rate FROM rates WHERE timestamp > ? ORDER BY timestamp ASC", (yesterday,))
all_rates = cursor.fetchall()
# Downsample rates to avoid massive JSON files (target ~1000 points)
# Instead of taking arbitrary interleaved points (which causes graph jitter and misses peaks),
# we group the data into bins and calculate the maximum value for each bin.
rates_history = []
if all_rates:
step = max(1, len(all_rates) // 1000)
for i in range(0, len(all_rates), step):
chunk = all_rates[i:i+step]
max_dose = max(r[2] for r in chunk)
max_count = max(r[1] for r in chunk)
# Use the middle timestamp of the chunk to represent the bin's time
timestamp = chunk[len(chunk)//2][0]
rates_history.append((timestamp, max_count, max_dose))
# 2. Fetch all 5-minute isolated intervals from the last 24 hours
cursor.execute("SELECT timestamp, channels_json FROM spectrums WHERE timestamp > ? ORDER BY timestamp ASC", (yesterday,))
spectrogram_rows = cursor.fetchall()
spectrogram_history_list = []
master_aggregation_dict = {}
# 3. Parse rows: build the waterfall history AND calculate the 24-hour combined sum
for row in spectrogram_rows:
timestamp = row[0]
channels = json.loads(row[1])
# Keep this isolated chunk intact for the spectrogram waterfall
# We MUST keep the full array (including zeros) because Plotly's heatmap
# expects zValues[row][col] to map directly to xChannels[col]
spectrogram_history_list.append({"time": timestamp, "counts": [ch["count"] for ch in channels]})
# Accumulate the values into our master math dictionary to wipe out empty channels
# Only the accumulated total spectrum can be filtered for zeros to save size
for ch in channels:
kev = ch["kev"]
count = ch["count"]
master_aggregation_dict[kev] = master_aggregation_dict.get(kev, 0) + count
# Convert the flattened dictionary back to a sorted list of objects for Plotly
accumulated_spectrum_kev = []
accumulated_spectrum_counts = []
for kev, count in sorted(master_aggregation_dict.items()):
accumulated_spectrum_kev.append(round(kev, 2))
accumulated_spectrum_counts.append(count)
# Prepare parallel arrays for rates_history
rates_history_times = []
rates_history_count_rates = []
rates_history_dose_rates = []
for r in rates_history:
rates_history_times.append(r[0])
rates_history_count_rates.append(r[1])
rates_history_dose_rates.append(r[2])
# Prepare parallel arrays for spectrogram_history
spectrogram_history_times = [s["time"] for s in spectrogram_history_list]
spectrogram_history_counts = [s["counts"] for s in spectrogram_history_list]
# 4. Construct the output payload
payload = {
"accumulated_spectrum": {
"kev": accumulated_spectrum_kev,
"count": accumulated_spectrum_counts
},
"rates_history": {
"time": rates_history_times,
"count_rate": rates_history_count_rates,
"dose_rate": rates_history_dose_rates
},
"spectrogram_history": {
"time": spectrogram_history_times,
"counts": spectrogram_history_counts
}
}
# Atomic write to prevent Apache from reading a half-written file
temp_path = JSON_OUTPUT_PATH + ".tmp"
with open(temp_path, 'w') as f:
json.dump(payload, f)
os.replace(temp_path, JSON_OUTPUT_PATH)
conn.close()
def main():
init_db()
rc = RadiaCode()
# Reset device memory on script startup to ensure a clean window alignment
try:
rc.spectrum_reset()
except Exception as e:
print(f"Initial hardware reset failed: {e}")
# Generate the JSON immediately on startup so the frontend is populated
update_web_json()
print("Initial web JSON generated.")
last_spectrum_time = time.time()
while True:
try:
# 1. Handle Continuous Data Buffer
buf = rc.data_buf()
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
for msg in buf:
# Check for DoseRateDB messages
if hasattr(msg, 'dose_rate'):
ts = msg.dt.isoformat()
cursor.execute("INSERT OR REPLACE INTO rates VALUES (?, ?, ?)",
(ts, msg.count_rate, msg.dose_rate))
conn.commit()
conn.close()
# 2. Handle 5-minute Spectrum Polling
if time.time() - last_spectrum_time >= 300: # 300 seconds = 5 mins
spec = rc.spectrum()
# RE-ENABLED RESET: Wipes the device memory so the NEXT 5 mins start from zero
rc.spectrum_reset()
ts = datetime.datetime.now().isoformat()
# Process and format this isolated chunk
channels = calculate_channels(spec.a0, spec.a1, spec.a2, spec.counts)
channels_json = json.dumps(channels)
# Save isolated snapshot to DB
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("INSERT OR REPLACE INTO spectrums VALUES (?, ?, ?)",
(ts, spec.duration.total_seconds(), channels_json))
conn.commit()
conn.close()
# 3. Pull from DB, compile math, and write out to Apache
update_web_json()
last_spectrum_time = time.time()
print(f"[{ts}] Database and web JSON refreshed with hardware interval reset.")
except Exception as e:
print(f"Error loop caught: {e}")
time.sleep(1) # Sleep briefly to prevent CPU pinning
if __name__ == "__main__":
main()