Index: CN0_Python_Scripts/.idea/.gitignore
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/.gitignore (revision 0)
+++ CN0_Python_Scripts/.idea/.gitignore (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
Index: CN0_Python_Scripts/.idea/CN0_Python_Scripts.iml
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/CN0_Python_Scripts.iml (revision 0)
+++ CN0_Python_Scripts/.idea/CN0_Python_Scripts.iml (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: CN0_Python_Scripts/.idea/inspectionProfiles/profiles_settings.xml
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/inspectionProfiles/profiles_settings.xml (revision 0)
+++ CN0_Python_Scripts/.idea/inspectionProfiles/profiles_settings.xml (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
Index: CN0_Python_Scripts/.idea/misc.xml
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/misc.xml (revision 0)
+++ CN0_Python_Scripts/.idea/misc.xml (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
Index: CN0_Python_Scripts/.idea/modules.xml
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/modules.xml (revision 0)
+++ CN0_Python_Scripts/.idea/modules.xml (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: CN0_Python_Scripts/.idea/vcs.xml
===================================================================
diff -u
--- CN0_Python_Scripts/.idea/vcs.xml (revision 0)
+++ CN0_Python_Scripts/.idea/vcs.xml (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
Index: CN0_Python_Scripts/EVAL_CN0359_REV_B.py
===================================================================
diff -u -r685d8eb11e2a49f2c8f7a270e379186415594601 -r855524c483c89b2cabf1b0fdb91227d5614b1639
--- CN0_Python_Scripts/EVAL_CN0359_REV_B.py (.../EVAL_CN0359_REV_B.py) (revision 685d8eb11e2a49f2c8f7a270e379186415594601)
+++ CN0_Python_Scripts/EVAL_CN0359_REV_B.py (.../EVAL_CN0359_REV_B.py) (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -1,10 +1,4 @@
-import numpy as np
import time
-from datetime import datetime
-import pandas as pd
-import os
-from serial import Serial
-import struct
import commands_rev_b as commands
# load the Python API for the Analog Devices CN0359 evaluation board
@@ -79,54 +73,12 @@
commands.cmd_get_cal_data(cond1)
time.sleep(sleep_time)
-# Update Coefficient 1 and read response in ASCII format
-commands.cmd_setcal(cond1, 1, 3.1)
-time.sleep(sleep_time)
+# Set Coefficients value 1..12 in order
+for idx, value in enumerate(commands.exp_coeff, start=1):
+ # Update coefficient idx and read response in ASCII format
+ commands.cmd_setcal(cond1, idx, value)
+ time.sleep(sleep_time)
-# Update Coefficient 2 and read response in ASCII format
-commands.cmd_setcal(cond1, 2, 0.006)
-time.sleep(sleep_time)
-
-# Update Coefficient 3 and read response in ASCII format
-commands.cmd_setcal(cond1, 3, 0.6)
-time.sleep(sleep_time)
-
-# Update Coefficient 4 and read response in ASCII format
-commands.cmd_setcal(cond1, 4, -1.0)
-time.sleep(sleep_time)
-
-# Update Coefficient 5 and read response in ASCII format
-commands.cmd_setcal(cond1, 5, 3.1)
-time.sleep(sleep_time)
-
-# Update Coefficient 6 and read response in ASCII format
-commands.cmd_setcal(cond1, 6, 0.03)
-time.sleep(sleep_time)
-
-# Update Coefficient 7 and read response in ASCII format
-commands.cmd_setcal(cond1, 7, 2.0)
-time.sleep(sleep_time)
-
-# Update Coefficient 8 and read response in ASCII format
-commands.cmd_setcal(cond1, 8, 999.0)
-time.sleep(sleep_time)
-
-# Update Coefficient 9 and read response in ASCII format
-commands.cmd_setcal(cond1, 9, 1.5)
-time.sleep(sleep_time)
-
-# Update Coefficient 10 and read response in ASCII format
-commands.cmd_setcal(cond1, 10, 7.1)
-time.sleep(sleep_time)
-
-# Update Coefficient 11 and read response in ASCII format
-commands.cmd_setcal(cond1, 11, 100.11)
-time.sleep(sleep_time)
-
-# Update Coefficient 12 and read response in ASCII format
-commands.cmd_setcal(cond1, 12, 100.12)
-time.sleep(sleep_time)
-
# Read Calibration data in ASCII format to confirm the changes were made
commands.cmd_get_cal_data(cond1)
time.sleep(sleep_time)
@@ -136,11 +88,11 @@
time.sleep(sleep_time)
# Update Hardware Version Number and read response in ASCII format
-commands.cmd_set_hardware_ver(cond1)
+commands.cmd_set_hardware_ver(cond1, commands.exp_hw_ver)
time.sleep(sleep_time)
# Update Serial Number and read response in ASCII format
-commands.cmd_set_serial_num(cond1)
+commands.cmd_set_serial_num(cond1, commands.exp_sn)
time.sleep(sleep_time)
# Read Version information in ASCII format to confirm the changes were made
@@ -151,4 +103,27 @@
commands.cmd_get_cal_ver_bin(cond1)
time.sleep(sleep_time)
+########################################################################################################################
+# Below code is for data analysis
+# commands.cmd_stop_poll_bin(cond1)
+# time.sleep(sleep_time)
+# commands.cmd_frequency(cond1,10000.0)
+# time.sleep(sleep_time)
+# commands.cmd_poll(cond1)
+# time.sleep(sleep_time)
+
+# # Reading Auto polled data for different durations
+# read_durations = [30, 60, 120, 240, 480]
+# for read_duration in read_durations:
+# print("Read Duration:", read_duration)
+# commands.cmd_start_poll_bin(cond1)
+# time.sleep(sleep_time)
+# commands.printTimestamp()
+# commands.read_autopoll(cond1, read_duration)
+# commands.printTimestamp()
+# time.sleep(sleep_time)
+# commands.cmd_stop_poll_bin(cond1)
+# time.sleep(sleep_time)
+
+
Index: CN0_Python_Scripts/__pycache__/commands_rev_b.cpython-313.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/__pycache__/data_analysis.cpython-313.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/__pycache__/parser.cpython-313.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/autopoll_log.xlsx
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/commands_rev_b.py
===================================================================
diff -u -r2b4e5017c953ae23dfbedf1776eec9e492647aa9 -r855524c483c89b2cabf1b0fdb91227d5614b1639
--- CN0_Python_Scripts/commands_rev_b.py (.../commands_rev_b.py) (revision 2b4e5017c953ae23dfbedf1776eec9e492647aa9)
+++ CN0_Python_Scripts/commands_rev_b.py (.../commands_rev_b.py) (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -2,6 +2,11 @@
import struct
import time
from datetime import datetime
+
+exp_coeff = [3.1, 0.006, 0.6, -1.0, 3.1, 0.03, 2.0, 999.0, 1.5, 7.1, 100.11, 100.12]
+exp_fw_ver = "v0.1.0"
+exp_hw_ver = "EFGH"
+exp_sn = "IJKL"
########################################################################################################################
# This function reads the sensor data and settings
# {"poll", cmd_poll},
@@ -136,7 +141,7 @@
data = cond1.ser.read(7)
data_count = data_count + 1
# print(data)
- # parser.parse_autopoll(data)
+ parser.parse_autopoll(data)
print(data_count)
########################################################################################################################
@@ -208,8 +213,7 @@
########################################################################################################################
# This function updates the hardware version number in the flash.
# {"sethw", cmd_set_hardware_ver},
-def cmd_set_hardware_ver(cond1):
- value = "EFGH"
+def cmd_set_hardware_ver(cond1, value):
cmd = f"sethw {value}\n"
cond1.ser.reset_input_buffer()
cond1.ser.write(cmd.encode("ascii"))
@@ -225,8 +229,7 @@
########################################################################################################################
# This function updates the serial number in the flash.
# {"setsn", cmd_set_serial_num},
-def cmd_set_serial_num(cond1):
- value = "IJKL"
+def cmd_set_serial_num(cond1, value):
cmd = f"setsn {value}\n"
cond1.ser.reset_input_buffer()
cond1.ser.write(cmd.encode("ascii"))
@@ -248,7 +251,9 @@
print("Sent command cmd_get_cal_ver_bin")
data = cond1.ser.read(80)
print(data)
- parser.parse_coeff_and_version(data)
+ # parser.parse_coeff_and_version(data)
+ status = parser.verify_coeffs_and_versions(data, exp_coeff, exp_fw_ver,exp_hw_ver,exp_sn,1e-5)
+ parser.print_verification_results(status)
########################################################################################################################
# This function prints the current data and time
Index: CN0_Python_Scripts/data_analysis.py
===================================================================
diff -u
--- CN0_Python_Scripts/data_analysis.py (revision 0)
+++ CN0_Python_Scripts/data_analysis.py (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,135 @@
+
+from pathlib import Path
+from datetime import datetime
+import atexit
+from openpyxl import Workbook, load_workbook
+
+EXCEL_PATH = "autopoll_log.xlsx"
+
+# Keep workbook open for performance
+_wb = None
+_ws_map = {}
+_pending_writes = 0
+
+# Per-sheet headers (include units in the Value column)
+HEADER_MAP = {
+ "Conductivity": ["Number", "Date", "Time", "Value (S/cm)", "Read Count", "Error"],
+ "Temperature": ["Number", "Date", "Time", "Value (°C)", "Read Count", "Error"],
+}
+
+########################################################################################################################
+
+def _headers_match(ws, expected_headers):
+ """Check if the first row in ws matches the expected headers exactly."""
+ row = [ws.cell(row=1, column=c).value for c in range(1, len(expected_headers) + 1)]
+ return row == expected_headers
+
+def _write_headers(ws, headers):
+ """Write headers to the first row."""
+ for col, h in enumerate(headers, start=1):
+ ws.cell(row=1, column=col).value = h
+
+def _get_or_create_workbook(path: str):
+ """Open workbook if exists, otherwise create it. Ensure required sheets and headers."""
+ global _wb, _ws_map
+
+ if _wb is not None:
+ return _wb
+
+ p = Path(path)
+
+ if p.exists():
+ _wb = load_workbook(p)
+ else:
+ _wb = Workbook() # default contains one sheet; we’ll clean it up later
+
+ def ensure_sheet(name: str, headers: list[str]):
+ # Create or get the sheet
+ if name in _wb.sheetnames:
+ ws = _wb[name]
+ else:
+ ws = _wb.create_sheet(title=name)
+
+ # If empty or headers mismatch, (re)write headers
+ # "Empty" heuristic: either only header row exists and it's blank, or sheet is brand new
+ is_probably_empty = (
+ ws.max_row == 1 and
+ all(ws.cell(row=1, column=c).value is None for c in range(1, len(headers) + 1))
+ )
+ if is_probably_empty or not _headers_match(ws, headers):
+ _write_headers(ws, headers)
+
+ return ws
+
+ # Ensure both sheets with their respective headers
+ _ws_map["Conductivity"] = ensure_sheet("Conductivity", HEADER_MAP["Conductivity"])
+ _ws_map["Temperature"] = ensure_sheet("Temperature", HEADER_MAP["Temperature"])
+
+ # Remove default "Sheet" if unused and not one of our targets
+ if "Sheet" in _wb.sheetnames and len(_wb.sheetnames) > 2:
+ ws_default = _wb["Sheet"]
+ if (
+ ws_default.max_row == 1 and
+ ws_default.max_column == 1 and
+ ws_default["A1"].value is None
+ ):
+ _wb.remove(ws_default)
+
+ return _wb
+
+
+def _next_index(ws):
+ """Return next sequential Number for this sheet, based on the last used row."""
+ # Row 1 is header; data starts at row 2
+ if ws.max_row < 2:
+ return 1
+ last_num = ws.cell(row=ws.max_row, column=1).value
+ try:
+ return int(last_num) + 1
+ except (TypeError, ValueError):
+ # Fallback based on row count (data rows = max_row - 1)
+ return (ws.max_row - 1) + 1
+
+
+def log_to_excel(kind: str, ts: datetime, value: float, read_count: int, error: int, path: str = EXCEL_PATH):
+ """
+ Append a row to the Excel workbook.
+
+ kind: "Conductivity" or "Temperature"
+ ts: datetime timestamp
+ value: float
+ read_count: int
+ error: int
+ """
+ global _pending_writes
+
+ if kind not in HEADER_MAP:
+ raise ValueError(f"Unsupported kind '{kind}'. Expected one of: {list(HEADER_MAP.keys())}")
+
+ wb = _get_or_create_workbook(path)
+ ws = _ws_map[kind]
+
+ n = _next_index(ws)
+ date_str = ts.date().isoformat()
+ time_str = ts.time().isoformat() # includes microseconds if present
+
+ ws.append([n, date_str, time_str, float(value), int(read_count), int(error)])
+
+ _pending_writes += 1
+
+ # Save periodically to avoid losing data if the script stops unexpectedly
+ if _pending_writes >= 25: # adjust as you like
+ wb.save(path)
+ _pending_writes = 0
+
+
+def flush_excel(path: str = EXCEL_PATH):
+ """Force-save any pending writes."""
+ global _wb, _pending_writes
+ if _wb is not None:
+ _wb.save(path)
+ _pending_writes = 0
+
+
+# Auto-save when program exits
+atexit.register(flush_excel)
Index: CN0_Python_Scripts/modules/__init__.py
===================================================================
diff -u
--- CN0_Python_Scripts/modules/__init__.py (revision 0)
+++ CN0_Python_Scripts/modules/__init__.py (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1 @@
\ No newline at end of file
Index: CN0_Python_Scripts/modules/__pycache__/__init__.cpython-312.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/__pycache__/__init__.cpython-313.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/__pycache__/__init__.cpython-38.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/__pycache__/analog_devices_conductivity_board.cpython-312.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/__pycache__/analog_devices_conductivity_board.cpython-313.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/__pycache__/analog_devices_conductivity_board.cpython-38.pyc
===================================================================
diff -u
Binary files differ
Index: CN0_Python_Scripts/modules/analog_devices_conductivity_board.py
===================================================================
diff -u
--- CN0_Python_Scripts/modules/analog_devices_conductivity_board.py (revision 0)
+++ CN0_Python_Scripts/modules/analog_devices_conductivity_board.py (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -0,0 +1,141 @@
+###########################################################################
+#
+# Copyright (c) 2024-2024 Diality Inc. - All Rights Reserved.
+#
+# THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+# WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+#
+# @file analog_devices_conductivity_board.py
+#
+# @author (original) Christina Heine
+# @date (original) 08-October-2024
+# @author (last) Eliza Petersen
+# @date (last) 15-November-2024
+#
+############################################################################
+
+from serial import Serial
+import re
+import time
+
+class AnalogDevicesConductivityBoard:
+ def __init__(self):
+ self.ser = None
+ self.run = False
+ self.pressure = None
+ self.id = None
+ self.address = None
+
+ def create_connection(self, port_name, address):
+ """Creates serial connection to device using known port number."""
+ self.address = address.encode('UTF-8')
+ self.ser = Serial(port_name, baudrate=115200)
+
+ def get_poll(self):
+ self.ser.write(self.address + b' poll\n')
+ # poll = str(self.ser.read(385).decode()) #decode by number of characters - this may change
+ i = 0
+ poll = ""
+ while i <= 21: # decode by number of lines of data messages
+ current_line_text = str(self.ser.readline().decode())
+ poll = poll + current_line_text
+ i += 1
+
+ return poll
+
+ def get_poll_more_lines(self, lines_index):
+ #This function is used for debugging
+ self.ser.write(self.address + b' poll\n')
+ # poll = str(self.ser.read(385).decode()) #decode by number of characters - this may change
+ i = 0
+ poll = ""
+ while i <= lines_index: # decode by number of lines of data messages
+ current_line_text = str(self.ser.readline().decode())
+ poll = poll + current_line_text
+ i += 1
+
+ return poll
+
+ def decode_last_poll(self, last_poll, lines_index):
+ # This function is used for debugging
+ i = 0
+
+ while i <= lines_index: # decode by number of lines of data messages
+ current_line_text = str(self.ser.readline().decode())
+ poll = poll + current_line_text
+ i += 1
+
+ return poll
+
+ def get_cond(self):
+ measurements = self.get_poll()
+ cond = float(re.search('conductivity: (.*)S/cm', measurements).group(1)) * 1000000.00
+ return cond
+
+ def set_frequency(self, freq):
+ freq = str(freq)
+ byte_freq = freq.encode('UTF-8')
+ self.ser.write(self.address + b' setfreq ' + byte_freq + b'\n')
+ time.sleep(1)
+ self.ser.write(self.address + b' poll\n')
+ i = 0
+ set_freq_message = ""
+ while i <= 23: #
+ current_line_text = str(self.ser.readline().decode())
+ set_freq_message = set_freq_message + current_line_text
+ i += 1
+
+ return set_freq_message
+
+ def set_potential(self, voltage):
+ voltage = str(voltage)
+ byte_voltage = voltage.encode('UTF-8')
+ self.ser.write(self.address + b' setvolt ' + byte_voltage + b'\n')
+
+ time.sleep(0.5) # This delay is critical or else next poll will happen before the set voltage line is
+ # added to the poll, and won't properly clear out the extra message that adds to the buffer after setting
+ # potential, affecting the next get poll
+
+ self.ser.write(self.address + b' poll\n')
+ i = 0
+ set_pot_message = ""
+ while i <= 23:
+ current_line_text = str(self.ser.readline().decode())
+ set_pot_message = set_pot_message + current_line_text
+ i += 1
+
+ return set_pot_message
+
+ def get_set_frequency(self):
+ measurements = self.get_poll()
+ set_freq = float(re.search('EXC FREQ: (.*)Hz', measurements).group(1))
+ return set_freq
+
+ def get_set_potential(self):
+ measurements = self.get_poll()
+ set_pot = float(re.search('EXC V: (.*)V', measurements).group(1))
+ return set_pot
+
+ def check_frequency(self, target_frequency):
+ frequency_set = False
+ set_frequency = self.get_set_frequency()
+ if set_frequency != target_frequency:
+ print(f'Frequency incorrectly set! \n'
+ f'Target frequency: {target_frequency} V\n'
+ f'Set frequency: {set_frequency} V')
+ return frequency_set
+ else:
+ frequency_set = True
+ return frequency_set
+
+ def check_potential(self, target_potential):
+ potential_set = False
+ set_potential = self.get_set_potential()
+ if set_potential != target_potential:
+ print(f'Potential incorrectly set! \n'
+ f'Target potential: {target_potential} V\n'
+ f'Set potential: {set_potential} V')
+ else:
+ potential_set = True
+
+ return potential_set
\ No newline at end of file
Index: CN0_Python_Scripts/parser.py
===================================================================
diff -u -r685d8eb11e2a49f2c8f7a270e379186415594601 -r855524c483c89b2cabf1b0fdb91227d5614b1639
--- CN0_Python_Scripts/parser.py (.../parser.py) (revision 685d8eb11e2a49f2c8f7a270e379186415594601)
+++ CN0_Python_Scripts/parser.py (.../parser.py) (revision 855524c483c89b2cabf1b0fdb91227d5614b1639)
@@ -1,6 +1,8 @@
import struct
+from datetime import datetime
+import data_analysis
########################################################################################################################
# This function parses and prints the received packet during automated polling
@@ -24,10 +26,14 @@
_id, value, read_count, error = struct.unpack(' str:
- # decode up to first NUL, tolerate missing terminator
raw = field_bytes.split(b'\x00', 1)[0]
return raw.decode('utf-8', errors='replace').strip()
def take_field(max_len: int) -> bytes:
- """
- Take up to max_len bytes from packet starting at i.
- If fewer than max_len bytes remain, take what's left (tolerate truncated padding).
- """
nonlocal i
- chunk = packet[i:i + max_len]
+ chunk = packet[i:i+max_len]
i += len(chunk)
return chunk
- # Read 3 fields, each up to MAX_VERSION_LENGTH, but do NOT require full padding bytes.
fw_bytes = take_field(MAX_VERSION_LENGTH)
hw_bytes = take_field(MAX_VERSION_LENGTH)
sn_bytes = take_field(MAX_VERSION_LENGTH)
@@ -84,13 +77,71 @@
hw = parse_ver_field(hw_bytes)
sn = parse_ver_field(sn_bytes)
- print("Versions:")
- print(" FW:", fw)
- print(" HW:", hw)
- print(" SN:", sn)
+ # Return structured data
+ return {
+ "coeffs": coeffs,
+ "fw": fw,
+ "hw": hw,
+ "sn": sn,
+ }
- # If there’s extra trailing data, show it (optional but useful)
- if i < len(packet):
- print(f"Trailing bytes ({len(packet)-i}): {packet[i:]}")
########################################################################################################################
+
+
+def verify_coeffs_and_versions(packet: bytes,
+ expected_coeffs,
+ expected_fw,
+ expected_hw,
+ expected_sn,
+ float_tol=1e-5):
+ parsed = parse_coeff_and_version(packet)
+
+ coeffs_ok = True
+ coeff_compare = []
+ for i, (exp, got) in enumerate(zip(expected_coeffs, parsed["coeffs"]), start=1):
+ match = abs(exp - got) <= float_tol
+ coeff_compare.append((i, exp, got, match))
+ if not match:
+ coeffs_ok = False
+
+ fw_ok = (parsed["fw"] == expected_fw)
+ hw_ok = (parsed["hw"] == expected_hw)
+ sn_ok = (parsed["sn"] == expected_sn)
+
+ overall_ok = coeffs_ok and fw_ok and hw_ok and sn_ok
+
+ return {
+ "overall_match": overall_ok,
+ # Coeffs already carry expected & received per row
+ "coeff_details": coeff_compare,
+ # Versions: include both expected and parsed for pretty printing
+ "versions": {
+ "fw": {"expected": expected_fw, "received": parsed["fw"], "match": fw_ok},
+ "hw": {"expected": expected_hw, "received": parsed["hw"], "match": hw_ok},
+ "sn": {"expected": expected_sn, "received": parsed["sn"], "match": sn_ok},
+ },
+ # Keep the raw parsed payload too (optional)
+ "parsed": parsed,
+ }
+
+########################################################################################################################
+
+def print_verification_results(status):
+ print(f"Overall Match: {status['overall_match']}\n")
+
+ # ---- Coeff Table ----
+ print(f"{'Coeff':<8} {'Expected':>12} {'Received':>12} {'Match':>8}")
+ for idx, exp, got, ok in status["coeff_details"]:
+ print(f"{idx:<8} {exp:>12.2f} {got:>12.2f} {str(ok):>8}")
+
+ # ---- Version Table ----
+ print("\n" + f"{'Field':<12} {'Expected':>12} {'Received':>12} {'Match':>8}")
+ for field in ("fw", "hw", "sn"):
+ row = status["versions"][field]
+ label = field.upper()
+ print(f"{label:<12} {row['expected']:>12} {row['received']:>12} {str(row['match']):>8}")
+
+
+
+########################################################################################################################
\ No newline at end of file