From 8f3fdaa66ff5732b60cba1ff341b189c2085f161 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 6 Nov 2025 18:44:14 -0500 Subject: [PATCH 01/19] Test script for recieving arudino i2c data serially with pi --- Testing_Library/test_motor_interface.py | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Testing_Library/test_motor_interface.py diff --git a/Testing_Library/test_motor_interface.py b/Testing_Library/test_motor_interface.py new file mode 100644 index 0000000..3f67661 --- /dev/null +++ b/Testing_Library/test_motor_interface.py @@ -0,0 +1,51 @@ +import sys +import serial +sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') + +THROTTLE_ADDR = 0x2F +REGEN_ADDR = 0x2E + + +''' +Both the throttle and regen are 9 it bit values, send them both at once in +a 3 byte value and then just read based on there +''' +# TODO: Serial Configuration +ser = serial.Serial( + port = '/dev/ttyAMA0', + baudrate =115200, + timeout = 1 +) + + +while True: + if ser.read(1) == b'\xFF': + data = ser.read(2) + if len(data) == 2: + throttle_data = data[0] + regen_data = data[1] + print(f"Regen: {regen_data} Throttle: {throttle_data}") + + +''' +API for test Moto interface +''' +class Test_Motor_Interface: + def __init__(self): + self.mru_throttle = None + self.mru_regen = None + + def get_throttle() -> float: + # TODO Return throttle value as a double between [0-1] + + pass + def get_throttle_raw() -> int: + # TODO Return throttle value between [0-255] + pass + def get_regen() -> float: + # TODO Return throttle value between [0-1] + pass + def get_regen_raw() -> int: + # TODO Return throttle value between [0-255] + pass + From ad10ce532411ba1be8621ddad663d63965b0400f Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 6 Nov 2025 19:28:42 -0500 Subject: [PATCH 02/19] debuggin output to motor_interface.py --- Testing_Library/test_motor_interface.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Testing_Library/test_motor_interface.py b/Testing_Library/test_motor_interface.py index 3f67661..dd0f529 100644 --- a/Testing_Library/test_motor_interface.py +++ b/Testing_Library/test_motor_interface.py @@ -6,6 +6,8 @@ REGEN_ADDR = 0x2E + + ''' Both the throttle and regen are 9 it bit values, send them both at once in a 3 byte value and then just read based on there @@ -19,13 +21,16 @@ while True: - if ser.read(1) == b'\xFF': + byte_read = ser.read(1) + if byte_read == b'\xFF': data = ser.read(2) if len(data) == 2: throttle_data = data[0] regen_data = data[1] print(f"Regen: {regen_data} Throttle: {throttle_data}") - + else: + print("looping") + print(f"byte_read{byte_read}") ''' API for test Moto interface @@ -49,3 +54,4 @@ def get_regen_raw() -> int: # TODO Return throttle value between [0-255] pass + From cead4bc2f84a41d2a81fff9fe24ec9c145e357c4 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 6 Nov 2025 19:43:58 -0500 Subject: [PATCH 03/19] 3 bytes read for motorinterface.py --- Testing_Library/test_motor_interface.py | 45 ++++++++++++++----------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Testing_Library/test_motor_interface.py b/Testing_Library/test_motor_interface.py index dd0f529..230967c 100644 --- a/Testing_Library/test_motor_interface.py +++ b/Testing_Library/test_motor_interface.py @@ -1,36 +1,41 @@ import sys import serial +import time + sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') THROTTLE_ADDR = 0x2F -REGEN_ADDR = 0x2E - - +REGEN_ADDR = 0x2E - -''' -Both the throttle and regen are 9 it bit values, send them both at once in -a 3 byte value and then just read based on there -''' -# TODO: Serial Configuration +# Serial Configuration ser = serial.Serial( - port = '/dev/ttyAMA0', - baudrate =115200, - timeout = 1 + port='/dev/ttyAMA0', + baudrate=115200, + timeout=1 ) +print("Waiting for Arduino to start...") +time.sleep(2) # Give Arduino time to boot while True: byte_read = ser.read(1) if byte_read == b'\xFF': - data = ser.read(2) - if len(data) == 2: - throttle_data = data[0] - regen_data = data[1] - print(f"Regen: {regen_data} Throttle: {throttle_data}") - else: - print("looping") - print(f"byte_read{byte_read}") + data = ser.read(3) # Read 3 bytes for two 9-bit values + if len(data) == 3: + # Unpack two 9-bit values from 3 bytes + # Byte 0: throttle[7:0] + # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 + # Byte 2: regen[8:7] in bits 1:0 + + # Extracting throttle and regen + throttle = data[0] | ((data[1] & 0x01) << 8) + regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) + + print(f"Throttle: {throttle}%) | Regen: {regen}}%)") + else: + print(f"Incomplete data: got {len(data)} bytes instead of 3") + elif byte_read: + print(f"Unexpected byte: {byte_read}") ''' API for test Moto interface From c35156df3ac50bc0d4cb6d4f42f25141c4c14ed7 Mon Sep 17 00:00:00 2001 From: Colby Wise Date: Mon, 20 Oct 2025 01:00:44 -0400 Subject: [PATCH 04/19] Working serial communication with pi and arduino --- Testing_Library/test_motor_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Testing_Library/test_motor_interface.py b/Testing_Library/test_motor_interface.py index 230967c..e046d2f 100644 --- a/Testing_Library/test_motor_interface.py +++ b/Testing_Library/test_motor_interface.py @@ -9,8 +9,8 @@ # Serial Configuration ser = serial.Serial( - port='/dev/ttyAMA0', - baudrate=115200, + port='/dev/ttyACM0', + baudrate=9600, timeout=1 ) @@ -31,11 +31,11 @@ throttle = data[0] | ((data[1] & 0x01) << 8) regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) - print(f"Throttle: {throttle}%) | Regen: {regen}}%)") + print(f"Throttle: {throttle}%) | Regen: {regen})") else: print(f"Incomplete data: got {len(data)} bytes instead of 3") elif byte_read: - print(f"Unexpected byte: {byte_read}") + print(f"Unexpected byte:{byte_read}") ''' API for test Moto interface From 8fcfa438bbc58fdbcf3c5ba884d23037406b55cf Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Sun, 9 Nov 2025 14:32:15 -0500 Subject: [PATCH 05/19] implementation of MotorInterfaceClass --- Testing_Library/MotorInterface.py | 122 ++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Testing_Library/MotorInterface.py diff --git a/Testing_Library/MotorInterface.py b/Testing_Library/MotorInterface.py new file mode 100644 index 0000000..00dde40 --- /dev/null +++ b/Testing_Library/MotorInterface.py @@ -0,0 +1,122 @@ +import sys +import serial +import time +import threading + + +sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') + +THROTTLE_ADDR = 0x2F +REGEN_ADDR = 0x2E + +# Serial Configuration +ser = serial.Serial( + port='/dev/ttyACM0', + baudrate=9600, + timeout=1 +) + +print("Waiting for Arduino to start...") +time.sleep(2) # Give Arduino time to boot +''' +while True: + byte_read = ser.read(1) + if byte_read == b'\xFF': + data = ser.read(3) # Read 3 bytes for two 9-bit values + if len(data) == 3: + # Unpack two 9-bit values from 3 bytes + # Byte 0: throttle[7:0] + # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 + # Byte 2: regen[8:7] in bits 1:0 + + # Extracting throttle and regen + throttle = data[0] | ((data[1] & 0x01) << 8) + regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) + + print(f"Throttle: {throttle}%) | Regen: {regen})") + else: + print(f"Incomplete data: got {len(data)} bytes instead of 3") + elif byte_read: + print(f"Unexpected byte:{byte_read}") +''' +ser = serial.Serial( + port='/dev/ttyACM0', + baudrate=9600, + timeout=1 + ) +''' +API for test Motor interface +''' +class MotorInterface: + def __init__(self): + self.stop_thread = False + self.mru_throttle = None + self.mru_regen = None + self.THROTTLE_ADDR = 0x2F + self.REGEN_ADDR = 0x2E + self.startReadThread() + + + #Run infinite thread to read in Throttle and Regen Values + def startReadThread(self): + read_thread = threading.Thread(target=self.readThrottleAndRegen) + read_thread.daemon = True + try: + read_thread.start() + except ValueError as e: + print(f"Exception") + def readThrottleAndRegen(self): + while(not self.stop_thread): + with self.lock: + byte_read = ser.read(1) + if byte_read == b'\xFF': # Start Byte + data = ser.read(3) # Read 3 bytes for two 9-bit values + if len(data) == 3: + # two 9-bit values from 3 bytes + # Byte 0: throttle[7:0] + # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 + # Byte 2: regen[8:7] in bits 1:0 + + # Extracting throttle and regen + self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) + self.mru_regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) + + def get_throttle(self) -> float: + # Normalized between [0-1.0] + if self.mru_throttle is None: + return None + return self.mru_throttle/256.0 + + def get_throttle_raw(self) -> int: + # raw between [0-256] + if self.mru_throttle is None: + return None + return self.mru_throttle + + def get_regen(self) -> float: + # Normalized between [0,1.0] + if self.mru_regen is None: + return None + return self.mru_regen/256.0 + + def get_regen_raw(self) -> int: + # raw between [0-256] + if self.mru_regen is None: + return None + return self.mru_regen + + +motor_interface = MotorInterface() + +while(True): + throttle_raw = motor_interface.get_throttle_raw() + throttle_norm = motor_interface.get_throttle() + regen_raw = motor_interface.get_regen_raw() + regen_norm = motor_interface.get_regen() + + + + + + + From efcdb30735ee4fdb55df9be4d940bfef7c297ad8 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Sun, 9 Nov 2025 14:50:26 -0500 Subject: [PATCH 06/19] fixes to self.lock --- Testing_Library/MotorInterface.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Testing_Library/MotorInterface.py b/Testing_Library/MotorInterface.py index 00dde40..1b5c4d1 100644 --- a/Testing_Library/MotorInterface.py +++ b/Testing_Library/MotorInterface.py @@ -54,6 +54,7 @@ def __init__(self): self.mru_regen = None self.THROTTLE_ADDR = 0x2F self.REGEN_ADDR = 0x2E + self.Lock = threading.lock() self.startReadThread() @@ -67,20 +68,20 @@ def startReadThread(self): print(f"Exception") def readThrottleAndRegen(self): while(not self.stop_thread): - with self.lock: - byte_read = ser.read(1) - if byte_read == b'\xFF': # Start Byte - data = ser.read(3) # Read 3 bytes for two 9-bit values - if len(data) == 3: - # two 9-bit values from 3 bytes - # Byte 0: throttle[7:0] - # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 - # Byte 2: regen[8:7] in bits 1:0 - - # Extracting throttle and regen - self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) - self.mru_regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) - + with self.Lock: + byte_read = ser.read(1) + if byte_read == b'\xFF': # Start Byte + data = ser.read(3) # Read 3 bytes for two 9-bit values + if len(data) == 3: + # two 9-bit values from 3 bytes + # Byte 0: throttle[7:0] + # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 + # Byte 2: regen[8:7] in bits 1:0 + + # Extracting throttle and regen + self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) + self.mru_regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) + def get_throttle(self) -> float: # Normalized between [0-1.0] if self.mru_throttle is None: From 1eb35c8aa05daef16f04e89150055fed4ab4eecf Mon Sep 17 00:00:00 2001 From: Colby Wise Date: Mon, 20 Oct 2025 01:29:44 -0400 Subject: [PATCH 07/19] confirmed working implemntatin of MotorInterface APi --- Testing_Library/MotorInterface.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Testing_Library/MotorInterface.py b/Testing_Library/MotorInterface.py index 1b5c4d1..425f554 100644 --- a/Testing_Library/MotorInterface.py +++ b/Testing_Library/MotorInterface.py @@ -54,7 +54,7 @@ def __init__(self): self.mru_regen = None self.THROTTLE_ADDR = 0x2F self.REGEN_ADDR = 0x2E - self.Lock = threading.lock() + self.Lock = threading.Lock() self.startReadThread() @@ -110,14 +110,15 @@ def get_regen_raw(self) -> int: motor_interface = MotorInterface() while(True): + throttle_raw = motor_interface.get_throttle_raw() throttle_norm = motor_interface.get_throttle() regen_raw = motor_interface.get_regen_raw() regen_norm = motor_interface.get_regen() - - - - - - + print("Details") + print(throttle_raw) + print(throttle_norm) + print(regen_raw) + print(regen_norm) + time.sleep(1) From 1dee8c58f0bf162d730ea2b66e190af5f81b1a40 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 13 Nov 2025 18:39:09 -0500 Subject: [PATCH 08/19] updates to motor interface API --- Testing_Library/MotorInterfaceTest.py | 130 ++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Testing_Library/MotorInterfaceTest.py diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py new file mode 100644 index 0000000..ce14f93 --- /dev/null +++ b/Testing_Library/MotorInterfaceTest.py @@ -0,0 +1,130 @@ +import sys +import serial +import time +import threading + + +sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') + +THROTTLE_ADDR = 0x2F +REGEN_ADDR = 0x2E +THROTTLE_START_BYTE = b'\xFF' +REGEN_START_BYTE = b'\xFE' + +# Serial Configuration +ser = serial.Serial( + port='/dev/ttyACM0', + baudrate=9600, + timeout=1 +) + +print("Waiting for Arduino to start...") +time.sleep(2) # Give Arduino time to boot +''' +while True: + byte_read = ser.read(1) + if byte_read == b'\xFF': + data = ser.read(3) # Read 3 bytes for two 9-bit values + if len(data) == 3: + # Unpack two 9-bit values from 3 bytes + # Byte 0: throttle[7:0] + # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 + # Byte 2: regen[8:7] in bits 1:0 + + # Extracting throttle and regen + throttle = data[0] | ((data[1] & 0x01) << 8) + regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) + + print(f"Throttle: {throttle}%) | Regen: {regen})") + else: + print(f"Incomplete data: got {len(data)} bytes instead of 3") + elif byte_read: + print(f"Unexpected byte:{byte_read}") +''' +ser = serial.Serial( + port='/dev/ttyACM0', + baudrate=9600, + timeout=1 + ) +''' +API for test Motor interface +''' +class MotorInterfaceTest: + def __init__(self): + self.stop_thread = False + self.mru_throttle = None + self.mru_regen = None + self.THROTTLE_ADDR = 0x2F + self.REGEN_ADDR = 0x2E + self.Lock = threading.Lock() + self.startReadThread() + + + #Run infinite thread to read in Throttle and Regen Values + def startReadThread(self): + read_thread = threading.Thread(target=self.readThrottleAndRegen) + read_thread.daemon = True + try: + read_thread.start() + except ValueError as e: + print(f"Exception") + def readThrottleAndRegen(self): + while(not self.stop_thread): + with self.Lock: + byte_read = ser.read(1) + if byte_read == THROTTLE_START_BYTE: # Throttle start marker + data = ser.read(2) # Read 2 bytes for 9-bit value + if len(data) == 2: + # Extract 9-bit value from 2 bytes + # Byte 0: value[7:0] + # Byte 1: value[8] in bit 0 + # Range: 0-256 (9 bits), but normalized to 0-256 + self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) + elif byte_read == REGEN_START_BYTE: + data = ser.read(2) # Read 2 bytes for 9-bit value + if len(data) == 2: + # Extract 9-bit value from 2 bytes + # Byte 0: value[7:0] + # Byte 1: value[8] in bit 0 + # Range: 0-256 (9 bits), but normalized to 0-256 + self.mru_regen = data[0] | ((data[1] & 0x01) << 8) + + def get_throttle(self) -> float: + # Normalized between [0-1.0] + if self.mru_throttle is None: + return None + return self.mru_throttle/256.0 + + def get_throttle_raw(self) -> int: + # raw between [0-256] + if self.mru_throttle is None: + return None + return self.mru_throttle + + def get_regen(self) -> float: + # Normalized between [0,1.0] + if self.mru_regen is None: + return None + return self.mru_regen/256.0 + + def get_regen_raw(self) -> int: + # raw between [0-256] + if self.mru_regen is None: + return None + return self.mru_regen + + +motor_interface = MotorInterfaceTest() + +while(True): + throttle_raw = motor_interface.get_throttle_raw() + throttle_norm = motor_interface.get_throttle() + regen_raw = motor_interface.get_regen_raw() + regen_norm = motor_interface.get_regen() + + + + + + + From c33d43834c9d70c197ee21616e297b1017be8ad4 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 13 Nov 2025 19:25:02 -0500 Subject: [PATCH 09/19] remove unused instance of MotorInterfaceTest and related test loop --- Testing_Library/MotorInterfaceTest.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py index ce14f93..40313fc 100644 --- a/Testing_Library/MotorInterfaceTest.py +++ b/Testing_Library/MotorInterfaceTest.py @@ -114,17 +114,6 @@ def get_regen_raw(self) -> int: return self.mru_regen -motor_interface = MotorInterfaceTest() - -while(True): - throttle_raw = motor_interface.get_throttle_raw() - throttle_norm = motor_interface.get_throttle() - regen_raw = motor_interface.get_regen_raw() - regen_norm = motor_interface.get_regen() - - - - From de7f1affae0a44ac81e43cf4dcf9f0d1b9a4fc8d Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 13 Nov 2025 20:25:33 -0500 Subject: [PATCH 10/19] Adding serial port as member of class --- Testing_Library/MotorInterfaceTest.py | 35 ++++++++++----------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py index 40313fc..3059e09 100644 --- a/Testing_Library/MotorInterfaceTest.py +++ b/Testing_Library/MotorInterfaceTest.py @@ -10,16 +10,6 @@ REGEN_ADDR = 0x2E THROTTLE_START_BYTE = b'\xFF' REGEN_START_BYTE = b'\xFE' - -# Serial Configuration -ser = serial.Serial( - port='/dev/ttyACM0', - baudrate=9600, - timeout=1 -) - -print("Waiting for Arduino to start...") -time.sleep(2) # Give Arduino time to boot ''' while True: byte_read = ser.read(1) @@ -41,22 +31,23 @@ elif byte_read: print(f"Unexpected byte:{byte_read}") ''' -ser = serial.Serial( - port='/dev/ttyACM0', - baudrate=9600, - timeout=1 - ) -''' -API for test Motor interface -''' + class MotorInterfaceTest: - def __init__(self): + """API for test Motor interface""" + def __init__(self, port='/dev/ttyACM0', baudrate=9600): self.stop_thread = False self.mru_throttle = None self.mru_regen = None self.THROTTLE_ADDR = 0x2F self.REGEN_ADDR = 0x2E self.Lock = threading.Lock() + self.ser = serial.Serial( + port=port, + baudrate=baudrate, + timeout=1 + ) + print("Waiting for Arduino to start...") + time.sleep(2) # Give Arduino time to boot self.startReadThread() @@ -71,9 +62,9 @@ def startReadThread(self): def readThrottleAndRegen(self): while(not self.stop_thread): with self.Lock: - byte_read = ser.read(1) + byte_read = self.ser.read(1) if byte_read == THROTTLE_START_BYTE: # Throttle start marker - data = ser.read(2) # Read 2 bytes for 9-bit value + data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: # Extract 9-bit value from 2 bytes # Byte 0: value[7:0] @@ -81,7 +72,7 @@ def readThrottleAndRegen(self): # Range: 0-256 (9 bits), but normalized to 0-256 self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) elif byte_read == REGEN_START_BYTE: - data = ser.read(2) # Read 2 bytes for 9-bit value + data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: # Extract 9-bit value from 2 bytes # Byte 0: value[7:0] From 2cbfa673d75213345448658cedcdc3c9bb9ea8fd Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 22 Jan 2026 17:40:21 -0500 Subject: [PATCH 11/19] fix: debug output --- Testing_Library/MotorInterfaceTest.py | 7 +-- Testing_Library/test_motor_interface.py | 62 ------------------------- 2 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 Testing_Library/test_motor_interface.py diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py index 3059e09..45d36d4 100644 --- a/Testing_Library/MotorInterfaceTest.py +++ b/Testing_Library/MotorInterfaceTest.py @@ -71,6 +71,7 @@ def readThrottleAndRegen(self): # Byte 1: value[8] in bit 0 # Range: 0-256 (9 bits), but normalized to 0-256 self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) + print(f"Throttle: {self.mru_throttle}") elif byte_read == REGEN_START_BYTE: data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: @@ -79,6 +80,7 @@ def readThrottleAndRegen(self): # Byte 1: value[8] in bit 0 # Range: 0-256 (9 bits), but normalized to 0-256 self.mru_regen = data[0] | ((data[1] & 0x01) << 8) + print(f"Regen: {self.mru_regen}") def get_throttle(self) -> float: # Normalized between [0-1.0] @@ -103,8 +105,3 @@ def get_regen_raw(self) -> int: if self.mru_regen is None: return None return self.mru_regen - - - - - diff --git a/Testing_Library/test_motor_interface.py b/Testing_Library/test_motor_interface.py deleted file mode 100644 index e046d2f..0000000 --- a/Testing_Library/test_motor_interface.py +++ /dev/null @@ -1,62 +0,0 @@ -import sys -import serial -import time - -sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') - -THROTTLE_ADDR = 0x2F -REGEN_ADDR = 0x2E - -# Serial Configuration -ser = serial.Serial( - port='/dev/ttyACM0', - baudrate=9600, - timeout=1 -) - -print("Waiting for Arduino to start...") -time.sleep(2) # Give Arduino time to boot - -while True: - byte_read = ser.read(1) - if byte_read == b'\xFF': - data = ser.read(3) # Read 3 bytes for two 9-bit values - if len(data) == 3: - # Unpack two 9-bit values from 3 bytes - # Byte 0: throttle[7:0] - # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 - # Byte 2: regen[8:7] in bits 1:0 - - # Extracting throttle and regen - throttle = data[0] | ((data[1] & 0x01) << 8) - regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) - - print(f"Throttle: {throttle}%) | Regen: {regen})") - else: - print(f"Incomplete data: got {len(data)} bytes instead of 3") - elif byte_read: - print(f"Unexpected byte:{byte_read}") - -''' -API for test Moto interface -''' -class Test_Motor_Interface: - def __init__(self): - self.mru_throttle = None - self.mru_regen = None - - def get_throttle() -> float: - # TODO Return throttle value as a double between [0-1] - - pass - def get_throttle_raw() -> int: - # TODO Return throttle value between [0-255] - pass - def get_regen() -> float: - # TODO Return throttle value between [0-1] - pass - def get_regen_raw() -> int: - # TODO Return throttle value between [0-255] - pass - - From 70d59c93ebe24ee77f68dd68178f367a3602992a Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Sun, 1 Feb 2026 14:14:21 -0500 Subject: [PATCH 12/19] test: added the arduino testscript --- Testing_Library/MotorInterfaceTest.py | 18 ++--- .../motor_interface_test_script1.29.ino | 74 +++++++++++++++++++ 2 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 Testing_Library/motor_interface_test_script1.29.ino diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py index 45d36d4..e09b2dc 100644 --- a/Testing_Library/MotorInterfaceTest.py +++ b/Testing_Library/MotorInterfaceTest.py @@ -66,20 +66,18 @@ def readThrottleAndRegen(self): if byte_read == THROTTLE_START_BYTE: # Throttle start marker data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: - # Extract 9-bit value from 2 bytes - # Byte 0: value[7:0] - # Byte 1: value[8] in bit 0 - # Range: 0-256 (9 bits), but normalized to 0-256 - self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) + # motorinterface.cpp sends (0x100 - throttle) + # first byte contains lower 8 bits, second byte LSB is the 9th bit + raw_from_arduino = data[0] | ((data[1] & 0x01) << 8) + self.mru_throttle = 256 - raw_from_arduino # PowerBoard sends (0x100 - throttle) over I2C print(f"Throttle: {self.mru_throttle}") elif byte_read == REGEN_START_BYTE: data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: - # Extract 9-bit value from 2 bytes - # Byte 0: value[7:0] - # Byte 1: value[8] in bit 0 - # Range: 0-256 (9 bits), but normalized to 0-256 - self.mru_regen = data[0] | ((data[1] & 0x01) << 8) + # motorinterface.cpp sends (0x100 - regen) + # first byte contains lower 8 bits, second byte LSB is the 9th bit + raw_from_arduino = data[0] | ((data[1] & 0x01) << 8) + self.mru_regen = 256 - raw_from_arduino # PowerBoard sends (0x100 - regen) over I2C print(f"Regen: {self.mru_regen}") def get_throttle(self) -> float: diff --git a/Testing_Library/motor_interface_test_script1.29.ino b/Testing_Library/motor_interface_test_script1.29.ino new file mode 100644 index 0000000..729edaf --- /dev/null +++ b/Testing_Library/motor_interface_test_script1.29.ino @@ -0,0 +1,74 @@ +#include + +#define THROTTLE_ADDR 0x2F +#define REGEN_ADDR 0x2E +#define THROTTLE_MARKER 0xFF +#define REGEN_MARKER 0xFE +#define SWAP_COMMAND 0x1FF + +// Test Mode: 0 = Throttle only (listen on 0x2F), 1 = Regen only (listen on 0x2E). +// Must match which test is run: test_throttle needs TEST_MODE 0, test_regen needs TEST_MODE 1. +#define TEST_MODE 0 + +int currentMode = 0; // 0 = Throttle, 1 = Regen + +void setup() { + Serial.begin(9600); + while (!Serial); + Serial.println("Arduino Starting..."); + + // Initialize based on test mode + if (TEST_MODE == 1) { + // Start in regen mode + Wire.begin(REGEN_ADDR); + currentMode = 1; + Serial.println("Initialized on REGEN_ADDR (0x2E)"); + } else { + // Start in throttle mode (default) + Wire.begin(THROTTLE_ADDR); + currentMode = 0; + Serial.println("Initialized on THROTTLE_ADDR (0x2F)"); + } + + Wire.onReceive(receiveEvent); + Serial.println("Ready to receive I2C data"); + Serial.print("TEST_MODE: "); + Serial.println(TEST_MODE); +} + +void loop() { + delay(100); +} + +void receiveEvent(int howMany) { + if (Wire.available() < 2) return; + + // Read 2 bytes (16-bit value) + int highByte = Wire.read(); + int lowByte = Wire.read(); + int value = ((highByte << 8) | lowByte) & 0x1FF; + + // Handle swap command + if (value == SWAP_COMMAND) { + //TODO: + return; + } + // Clamp value to valid range [0, 256] + if (value > 256) value = 256; + + + // Ensure value stays in valid range after inversion + if (value > 256) value = 256; + if (value < 0) value = 0; + + // Send based on current mode + if (currentMode == 0) { + Serial.write(THROTTLE_MARKER); + Serial.write(value & 0xFF); // Low byte + Serial.write((value >> 8) & 0x01); // Bit 8 + } else { + Serial.write(REGEN_MARKER); + Serial.write(value & 0xFF); // Low byte + Serial.write((value >> 8) & 0x01); // Bit 8 + } +} From cd0ed8be7704c22105e8ea1785ee389f9a48e06d Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Sun, 1 Feb 2026 14:24:40 -0500 Subject: [PATCH 13/19] added arudino script --- .../motor_interface_test_script1.29.ino | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Testing_Library/CAN-messages/motor_interface_test_script1.29.ino diff --git a/Testing_Library/CAN-messages/motor_interface_test_script1.29.ino b/Testing_Library/CAN-messages/motor_interface_test_script1.29.ino new file mode 100644 index 0000000..b676714 --- /dev/null +++ b/Testing_Library/CAN-messages/motor_interface_test_script1.29.ino @@ -0,0 +1,90 @@ +#include + +#define THROTTLE_ADDR 0x2F +#define REGEN_ADDR 0x2E +#define THROTTLE_MARKER 0xFF +#define REGEN_MARKER 0xFE +#define SWAP_COMMAND 0x1FF + +// Test Mode: 0 = Throttle only, 1 = Regen only +#define TEST_MODE 0 + +int currentMode = 0; // 0 = Throttle, 1 = Regen + +void setup() { + Serial.begin(9600); + while (!Serial); + Serial.println("Arduino Starting..."); + + // Initialize based on test mode + if (TEST_MODE == 1) { + // Start in regen mode + Wire.begin(REGEN_ADDR); + currentMode = 1; + Serial.println("Initialized on REGEN_ADDR (0x2E)"); + } else { + // Start in throttle mode (default) + Wire.begin(THROTTLE_ADDR); + currentMode = 0; + Serial.println("Initialized on THROTTLE_ADDR (0x2F)"); + } + + Wire.onReceive(receiveEvent); + Serial.println("Ready to receive I2C data"); + Serial.print("TEST_MODE: "); + Serial.println(TEST_MODE); +} + +void loop() { + delay(100); +} + +void swapMode() { + Wire.end(); + currentMode = !currentMode; + + if (currentMode == 0) { + Wire.begin(THROTTLE_ADDR); + Serial.println("Swapped to THROTTLE mode"); + } else { + Wire.begin(REGEN_ADDR); + Serial.println("Swapped to REGEN mode"); + } + Wire.onReceive(receiveEvent); +} + +void receiveEvent(int howMany) { + if (Wire.available() < 2) return; + + // Read 2 bytes (16-bit value) + int highByte = Wire.read(); + int lowByte = Wire.read(); + int value = ((highByte << 8) | lowByte) & 0x1FF; + + // Handle swap command + if (value == SWAP_COMMAND) { + if (TEST_MODE == 2) { // Only swap if in mode 2 (both) + swapMode(); + } + return; + } + + // Clamp value to valid range [0, 256] + if (value > 256) value = 256; + + + // Ensure value stays in valid range after inversion + if (value > 256) value = 256; + if (value < 0) value = 0; + + // Send based on current mode + if (currentMode == 0) { + Serial.write(THROTTLE_MARKER); + Serial.write(value & 0xFF); // Low byte + Serial.write((value >> 8) & 0x01); // Bit 8 + } else { + Serial.write(REGEN_MARKER); + Serial.write(value & 0xFF); // Low byte + Serial.write((value >> 8) & 0x01); // Bit 8 + } +} From 8f69c9830b2240ee69ad82fdc947339739a4469e Mon Sep 17 00:00:00 2001 From: Colby Wise Date: Thu, 29 Jan 2026 21:33:49 -0500 Subject: [PATCH 14/19] New File for System Wide Testing --- Testing_Library/test_all_comms.py | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 Testing_Library/test_all_comms.py diff --git a/Testing_Library/test_all_comms.py b/Testing_Library/test_all_comms.py new file mode 100644 index 0000000..3a0d928 --- /dev/null +++ b/Testing_Library/test_all_comms.py @@ -0,0 +1,51 @@ +import sys +sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') + +from CANBus import CANBus +from CANMessage import CanMessage +from CANPi import readIn +import time +import serial +from gpioPins import DigitalInput, DigitalOutput + +# Testing CAN Comms +def testAddMethod(canBus : CANBus): + canBus.printCANBus() + + input("WAITING") + #Example CANMessage + name = "BPSError" + id = 0x106 + signals = {"internal_communications_fault" : 1} + timestamp = 1.0 + can_message = CanMessage(name, id, signals, timestamp) + canBus.addToCANBus(can_message) + canBus.printCANBus() + +def testReadThread(canBus : CANBus): + counter = 0 + while (counter < 10): + print(f'Reading number {counter}') + canBus.printCANBus() + time.sleep(0.5) + counter = counter + 1 + +#Send BPSError to Nucleo (currently turn LD2 off and on) +def testWrite(canBus : CANBus): + #Example CANMessage + name = "BPSError" + id = 0x106 + signals = {"internal_communications_fault" : 1} + timestamp = 1.0 + can_message = CanMessage(name, id, signals, timestamp) + counter = 0 + while (counter < 5): + print(f'Sending number {counter}') + canBus.sendMessage(can_message) + time.sleep(2) + counter = counter + 1 + +canBus = CANBus() +#testAddMethod(canBus) +testWrite(canBus) +#testReadThread(canBus) From 235dee9d2539523fd5a69c0e29338146b26bedce Mon Sep 17 00:00:00 2001 From: Andrew Amos Date: Sun, 1 Feb 2026 15:54:07 -0500 Subject: [PATCH 15/19] Developing Testing for all Comms --- Testing_Library/test_all_comms.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Testing_Library/test_all_comms.py b/Testing_Library/test_all_comms.py index 3a0d928..5bd51be 100644 --- a/Testing_Library/test_all_comms.py +++ b/Testing_Library/test_all_comms.py @@ -45,7 +45,18 @@ def testWrite(canBus : CANBus): time.sleep(2) counter = counter + 1 -canBus = CANBus() -#testAddMethod(canBus) -testWrite(canBus) -#testReadThread(canBus) +def testCAN(): + canBus = CANBus() + testWrite(canBus) + testReadThread(canBus) + +def testAnalog(): +# def testDigital(): +# pinOut = DigitalOutput("GPIO2") +# pinIn = DigitalInput("GPIO11") +# for i in range(10): +# pinOut + +if __name__ == "__main__": + testCAN() + testAnalog() \ No newline at end of file From 79bb5d471278dacdb92c90e5e1192c69f2ca6b44 Mon Sep 17 00:00:00 2001 From: Andrew Amos Date: Sun, 8 Feb 2026 14:26:20 -0500 Subject: [PATCH 16/19] Added Test for Digital, Analog and CAN --- Testing_Library/test_all_comms.py | 70 +++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/Testing_Library/test_all_comms.py b/Testing_Library/test_all_comms.py index 5bd51be..cb1dbe9 100644 --- a/Testing_Library/test_all_comms.py +++ b/Testing_Library/test_all_comms.py @@ -7,8 +7,16 @@ import time import serial from gpioPins import DigitalInput, DigitalOutput +import random +import gpiozero +from gpioPins import AnalogOutput + +# Global Pins for Synchronization +pinOut = DigitalOutput("GPIO2") +pinIn = DigitalInput("GPIO11") + +# -----------Testing CAN Comms----------- -# Testing CAN Comms def testAddMethod(canBus : CANBus): canBus.printCANBus() @@ -23,8 +31,9 @@ def testAddMethod(canBus : CANBus): canBus.printCANBus() def testReadThread(canBus : CANBus): + time.sleep(1) # Wait for Nucleo to send CAN counter = 0 - while (counter < 10): + while (counter < 5): print(f'Reading number {counter}') canBus.printCANBus() time.sleep(0.5) @@ -39,24 +48,59 @@ def testWrite(canBus : CANBus): timestamp = 1.0 can_message = CanMessage(name, id, signals, timestamp) counter = 0 - while (counter < 5): - print(f'Sending number {counter}') - canBus.sendMessage(can_message) - time.sleep(2) - counter = counter + 1 + canBus.sendMessage(can_message) def testCAN(): canBus = CANBus() testWrite(canBus) + + pinOut.value = 0 # Block nucleo from proceeding + # Check that nucleo modified pin to 1 to switch to receiving + while pinIn.value == 0: + pinOut.value = 0 + time.sleep(0.1) + testReadThread(canBus) +#----------------Testing Analog Comms--------------- + +#Test case without the wrapper api +def send_to_nucleo(): + pinOut = gpiozero.PWMOutputDevice("GPIO5", frequency=5000) + + #Sending analog value to nucleo + while True: + pinOut.value = 0.64 + print(f"SENDING {pinOut.value}") + #pinOut.pulse() #Useful function to sweep all ranges + + time.sleep(2) + +#Test the wrapper api +def test_analog_class(pinOut): + value = round(random.uniform(1.0, 100.0), 2) # Generate random 2 d.p. float + + pinOut.write_voltage(value) + print(f'READING IN PIN VALUE: {pinOut.read()}') + def testAnalog(): -# def testDigital(): -# pinOut = DigitalOutput("GPIO2") -# pinIn = DigitalInput("GPIO11") -# for i in range(10): -# pinOut + pinOut = AnalogOutput("5") + test_analog_class(pinOut) if __name__ == "__main__": - testCAN() + # Wait for nucleo + while pinIn.value == 1: + pinOut.value = 0 + time.sleep(0.1) + + pinOut.value = 1 + testCAN() + + # Make sure finished sending + while pinIn.value == 1: + pinOut.value = 0 + print("THERE WAS A SYNC ERROR WITH RECEIVING CAN") + time.sleep(0.1) + + pinOut.value = 1 testAnalog() \ No newline at end of file From 018c0bd677202c1de668b31076d06c0444287853 Mon Sep 17 00:00:00 2001 From: Colby Wise Date: Thu, 29 Jan 2026 23:14:42 -0500 Subject: [PATCH 17/19] Changing Tabs --- Testing_Library/test_all_comms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Testing_Library/test_all_comms.py b/Testing_Library/test_all_comms.py index cb1dbe9..9a9efba 100644 --- a/Testing_Library/test_all_comms.py +++ b/Testing_Library/test_all_comms.py @@ -12,8 +12,8 @@ from gpioPins import AnalogOutput # Global Pins for Synchronization -pinOut = DigitalOutput("GPIO2") -pinIn = DigitalInput("GPIO11") +pinOut = DigitalOutput("2") +pinIn = DigitalInput("11") # -----------Testing CAN Comms----------- @@ -89,7 +89,7 @@ def testAnalog(): if __name__ == "__main__": # Wait for nucleo - while pinIn.value == 1: + while pinIn.value == 1: pinOut.value = 0 time.sleep(0.1) @@ -103,4 +103,4 @@ def testAnalog(): time.sleep(0.1) pinOut.value = 1 - testAnalog() \ No newline at end of file + testAnalog() From e69acd1c575ba7c4b0976f73600b6546605d9620 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 12 Feb 2026 16:38:00 -0500 Subject: [PATCH 18/19] fix: exception when stop reading for less error-y exit from test --- Testing_Library/MotorInterfaceTest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Testing_Library/MotorInterfaceTest.py b/Testing_Library/MotorInterfaceTest.py index e09b2dc..641b586 100644 --- a/Testing_Library/MotorInterfaceTest.py +++ b/Testing_Library/MotorInterfaceTest.py @@ -62,7 +62,10 @@ def startReadThread(self): def readThrottleAndRegen(self): while(not self.stop_thread): with self.Lock: - byte_read = self.ser.read(1) + try: + byte_read = self.ser.read(1) + except serial.SerialException as e: + return if byte_read == THROTTLE_START_BYTE: # Throttle start marker data = self.ser.read(2) # Read 2 bytes for 9-bit value if len(data) == 2: From 6301b450383a590b3086b596d6bf50198d7f2ec0 Mon Sep 17 00:00:00 2001 From: abhinavp5 Date: Thu, 12 Feb 2026 17:15:07 -0500 Subject: [PATCH 19/19] feat: add motor interface test script for throttle and regen modes --- Testing_Library/MotorInterface.py | 124 ------------------ .../motor_interface_test_script1.29.ino | 6 +- 2 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 Testing_Library/MotorInterface.py rename Testing_Library/{ => motor_interface_test_script1.29}/motor_interface_test_script1.29.ino (91%) diff --git a/Testing_Library/MotorInterface.py b/Testing_Library/MotorInterface.py deleted file mode 100644 index 425f554..0000000 --- a/Testing_Library/MotorInterface.py +++ /dev/null @@ -1,124 +0,0 @@ -import sys -import serial -import time -import threading - - -sys.path.append('/home/solarcar/solarcar/HiL_Testing/Server') - -THROTTLE_ADDR = 0x2F -REGEN_ADDR = 0x2E - -# Serial Configuration -ser = serial.Serial( - port='/dev/ttyACM0', - baudrate=9600, - timeout=1 -) - -print("Waiting for Arduino to start...") -time.sleep(2) # Give Arduino time to boot -''' -while True: - byte_read = ser.read(1) - if byte_read == b'\xFF': - data = ser.read(3) # Read 3 bytes for two 9-bit values - if len(data) == 3: - # Unpack two 9-bit values from 3 bytes - # Byte 0: throttle[7:0] - # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 - # Byte 2: regen[8:7] in bits 1:0 - - # Extracting throttle and regen - throttle = data[0] | ((data[1] & 0x01) << 8) - regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) - - print(f"Throttle: {throttle}%) | Regen: {regen})") - else: - print(f"Incomplete data: got {len(data)} bytes instead of 3") - elif byte_read: - print(f"Unexpected byte:{byte_read}") -''' -ser = serial.Serial( - port='/dev/ttyACM0', - baudrate=9600, - timeout=1 - ) -''' -API for test Motor interface -''' -class MotorInterface: - def __init__(self): - self.stop_thread = False - self.mru_throttle = None - self.mru_regen = None - self.THROTTLE_ADDR = 0x2F - self.REGEN_ADDR = 0x2E - self.Lock = threading.Lock() - self.startReadThread() - - - #Run infinite thread to read in Throttle and Regen Values - def startReadThread(self): - read_thread = threading.Thread(target=self.readThrottleAndRegen) - read_thread.daemon = True - try: - read_thread.start() - except ValueError as e: - print(f"Exception") - def readThrottleAndRegen(self): - while(not self.stop_thread): - with self.Lock: - byte_read = ser.read(1) - if byte_read == b'\xFF': # Start Byte - data = ser.read(3) # Read 3 bytes for two 9-bit values - if len(data) == 3: - # two 9-bit values from 3 bytes - # Byte 0: throttle[7:0] - # Byte 1: throttle[8] in bit 0, regen[6:0] in bits 7:1 - # Byte 2: regen[8:7] in bits 1:0 - - # Extracting throttle and regen - self.mru_throttle = data[0] | ((data[1] & 0x01) << 8) - self.mru_regen = ((data[1] >> 1) & 0x7F) | ((data[2] & 0x03) << 7) - - def get_throttle(self) -> float: - # Normalized between [0-1.0] - if self.mru_throttle is None: - return None - return self.mru_throttle/256.0 - - def get_throttle_raw(self) -> int: - # raw between [0-256] - if self.mru_throttle is None: - return None - return self.mru_throttle - - def get_regen(self) -> float: - # Normalized between [0,1.0] - if self.mru_regen is None: - return None - return self.mru_regen/256.0 - - def get_regen_raw(self) -> int: - # raw between [0-256] - if self.mru_regen is None: - return None - return self.mru_regen - - -motor_interface = MotorInterface() - -while(True): - - throttle_raw = motor_interface.get_throttle_raw() - throttle_norm = motor_interface.get_throttle() - regen_raw = motor_interface.get_regen_raw() - regen_norm = motor_interface.get_regen() - print("Details") - print(throttle_raw) - print(throttle_norm) - print(regen_raw) - print(regen_norm) - time.sleep(1) - diff --git a/Testing_Library/motor_interface_test_script1.29.ino b/Testing_Library/motor_interface_test_script1.29/motor_interface_test_script1.29.ino similarity index 91% rename from Testing_Library/motor_interface_test_script1.29.ino rename to Testing_Library/motor_interface_test_script1.29/motor_interface_test_script1.29.ino index 729edaf..80d0dfa 100644 --- a/Testing_Library/motor_interface_test_script1.29.ino +++ b/Testing_Library/motor_interface_test_script1.29/motor_interface_test_script1.29.ino @@ -7,10 +7,10 @@ #define SWAP_COMMAND 0x1FF // Test Mode: 0 = Throttle only (listen on 0x2F), 1 = Regen only (listen on 0x2E). -// Must match which test is run: test_throttle needs TEST_MODE 0, test_regen needs TEST_MODE 1. -#define TEST_MODE 0 +// Must match PowerBoard main.cpp I2C_TEST_MODE: test_throttle → 0, test_regen → 1. +#define TEST_MODE 1 -int currentMode = 0; // 0 = Throttle, 1 = Regen +int currentMode = 1; // 0 = Throttle, 1 = Regen void setup() { Serial.begin(9600);