I am creating a Python 3.8 script that executes a series of tests that reads and writes information to and from a CAN bus network. I'm using the python-can and the cantools packages. I've managed to transmit and receive data with small functions individually without issue.
I feel I'm not creating the proper "Pythonic" script architecture that allows my script to use all functions, instances and variables between modules.
Intended architecture goal:
main.py
- contains the CAN bus transmit and receive functions, performs initialization and cycles through each test case located in thetest_case.py
module.test_case.py
- stores all test cases. Where each test case is a stand alone function. Each test case must be an isolated function so that if one test needs to be removed or a new test added the script won't break. Additionally, there will likely be dozens maybe hundreds of test cases. So I'd like to keep them isolated to one module for code cleanliness.test_thresholds.py
- would keep all the pass/fail threshold variables that each test case intest_case.py
will refer to.
Problems / Questions:
main.py
instantiates a CAN bus objectbus = can.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=500000)
this object is required for the transmit and receive functions. Because the transmit and receive functions are inmain.py
, this wasn't a problem until I tried to execute a test case in thetest_case.py
module which references the transmit and receive functions inmain.py
Once I attempted to execute a test case an error occurred stating that the
receive()
function being called from thetest_case.py
moduleNameError: name 'bus' is not defined
I understand this astest_case.py
does not know what thebus
instance is. This problem also occurs with mycan
instances. I havefrom main import *
in mytest_case.py
I know this is bad but I am not sure how elsetest_cases.py
will use the transmit and receive functions along with thebus
andcan
instancesHow can I share that instances between modules? What are the best practices here? I have tried to go over several posts on Stack Overflow regarding passing objects (I think that's what my problem is) but none of them seem to answer what I'm looking for.
Is my architecture design acceptable? I'm new to designing larger scripts and I want to make sure I am doing it effectively/proper so that it can scale.
Note: I've cut down a lot of my code to make it more readable here. It may not run if you try it.
main.py
import can
import cantools
import test_cases.test_cases # import all test cases
import time
# sending a single CAN message
def single_send(message):
try:
bus.send(message)
except can.CanError:
print("Message NOT sent")
# receive a message and decode payload
def receive(message, signal):
_counter = 0
try:
while True:
msg = bus.recv(1)
try:
if msg.arbitration_id == message.arbitration_id:
message_data = db.decode_message(msg.arbitration_id, msg.data)
signal_data = message_data.get(signal)
return signal_data
except AttributeError:
_counter += 1
if _counter == 5:
print("CAN Bus InActive")
break
finally:
if _counter == 5:
# reports false if message fails to be received
return False
def main():
for name, tests in test_cases.test_cases.__dict__.items():
if name.startswith("tc") and callable(tests):
tests()
if __name__ == "__main__":
bus = can.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=500000)
db = cantools.db.load_file('C:\\Users\\tw\\Desktop\\dbc_file.dbc')
verbose_log = open("verbose_log.txt", "a")
main()
bus.shutdown()
verbose_log.close()
test_case.py
from test_thresholds.test_thresholds import *
from main import * # to use the single_send and receive functions in main
def tc_1():
ct = receive(0x300, 'ct_signal') # this is where the issue occurs. receive expects the bus instance
message = can.Message(arbitration_id=0x303, data=1)
if (ct > ct_min) and (ct < ct_max):
verbose_log.write("PASS")
else:
verbose_log.write("FAIL")
test_thresholds.py
ct_min = 4.2
ct_max = 5.3
receive
function? It is not clear to me why that function needs to take a message to receive? \$\endgroup\$receive
function is used intc_1
. Its is supposed to take the message ID and the signal as args. Then return the value of the signal at that given instant \$\endgroup\$