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 the
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 in
test_case.pywill refer to.
Problems / Questions:
main.pyinstantiates a CAN bus object
bus = 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 in
main.py, this wasn't a problem until I tried to execute a test case in the
test_case.pymodule which references the transmit and receive functions in
Once I attempted to execute a test case an error occurred stating that the
receive()function being called from the
NameError: name 'bus' is not definedI understand this as
test_case.pydoes not know what the
businstance is. This problem also occurs with my
caninstances. I have
from main import *in my
test_case.pyI know this is bad but I am not sure how else
test_cases.pywill use the transmit and receive functions along with the
How 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.
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()
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")
ct_min = 4.2 ct_max = 5.3