diff --git a/.forgejo/workflows/backup-repo.yaml b/.forgejo/workflows/backup-repo.yaml new file mode 100644 index 0000000..d49c28d --- /dev/null +++ b/.forgejo/workflows/backup-repo.yaml @@ -0,0 +1,39 @@ +name: Git Backup to WebDAV +on: + push: + # This ensures it runs on every branch push + branches: + - '**' +jobs: + print-content: + runs-on: debian-latest + steps: + - name: checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: archive repository + run: | + PROJECT_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2) + BRANCH_NAME=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') + TIMESTAMP=$(date +'%Y-%m-%d_%H-%M') + + # Create the local variable + FINAL_NAME="${PROJECT_NAME}_${BRANCH_NAME}_${TIMESTAMP}.tar.gz" + + # CRITICAL: Save it for the next step + echo "PROJECTNAME=$PROJECT_NAME" >> $GITHUB_ENV + echo "FILENAME=$FINAL_NAME" >> $GITHUB_ENV + + # Create the archive + git archive --format=tar.gz -v -o "$FINAL_NAME" HEAD + echo "Archive created: $FINAL_NAME" + + - name: Upload via Curl + run: | + ls -lh "$FILENAME" + + curl -T "$FILENAME" \ + -u "${{ secrets.WEBDAV_USER }}:${{ secrets.WEBDAV_PASSWORD }}" \ + "${{ secrets.WEBDAV_URL }}/$PROJECTNAME/$FILENAME" diff --git a/config.yaml b/config.yaml index 6e3e2c2..c4b0e10 100644 --- a/config.yaml +++ b/config.yaml @@ -1,9 +1,12 @@ # Configuration file for scanner service # it is automatically re-loaded between scans -start_freq: 10000.0 -stop_freq: 0.1 +start_freq: 4000 +stop_freq: 0.4 freq_step_multiply: 0.95 +allowed_noise_level: 15 +skip_scans_for_noisy_freqs: 3 ampl: 1600 -interference_freq: 0.052 -interference_bandwidth: 0.008 noise_scan: False +hw_version: "2026-05-28" # aangepaste R27 en R28 voor boven 700A +scan_comment: "normale full scan met verbeterde PCB (nu goed boven 700A)" + diff --git a/scanner.py b/scanner.py index 2fecf56..039d362 100644 --- a/scanner.py +++ b/scanner.py @@ -12,21 +12,22 @@ from datetime import datetime import yaml import os +VersionScripSoftware = "2026-05-28" +VersionDspSoftware = "Unknown" + data_rows = [] # global 2-D list state = {} +blacklist = [] +Inoise_baseline = 0.1 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 + r"Va=(?P[\d.-]+)\s+Vp=(?P[\d.-]+)\s*\|\s*" + r"Ia=(?P[\d.-]+)\s+Ip=(?P[\d.-]+)\s*\|\s*" + r"ZR=(?P[\d.-]+)\s+ZX=(?P[\d.-]+).*?\|\s*" + r".*?irq=\w+\s+" + r"(?P[0-9a-fA-F]+)-(?P[0-9a-fA-F]+)\s+" + r"(?P[0-9a-fA-F]+)-(?P[0-9a-fA-F]+).*?\|\s*" + r"nv=(?P[\d.-]+)\s+ni=(?P[\d.-]+)\s*\|\s*" ) config_path = '/home/bart/python-scanner/config.yaml' @@ -54,21 +55,36 @@ conn_str = ( ) def dump_into_database(): + print("dump into database\n") try: with pyodbc.connect(conn_str) as conn: cursor = conn.cursor() sweep_insert_time = datetime.now() - print("dump into database\n") + # Add scan details + cursor.execute(""" + IF NOT EXISTS ( + SELECT 1 FROM ScanDetails WHERE StartTimeOfSweep = ? + ) + INSERT INTO ScanDetails + (StartTimeOfSweep, VersionScanScript, VersionDspSoftware, VersionHardware, Comment) + VALUES (?, ?, ?, ?, ?) + """, sweep_insert_time, + sweep_insert_time, VersionScripSoftware, VersionDspSoftware, state["hw_version"], state["scan_comment"]) + print("commit1\n") + conn.commit() + + with pyodbc.connect(conn_str) as conn: + cursor = conn.cursor() # 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]] + [sweep_insert_time, r[0], r[5], r[6], r[1], r[2], r[3], r[4], r[11], r[12]] for r in data_rows ] sql = """ - INSERT INTO SequenceValues (StartTimeOfSweep, Freq, ZR, ZX, Vampl, Vphase, Iampl, Iphase) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) \ + INSERT INTO SequenceValues (StartTimeOfSweep, Freq, ZR, ZX, Vampl, Vphase, Iampl, Iphase, Vnoise, Inoise) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \ """ else: # noise scan data @@ -87,7 +103,7 @@ def dump_into_database(): cursor.fast_executemany = False # needs less RAM cursor.executemany(sql, formatted_rows) - print("commit\n") + print("commit2\n") conn.commit() print(f"Successfully inserted {len(formatted_rows)} rows.") @@ -134,17 +150,19 @@ def extract_to_dataframe(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), + float(state["freq"]), # row 0 + float(match.group("Va")), # row 1 + float(match.group("Vp")), # row 2 + float(match.group("Ia")), # row 3 + float(match.group("Ip")), # row 4 + float(match.group("ZR")), # row 5 + float(match.group("ZX")), # row 6 + int(match.group("adc_vmin"), 16), # row 7 + int(match.group("adc_vmax"), 16), # row 8 + int(match.group("adc_imin"), 16), # row 9 + int(match.group("adc_imax"), 16), # row 10 + float(match.group("nv")), # row 11 + float(match.group("ni")), # row 12 ] 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])) @@ -196,25 +214,31 @@ class TelnetReader: 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"] + if state["freq"]>0.00001: # check if scan ended normally (e.g. no abort due to clipping) + if True: # XXXDB + dump_into_database() + else: + dump_csv("measurements.csv") + #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}") + global Inoise_baseline + global VersionDspSoftware + if line.startswith("SW-Version"): + VersionDspSoftware = line.split(":", 1)[1].strip() # remember the DSP software version from its reporting + print(f"DSP SW Version={VersionDspSoftware}\r\n") if not line.startswith("Va="): # skip lines that are not to be analyzed return if state["remaining_receive_lines"] > 0 : @@ -223,7 +247,7 @@ class TelnetReader: # 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 + response = f"\rr331\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")) @@ -236,12 +260,17 @@ class TelnetReader: # 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 + if state["freq"]>3.0: + Inoise_baseline = 0.8*Inoise_baseline + 0.2*data_rows[-1][12] # remember last baseline around 3Hz (assuming top-down scanning) + if data_rows and (data_rows[-1][11]>state["allowed_noise_level"] or data_rows[-1][12]>state["allowed_noise_level"] or (state["freq"]<3.0 and data_rows[-1][12]>1.25*Inoise_baseline)): + # Too much noise: add to blacklist + print("Too much noise - adding to blacklist") + blacklist.append({"Freq": state["freq"], "NrToSkip": state["skip_scans_for_noisy_freqs"]}) + print(f"Blacklist: {blacklist}\r") + del data_rows[-1] # remove this last entry from the list (for DB it is okay, but for CSV things will shift) + else: + 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): # XXXDB + 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 @@ -253,8 +282,8 @@ class TelnetReader: 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"]