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 import yaml import os data_rows = [] # global 2-D list state = {} PATTERN = re.compile( r"Va=(?P-?\d+\.?\d*)\s+" r"Vp=(?P-?\d+\.?\d*)\s+\|\s+" r"Ia=(?P-?\d+\.?\d*)\s+" r"Ip=(?P-?\d+\.?\d*)\s+\|\s+" r"ZR=(?P-?\d+\.?\d*)\s+" r"ZX=(?P-?\d+\.?\d*)" r".*?irq=\d+\s+" # Skip to 'irq=', match the first digits and space r"(?P[0-9a-fA-F]+)-" # First hex r"(?P[0-9a-fA-F]+)\s+" # Second hex r"(?P[0-9a-fA-F]+)-" # Third hex r"(?P[0-9a-fA-F]+)" # Fourth hex ) config_path = '/home/bart/python-scanner/config.yaml' def load_yaml_config(state_dict): if os.path.exists(config_path): with open(config_path, 'r') as f: # Loader=yaml.SafeLoader is the secure way to load YAML config_data = yaml.load(f, Loader=yaml.SafeLoader) if config_data: state_dict.update(config_data) else: print(f"Error: {config_path} not found.") # ============================================= # 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), ] 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])) 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 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"]%state["interference_freq"] 0.01: 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() load_yaml_config(state) # Load configuration settings state["freq"] = state["start_freq"]; state["remaining_receive_lines"] = 0; state["initializing"] = 1; reader.read_loop() finally: reader.disconnect()