This version is stable
This commit is contained in:
commit
0744c060f1
1 changed files with 305 additions and 0 deletions
305
scanner.py
Normal file
305
scanner.py
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue