scanner_script/scanner.py
2026-03-12 22:39:06 +01:00

305 lines
12 KiB
Python

import gc
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import re
import telnetlib3
import csv
import time
import pyodbc
import math
from datetime import datetime
data_rows = [] # global 2-D list
state = {
"ampl": 1600,
"remaining_receive_lines": 0,
"freq": 10000.0,
"freq_step_multiply": 0.85,
"stop_freq": 0.1,
"initializing": 1,
"noise_scan": False
}
PATTERN = re.compile(
r"Va=(?P<Va>-?\d+\.?\d*)\s+"
r"Vp=(?P<Vp>-?\d+\.?\d*)\s+\|\s+"
r"Ia=(?P<Ia>-?\d+\.?\d*)\s+"
r"Ip=(?P<Ip>-?\d+\.?\d*)\s+\|\s+"
r"ZR=(?P<ZR>-?\d+\.?\d*)\s+"
r"ZX=(?P<ZX>-?\d+\.?\d*)"
r".*?irq=\d+\s+" # Skip to 'irq=', match the first digits and space
r"(?P<adc_vmin>[0-9a-fA-F]+)-" # First hex
r"(?P<adc_vmax>[0-9a-fA-F]+)\s+" # Second hex
r"(?P<adc_imin>[0-9a-fA-F]+)-" # Third hex
r"(?P<adc_imax>[0-9a-fA-F]+)" # Fourth hex
)
# Database connection parameters
conn_str = (
"DRIVER={FreeTDS};"
"SERVER=10.1.20.18;"
"PORT=1433;"
"DATABASE=H2_HealthMonitoring01;"
"UID=rh\\sa_H2_HealthMon01;"
"PWD=AAo6EM2pttfV5EVZrWqB;"
"TDS_Version=7.4;"
)
def dump_into_database():
try:
with pyodbc.connect(conn_str) as conn:
cursor = conn.cursor()
sweep_insert_time = datetime.now()
print("dump into database\n")
# Prepare the data: SQL Server expects the columns in order.
if state["noise_scan"]==False:
# regular scan data
formatted_rows = [
[sweep_insert_time, r[0], r[5], r[6], r[1], r[2], r[3], r[4]]
for r in data_rows
]
sql = """
INSERT INTO SequenceValues (StartTimeOfSweep, Freq, ZR, ZX, Vampl, Vphase, Iampl, Iphase)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) \
"""
else:
# noise scan data
formatted_rows = [
[sweep_insert_time, r[0], r[1], r[3]]
for r in data_rows
]
sql = """
INSERT INTO SequenceNoiseFloor (StartTimeOfSweep, Freq, Vampl, Iampl)
VALUES (?, ?, ?, ?) \
"""
print("execute\n")
# Execute in bulk
cursor.fast_executemany = False # needs less RAM
cursor.executemany(sql, formatted_rows)
print("commit\n")
conn.commit()
print(f"Successfully inserted {len(formatted_rows)} rows.")
except Exception as e:
print(f"Database error: {e}")
def dump_csv(filename="data.csv"):
df = pd.DataFrame(
data_rows,
columns=["Freq", "Va", "Vp", "Ia", "Ip", "ZR", "ZX", "adc_vmin", "adc_vmax", "adc_imin", "adc_imax"]
)
df.to_csv(filename, index=False)
def append_line_to_file(column_nr, filename):
# Extract freq column (index 0)
# Extract ZR column (index 5)
# Extract ZX column (index 6)
zr_values = [row[column_nr] for row in data_rows if len(row) > column_nr]
if not zr_values:
print("No ZR data to write")
return
with open(filename, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow(zr_values)
def append_Nyquist_run(filename):
# Flatten ZR/ZX pairs into one row
row = []
for r in data_rows:
if len(r) > 6:
row.extend([r[5], r[6]])
if not row:
print("No ZR/ZX data to write")
return
with open(filename, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow(row)
def extract_to_dataframe(line):
# Extract Va, Vp, Ia, Ip, ZR, ZX from a line and append them as a row to the global data_rows list.
global state
match = PATTERN.search(line)
if not match:
return # silently ignore malformed lines
row = [
float(state["freq"]),
float(match.group("Va")),
float(match.group("Vp")),
float(match.group("Ia")),
float(match.group("Ip")),
float(match.group("ZR")),
float(match.group("ZX")),
int(match.group("adc_vmin"), 16),
int(match.group("adc_vmax"), 16),
int(match.group("adc_imin"), 16),
int(match.group("adc_imax"), 16),
]
print("uncorr:\r")
print(row)
row[1] = row[1]/(2*3.14159*row[0]*0.00005 + 1) # compensate Va for pole at 20kHz
row[5] = row[1]/row[3] * math.cos(0.01745*(row[2]-row[4]))
row[6] = row[1]/row[3] * math.sin(0.01745*(row[2]-row[4]))
print("corr:\r")
print(row)
data_rows.append(row)
def process_line(line):
extract_to_dataframe(line)
# Example: print last row
if data_rows:
print("Last row:", data_rows[-1])
def get_dataframe():
return pd.DataFrame(
data_rows,
columns=["Va", "Vp", "Ia", "Ip", "ZR", "ZX", "adc_vmin", "adc_vmax", "adc_imin", "adc_imax"]
)
class TelnetReader:
def __init__(self, host, port=23, timeout=10):
self.host = host
self.port = port
self.timeout = timeout
self.tn = None
def connect(self):
print(f"Connecting to {self.host}:{self.port} ...")
self.tn = telnetlib3.Telnet(self.host, self.port, self.timeout)
print("Connected.")
def disconnect(self):
if self.tn:
self.tn.close()
self.tn = None
print("Disconnected.")
def read_loop(self):
global state
# Main loop: reads incoming lines forever
try:
while state["freq"] > state["stop_freq"]:
line = self.tn.read_until(b"\n") # read line
if not line:
break
decoded = line.decode("utf-8", errors="ignore").strip()
self.process_line(decoded) # extract all info from line
except KeyboardInterrupt:
print("Interrupted by user.")
print(data_rows)
if state["freq"]>0.00001: # check if scan endeed normally (e.g. no abort due to clipping)
#dump_csv("measurements.csv")
dump_into_database()
#append_line_to_file(0, "scan_freq.csv")
#append_line_to_file(1, "scan_Va.csv")
#append_line_to_file(2, "scan_Vp.csv")
#append_line_to_file(3, "scan_Ia.csv")
#append_line_to_file(4, "scan_Ip.csv")
#append_line_to_file(5, "scan_ZR.csv")
#append_line_to_file(6, "scan_ZX.csv")
#append_Nyquist_run("scan_Nyquist.csv")
#state["noise_scan"] = not state["noise_scan"]
else:
print("scan aborted\r")
gc.collect() # clean up internal memory (garbage collect)
def process_line(self, line):
global state
# print(f"RAW: {line}")
if not line.startswith("Va="): # skip lines that are not to be analyzed
return
if state["remaining_receive_lines"] > 0 :
state["remaining_receive_lines"] -= 1 # we are waiting for settling - just skip the line
else:
# prepare new frequency measurement
if state["initializing"] == 1:
# since we just start a frequency scan, let's set the R, Max and Amplitude
response = f"\rr516\r" # set Resistance value for scaling HAL sensor
self.tn.write(response.encode("utf-8"))
response = f"m1600\r" # set max amplitude value
self.tn.write(response.encode("utf-8"))
# for noise scans the amplitude is zero, else the state["ampl"]
if state["noise_scan"]==True:
response = f"a0\r" # zero amplitude for noise measurement
else:
response = f"a{state["ampl"]:.1f}\r" # set amplitude
self.tn.write(response.encode("utf-8"))
# there will be a lot of lines, but they will be skipped as they do not match the pattern
else: # regular loop (not initializing)
extract_to_dataframe(line) # capture the measurement
# print(f"minmax: {data_rows[7]},{data_rows[8]},{data_rows[9]},{data_rows[10] }\n")
if data_rows and (data_rows[-1][7]<5 or data_rows[-1][8]>250 or data_rows[-1][9]<5 or data_rows[-1][10]>250):
#response = f"a{state["ampl"]:.1f}\r" # send ampl to trigger sweep
#self.tn.write(response.encode("utf-8"))
# there will be a lot of lines, but they will be skipped as they do not match the pattern
state["freq"] = 0 # force ending of the scan, and write no data in the database
# calculate next freq
if state["freq"] > 1.0:
freq_multiplier = state["freq_step_multiply"] # calc next freq
else:
if state["freq"] > 0.01:
freq_multiplier = state["freq_step_multiply"] ** 2 # skip 1/2 steps to speed up
else:
freq_multiplier = state["freq_step_multiply"] ** 4 # skip 3/4 steps to speed up
if state["noise_scan"]==True:
state["freq"] *= freq_multiplier ** 2 # noise scans step twice as quickly
else:
state["freq"] *= freq_multiplier
if (state["freq"]>40) and (state["freq"]<660) and ((state["freq"]%50<2.5) or (-state["freq"]%50<2.5)):
state["freq"] *= freq_multiplier # if near a 50Hz harmonic, skip to the next frequency
while (state["freq"]<0.6) and ((state["freq"]%0.053<0.008) or (-state["freq"]%0.053<0.008)):
state["freq"] *= freq_multiplier # if near a 0.052Hz harmonic, skip to the next frequency
if state["freq"] <= state["stop_freq"]:
print("Reached stop frequency")
else:
if state["freq"] > 1:
response = f"\rf{state["freq"]:.1f}\r" # new freq
else:
response = f"\rf{state["freq"]:.3f}\r" # new freq
self.tn.write(response.encode("utf-8"))
bandwidth = state["freq"]/20
if bandwidth > 0.5 :
bandwidth = 0.5
response = f"b{bandwidth:.3f}\r" # new freq
self.tn.write(response.encode("utf-8"))
state["remaining_receive_lines"] = 1 + 4/bandwidth
print(line)
print(state)
state["initializing"] = 0
# Example:
# value = self.extract_value(line)
# self.store_value(value)
# Example placeholder methods
def extract_value(self, line):
pass
def store_value(self, value):
pass
if __name__ == "__main__":
reader = TelnetReader(host="localhost", port=2002)
# reader = TelnetReader(host="10.1.122.152", port=2002)
# reader = TelnetReader(host="192.168.1.196", port=2002)
try:
reader.connect()
while True:
data_rows.clear()
state["ampl"] = 1600;
state["remaining_receive_lines"] = 0;
state["freq"] = 1000;
state["freq_step_multiply"] = 0.95;
state["stop_freq"] = 0.1;
state["initializing"] = 1;
reader.read_loop()
finally:
reader.disconnect()