From 35036c054f9b58ae96b03357257e1af40c78384a Mon Sep 17 00:00:00 2001 From: Manush Date: Sat, 23 Nov 2024 23:19:09 -0500 Subject: [PATCH 01/10] Added CAN Equation --- scripts/cangen/cangen/traffic_calculation.py | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/cangen/cangen/traffic_calculation.py diff --git a/scripts/cangen/cangen/traffic_calculation.py b/scripts/cangen/cangen/traffic_calculation.py new file mode 100644 index 000000000..162d0f09e --- /dev/null +++ b/scripts/cangen/cangen/traffic_calculation.py @@ -0,0 +1,61 @@ +class BusLoadCalculator: + + def __init__(self, can_speed = int): + """" + Initialize bus load calculator with the CAN Speed (bits per second) + :param can_speed: The speed of the CAN Bus in bits per second + + """ + self.can_speed = can_speed + + def frame_length(self, frame_data: tuple) -> float: + total_message_length = 0 + """ + Calculate the total length of a single CAN frame in bits, this will include bit stuffing + :param frame_data is tuple containing the bytes of data, and frames per second + :return: Bus load as percentage + """ + for messages in range(len(frame_data)): + + data_bytes, frequency_hz = frame_data + + fixed_length = 44 #fixed length of CAN frame + + variable_length = data_bytes * 9.6 #accounted for bit stuffing + + total_frame_length = variable_length + fixed_length #add fixed and variable lengths + + total_message_length +=(total_frame_length* frequency_hz) + + return total_message_length + + def calculate_bus_load(self,messages: list[tuple])-> float: + + total_busload = 0.0 + + for frame_data in messages: + + total_bits = self.frame_length(frame_data) # Get bus load for the current message + + total_busload += (total_bits/ (self.can_speed))*100 #bus load formula + + return round(total_busload,2) + +# if __name__ == "__main__": #TEST CASE USED +# # CAN speed: 500 kbps +# can_speed = 500000 + +# messages = [ +# (8, 100), # 8 bytes, 100Hz +# (5, 50), +# (2, 100), +# (8, 50), +# ] + +# # Initialize the calculator +# calculator = BusLoadCalculator(can_speed) + +# # Calculate total bus load +# total_bus_load = calculator.calculate_bus_load(messages) + +# print(f"Total Bus Load: {total_bus_load}%") \ No newline at end of file From 3949da8a6218072f0187202450c573f298de4176 Mon Sep 17 00:00:00 2001 From: Manush Date: Sun, 24 Nov 2024 22:13:06 -0500 Subject: [PATCH 02/10] Removed Class, Updated Code --- scripts/cangen/cangen/traffic.py | 70 ++++++++++++++++++++ scripts/cangen/cangen/traffic_calculation.py | 61 ----------------- 2 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 scripts/cangen/cangen/traffic.py delete mode 100644 scripts/cangen/cangen/traffic_calculation.py diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py new file mode 100644 index 000000000..6d23d2788 --- /dev/null +++ b/scripts/cangen/cangen/traffic.py @@ -0,0 +1,70 @@ +""" +Author: Manush Patel +Date: 2024-11-24 +https://macformula.github.io/racecar/firmware/can-traffic/ +""" +from dataclasses import dataclass + +@dataclass +class CANMessage: + data_length: int + frequency: int + +def frame_length(data_bytes:int) -> float: + """ + Calculate the total length of a single CAN frame in bits, this will include bit stuffing + :param frame_data is tuple containing the bytes of data, and frames per second + :return: Bus load as percentage + """ + + total_message_length = 0 + + fixed_length = 44 #fixed length of CAN frame + + variable_length = data_bytes * 9.6 #accounted for bit stuffing + + total_frame_length = variable_length + fixed_length + + return total_frame_length + +def calculate_bits(message_length, frequency): + """ + Calculate total bits by multiplying with frequency of message + :param message_length is float representing the message length + :param frequency is an int representing how often a message is transmitted per second + """ + + total_bits = message_length * frequency + + return (total_bits) + +def calculate_bus_load(total_bits, can_speed): + """ + Calculate bus load by dividing total bits by can_speed (default: 500,000bits) + Return Bus Load percentage + """ + + return (total_bits/ (can_speed))*100 #bus load formula + +if __name__ == "__main__": #TEST CASE USED + + total_bits = 0 + + can_speed = 500000 #set to 500kbaud + + messages = [ + CANMessage(data_length=8, frequency=100), # 8 bytes, 200 frames/sec + CANMessage(data_length=4, frequency=50), # 4 bytes, 100 frames/sec + CANMessage(data_length=5, frequency=500), # 5 bytes, 500 frames/sec + ] + + # Initialize the calculator + for message in messages: + + message_length = frame_length(message.data_length) #collect frame length of message + + total_bits +=(calculate_bits(message_length, message.frequency)) #add bits per second of message to bits_persec + + bus_load = calculate_bus_load(total_bits, can_speed) + + print(f"Calculated Bus Load is: {bus_load:.2f}%") diff --git a/scripts/cangen/cangen/traffic_calculation.py b/scripts/cangen/cangen/traffic_calculation.py deleted file mode 100644 index 162d0f09e..000000000 --- a/scripts/cangen/cangen/traffic_calculation.py +++ /dev/null @@ -1,61 +0,0 @@ -class BusLoadCalculator: - - def __init__(self, can_speed = int): - """" - Initialize bus load calculator with the CAN Speed (bits per second) - :param can_speed: The speed of the CAN Bus in bits per second - - """ - self.can_speed = can_speed - - def frame_length(self, frame_data: tuple) -> float: - total_message_length = 0 - """ - Calculate the total length of a single CAN frame in bits, this will include bit stuffing - :param frame_data is tuple containing the bytes of data, and frames per second - :return: Bus load as percentage - """ - for messages in range(len(frame_data)): - - data_bytes, frequency_hz = frame_data - - fixed_length = 44 #fixed length of CAN frame - - variable_length = data_bytes * 9.6 #accounted for bit stuffing - - total_frame_length = variable_length + fixed_length #add fixed and variable lengths - - total_message_length +=(total_frame_length* frequency_hz) - - return total_message_length - - def calculate_bus_load(self,messages: list[tuple])-> float: - - total_busload = 0.0 - - for frame_data in messages: - - total_bits = self.frame_length(frame_data) # Get bus load for the current message - - total_busload += (total_bits/ (self.can_speed))*100 #bus load formula - - return round(total_busload,2) - -# if __name__ == "__main__": #TEST CASE USED -# # CAN speed: 500 kbps -# can_speed = 500000 - -# messages = [ -# (8, 100), # 8 bytes, 100Hz -# (5, 50), -# (2, 100), -# (8, 50), -# ] - -# # Initialize the calculator -# calculator = BusLoadCalculator(can_speed) - -# # Calculate total bus load -# total_bus_load = calculator.calculate_bus_load(messages) - -# print(f"Total Bus Load: {total_bus_load}%") \ No newline at end of file From aa457b17b1cfdae5ced95a4e6af00ecdf6e7cc93 Mon Sep 17 00:00:00 2001 From: Manush Date: Mon, 25 Nov 2024 20:47:33 -0500 Subject: [PATCH 03/10] updated code --- scripts/cangen/cangen/traffic.py | 36 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py index 6d23d2788..570df2409 100644 --- a/scripts/cangen/cangen/traffic.py +++ b/scripts/cangen/cangen/traffic.py @@ -10,14 +10,7 @@ class CANMessage: data_length: int frequency: int -def frame_length(data_bytes:int) -> float: - """ - Calculate the total length of a single CAN frame in bits, this will include bit stuffing - :param frame_data is tuple containing the bytes of data, and frames per second - :return: Bus load as percentage - """ - - total_message_length = 0 +def data_length(data_bytes): fixed_length = 44 #fixed length of CAN frame @@ -46,25 +39,28 @@ def calculate_bus_load(total_bits, can_speed): return (total_bits/ (can_speed))*100 #bus load formula -if __name__ == "__main__": #TEST CASE USED +def return_baud(messages, can_speed): total_bits = 0 - - can_speed = 500000 #set to 500kbaud - - messages = [ - CANMessage(data_length=8, frequency=100), # 8 bytes, 200 frames/sec - CANMessage(data_length=4, frequency=50), # 4 bytes, 100 frames/sec - CANMessage(data_length=5, frequency=500), # 5 bytes, 500 frames/sec - ] - # Initialize the calculator for message in messages: - message_length = frame_length(message.data_length) #collect frame length of message + message_length = data_length(message.data_length) #collect frame length of message total_bits +=(calculate_bits(message_length, message.frequency)) #add bits per second of message to bits_persec bus_load = calculate_bus_load(total_bits, can_speed) - print(f"Calculated Bus Load is: {bus_load:.2f}%") + return bus_load + +if __name__ == "__main__": + messages = [ + CANMessage(data_length=8, frequency=100), + CANMessage(data_length=5, frequency=50), + CANMessage(data_length=5, frequency=500), + ] + can_speed = 500000 + + bus_load = return_baud(messages, can_speed) + + print(f"Calculated Bus Load is: {bus_load:.2f}%") \ No newline at end of file From 6b1f6d5e206bb717067e4f77f7436b0b12a3f620 Mon Sep 17 00:00:00 2001 From: Manush Date: Wed, 27 Nov 2024 15:07:48 -0500 Subject: [PATCH 04/10] updated, did not change can_generator, ruff improved formatting --- scripts/cangen/cangen/can_generator.py | 9 +++- scripts/cangen/cangen/traffic.py | 60 ++++++++------------------ 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/scripts/cangen/cangen/can_generator.py b/scripts/cangen/cangen/can_generator.py index a84e36f07..d05c6f7ad 100644 --- a/scripts/cangen/cangen/can_generator.py +++ b/scripts/cangen/cangen/can_generator.py @@ -168,11 +168,14 @@ def _camel_to_snake(text): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -def _create_output_file_name(output_dir: str, bus_name: str, template_file_name: str) -> str: +def _create_output_file_name( + output_dir: str, bus_name: str, template_file_name: str +) -> str: return os.path.join( output_dir, bus_name.lower() + "_" + template_file_name.removesuffix(".jinja2") ) + def _generate_code(bus: Bus, output_dir: str): """ Parses DBC files, extracts information, and generates code using Jinja2 @@ -212,7 +215,9 @@ def _generate_code(bus: Bus, output_dir: str): template = env.get_template(template_file_name) rendered_code = template.render(**context) - output_file_name = _create_output_file_name(output_dir, bus.bus_name, template_file_name) + output_file_name = _create_output_file_name( + output_dir, bus.bus_name, template_file_name + ) with open(output_file_name, "w") as output_file: output_file.write(rendered_code) logger.info(f"Rendered code written to '{os.path.abspath(output_file_name)}'") diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py index 570df2409..5a346f723 100644 --- a/scripts/cangen/cangen/traffic.py +++ b/scripts/cangen/cangen/traffic.py @@ -3,64 +3,40 @@ Date: 2024-11-24 https://macformula.github.io/racecar/firmware/can-traffic/ """ + from dataclasses import dataclass + @dataclass class CANMessage: data_length: int frequency: int -def data_length(data_bytes): - - fixed_length = 44 #fixed length of CAN frame - - variable_length = data_bytes * 9.6 #accounted for bit stuffing - - total_frame_length = variable_length + fixed_length +def data_length(data_bytes): + fixed_length = 44 # fixed length of CAN frame + variable_length = data_bytes * 9.6 # accounted for bit stuffing + total_frame_length = variable_length + fixed_length return total_frame_length -def calculate_bits(message_length, frequency): - """ - Calculate total bits by multiplying with frequency of message - :param message_length is float representing the message length - :param frequency is an int representing how often a message is transmitted per second - """ - - total_bits = message_length * frequency - - return (total_bits) - -def calculate_bus_load(total_bits, can_speed): - """ - Calculate bus load by dividing total bits by can_speed (default: 500,000bits) - Return Bus Load percentage - """ - - return (total_bits/ (can_speed))*100 #bus load formula - -def return_baud(messages, can_speed): +def calculate_bus_load(messages, can_speed): total_bits = 0 - # Initialize the calculator for message in messages: - - message_length = data_length(message.data_length) #collect frame length of message - - total_bits +=(calculate_bits(message_length, message.frequency)) #add bits per second of message to bits_persec - - bus_load = calculate_bus_load(total_bits, can_speed) - + message_length = data_length( + message.data_length + ) # collect frame length of message + total_bits += message_length * message.frequency + bus_load = total_bits / can_speed * 100 # bus load formula return bus_load + if __name__ == "__main__": messages = [ - CANMessage(data_length=8, frequency=100), - CANMessage(data_length=5, frequency=50), - CANMessage(data_length=5, frequency=500), + CANMessage(data_length=8, frequency=100), + CANMessage(data_length=5, frequency=50), + CANMessage(data_length=5, frequency=500), ] can_speed = 500000 - - bus_load = return_baud(messages, can_speed) - - print(f"Calculated Bus Load is: {bus_load:.2f}%") \ No newline at end of file + bus_load = calculate_bus_load(messages, can_speed) + print(f"Calculated Bus Load is: {bus_load:.2f}%") From b6cfd0fb98727587c2862017757d89a6e07d7a59 Mon Sep 17 00:00:00 2001 From: BlakeFreer Date: Fri, 29 Nov 2024 11:21:52 -0500 Subject: [PATCH 05/10] Create test folder --- scripts/cangen/README.md | 14 ++++ scripts/cangen/poetry.lock | 118 ++++++++++++++++++++++++++- scripts/cangen/pyproject.toml | 1 + scripts/cangen/tests/test_traffic.py | 5 ++ 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 scripts/cangen/tests/test_traffic.py diff --git a/scripts/cangen/README.md b/scripts/cangen/README.md index 4931ad817..529107b9c 100644 --- a/scripts/cangen/README.md +++ b/scripts/cangen/README.md @@ -41,3 +41,17 @@ FrontController ├─ main.cc └─ README.md ``` + +## Development + +We use Poetry to manage this package. Install the development environment with + +```bash +poetry install +``` + +To run the tests: + +```bash +poetry run pytest +``` diff --git a/scripts/cangen/poetry.lock b/scripts/cangen/poetry.lock index cbca92dfe..b46f79403 100644 --- a/scripts/cangen/poetry.lock +++ b/scripts/cangen/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "argparse-addons" @@ -87,6 +87,17 @@ dev = ["mypy", "pipx", "ruff", "tox"] plot = ["matplotlib"] windows-all = ["windows-curses"] +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "crccheck" version = "1.3.0" @@ -109,6 +120,31 @@ files = [ {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "jinja2" version = "3.1.3" @@ -317,6 +353,43 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-can" version = "4.4.2" @@ -477,6 +550,47 @@ files = [ {file = "textparser-0.24.0.tar.gz", hash = "sha256:56f708e75aa9d002adb76d823ba6ef166d7ecec1e3e4ca4c1ca103f817568335"}, ] +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -570,4 +684,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "2e720bbda2d5750ab9e0b3fcec76dc474d9087ad806bbbb5462874e769c8aa6e" +content-hash = "482b860045433a8394e41c1b3295178461ac533724c95c45acb6ffe2c767cdbe" diff --git a/scripts/cangen/pyproject.toml b/scripts/cangen/pyproject.toml index 28c7c45d7..dd3f20445 100644 --- a/scripts/cangen/pyproject.toml +++ b/scripts/cangen/pyproject.toml @@ -18,6 +18,7 @@ cangen = "cangen.main:main" [tool.poetry.group.dev.dependencies] ruff = "^0.6.9" +pytest = "^8.3.3" [build-system] requires = ["poetry-core"] diff --git a/scripts/cangen/tests/test_traffic.py b/scripts/cangen/tests/test_traffic.py new file mode 100644 index 000000000..cca87ec9f --- /dev/null +++ b/scripts/cangen/tests/test_traffic.py @@ -0,0 +1,5 @@ +from cangen.traffic import calculate_bus_load + + +def test_no_messages(): + assert calculate_bus_load([], 500000) == 0 From 884026701b191aa18a26a0a25add90435823786e Mon Sep 17 00:00:00 2001 From: Manush Date: Sun, 22 Dec 2024 01:03:19 -0500 Subject: [PATCH 06/10] included some test cases, and updated website --- docs/docs/firmware/can-traffic/index.md | 28 ++++++----- scripts/cangen/cangen/traffic.py | 62 +++++++++++++----------- scripts/cangen/tests/test_traffic.py | 64 ++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/docs/docs/firmware/can-traffic/index.md b/docs/docs/firmware/can-traffic/index.md index f6ef68031..a70c08333 100644 --- a/docs/docs/firmware/can-traffic/index.md +++ b/docs/docs/firmware/can-traffic/index.md @@ -29,7 +29,7 @@ Each CAN frame consists of several fields with fixed bit lengths, plus a variabl |Identifier (ID)| 11 | |Remote Transmission Request (RTR)| 1| | Control (DLC)| 6| -|Data| 8 bits per byte with 20% bit stuffing = 9.6x| +|Data| 8 bits per byte = 8x| |Cyclic Redundancy Check (CRC) | 16 | |Acknowledgement (ACK)| 2 | |End of Frame (EOF)| 7| @@ -38,7 +38,7 @@ Each CAN frame consists of several fields with fixed bit lengths, plus a variabl The maximum total frame length is: -\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 9.6x = 44 + 9.6x \text{ bits}\] +\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 9.6x = 44 + 8x \text{ bits}\] where \(x\) represents the number of bytes in the variable data field. @@ -46,14 +46,16 @@ where \(x\) represents the number of bytes in the variable data field. ## Total CAN Traffic +We must multiply total bits per second by 1.2 to account for bit stuffing + \begin{align} -\text{Total Bits Per Second} &= \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\ +\text{Total Bits Per Second} &= 1.2 \times \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right) \end{align} ## Total Bus Load \begin{align} -\text{Bus Load}\%= \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\% +\text{Bus Load}\% = \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\% \end{align} -------------------------------------- @@ -66,22 +68,24 @@ Baud Rate: 500 kbaud (500,000 bits transferred per second) |Message Type | Data Length| Frequency (Hz)| Message Length (Bits)| |--------------|------------|---------------|---------------------| -|Battery Status| 8 | 100 | 44 + 9.6 x 8 = 121| -|Motor Control| 5 | 50 | 44 + 9.6 x 5 = 92| +|Battery Status| 8 | 100 | 44 + 8 x 8 = 108| +|Motor Control| 5 | 50 | 44 + 8 x 5 = 84| \begin{align} -\text{Total Bits Per Second} &= \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\ -\text{Total Bits Per Second} &= (100\text{ Hz} \times 121\text{ bits}) + (50\text{ Hz}\times 92\text{ bits})\\ -&=(12100 + 4600)\text{ bits per second}\\ -&= 16700\text{ bits per second} +\text{Total Bits Per Second} &= 1.2 \times \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\ +\text{Total Bits Per Second} &= 1.2 \times \left((100\text{ Hz} \times 108\text{ bits}) + (50\text{ Hz} \times 84\text{ bits})\right)\\ +&= 1.2 \times \left(10800 + 4200\right)\text{ bits per second}\\ +&= 1.2 \times 15000\text{ bits per second}\\ +&= 18000\text{ bits per second} \end{align} + The bus load is the previous example can be calculated as: $$ -\text{Bus Load}\%= \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\% +\text{Bus Load}\% = \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\% $$ $$ -\text{Bus Load} = \left(\frac{16700}{500,000}\right) \times 100\% = 3.34\% +\text{Bus Load} = \left(\frac{18000}{500000}\right) \times 100\% = 3.60\% $$ diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py index 5a346f723..0e4d89daf 100644 --- a/scripts/cangen/cangen/traffic.py +++ b/scripts/cangen/cangen/traffic.py @@ -12,31 +12,37 @@ class CANMessage: data_length: int frequency: int - -def data_length(data_bytes): - fixed_length = 44 # fixed length of CAN frame - variable_length = data_bytes * 9.6 # accounted for bit stuffing - total_frame_length = variable_length + fixed_length - return total_frame_length - - -def calculate_bus_load(messages, can_speed): - total_bits = 0 - for message in messages: - message_length = data_length( - message.data_length - ) # collect frame length of message - total_bits += message_length * message.frequency - bus_load = total_bits / can_speed * 100 # bus load formula - return bus_load - - -if __name__ == "__main__": - messages = [ - CANMessage(data_length=8, frequency=100), - CANMessage(data_length=5, frequency=50), - CANMessage(data_length=5, frequency=500), - ] - can_speed = 500000 - bus_load = calculate_bus_load(messages, can_speed) - print(f"Calculated Bus Load is: {bus_load:.2f}%") + @property + def bit_count(self): + fixed_length = 44 # fixed length of CAN frame + variable_length = self.data_length * 8.0 # accounted for bit stuffing + total_frame_length = variable_length + fixed_length + return total_frame_length + + +def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float: + """Calculates the bus load percentage based on CAN messages and bus speed + + Parameters + ---------- + messages : list[CANMessage] + A list of CANMessage objects, each containing `bit_count` and `frequency` attributes + can_speed : int + The CAN bus speed in bits per second (bps). + + Returns + ------- + float + Returns bus load as a percentage + """ + if can_speed < 0: + print(f"Invalid CAN speed: {can_speed}") + return 0 + + total_bits = sum(message.bit_count * message.frequency for message in messages) + + if total_bits < 0: + return 0 + + bus_load = ((total_bits) * 1.2) / can_speed * 100 + return round(bus_load, 2) diff --git a/scripts/cangen/tests/test_traffic.py b/scripts/cangen/tests/test_traffic.py index cca87ec9f..266a791cc 100644 --- a/scripts/cangen/tests/test_traffic.py +++ b/scripts/cangen/tests/test_traffic.py @@ -1,5 +1,65 @@ -from cangen.traffic import calculate_bus_load +from cangen.traffic import calculate_bus_load, CANMessage +import pytest def test_no_messages(): - assert calculate_bus_load([], 500000) == 0 + assert calculate_bus_load( + [ + CANMessage(data_length=8, frequency=100), + CANMessage(data_length=5, frequency=50), + ], + 500000, + ) == pytest.approx(3.60, rel=1e-2) + + # zero frequency or negative frequency + assert calculate_bus_load( + [ + CANMessage(data_length=8, frequency=0), + CANMessage(data_length=10, frequency=-100), + ], + 500000, + ) == pytest.approx(0, rel=1e-2) + + # bus overload + assert calculate_bus_load( + [ + CANMessage(data_length=64, frequency=100), + CANMessage(data_length=32, frequency=200), + ], + 100000, + ) == pytest.approx(138.72, rel=1e-5) + + # high traffic - high frequency messages + assert calculate_bus_load( + [ + CANMessage(data_length=8, frequency=1000), + CANMessage(data_length=4, frequency=800), + ], + 1000000, + ) == pytest.approx(20.26, rel=1e-5) + + # zero can speed + try: + assert calculate_bus_load( + [ + CANMessage(data_length=8, frequency=1000), + CANMessage(data_length=4, frequency=800), + ], + 0, + ) == pytest.approx(0, rel=1e-5) + except ZeroDivisionError: + print("Cannot divide by zero baud") + + # negative can speed + assert ( + calculate_bus_load( + [ + CANMessage(data_length=8, frequency=1000), + CANMessage(data_length=4, frequency=800), + ], + -500000, + ) + ) == pytest.approx(0, rel=1e-5) + + # no message + assert (calculate_bus_load([], 500000)) == pytest.approx(0, rel=1e-5) From 1425544224ebfd0771f78ae6a0fc1ddb9ecc0df9 Mon Sep 17 00:00:00 2001 From: Manush Date: Mon, 23 Dec 2024 22:00:37 -0500 Subject: [PATCH 07/10] update --- scripts/cangen/tests/test_traffic.py | 32 ++++++++++------------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/scripts/cangen/tests/test_traffic.py b/scripts/cangen/tests/test_traffic.py index 266a791cc..393faa66c 100644 --- a/scripts/cangen/tests/test_traffic.py +++ b/scripts/cangen/tests/test_traffic.py @@ -3,6 +3,9 @@ def test_no_messages(): + assert (calculate_bus_load([], 500000)) == pytest.approx(0, rel=1e-5) + +def test_normal_messages(): assert calculate_bus_load( [ CANMessage(data_length=8, frequency=100), @@ -11,16 +14,7 @@ def test_no_messages(): 500000, ) == pytest.approx(3.60, rel=1e-2) - # zero frequency or negative frequency - assert calculate_bus_load( - [ - CANMessage(data_length=8, frequency=0), - CANMessage(data_length=10, frequency=-100), - ], - 500000, - ) == pytest.approx(0, rel=1e-2) - - # bus overload +def test_bus_overload(): assert calculate_bus_load( [ CANMessage(data_length=64, frequency=100), @@ -29,17 +23,17 @@ def test_no_messages(): 100000, ) == pytest.approx(138.72, rel=1e-5) - # high traffic - high frequency messages +def test_high_traffic(): assert calculate_bus_load( [ CANMessage(data_length=8, frequency=1000), CANMessage(data_length=4, frequency=800), ], 1000000, - ) == pytest.approx(20.26, rel=1e-5) + ) == pytest.approx(20.256, rel=1e-5) - # zero can speed - try: +def test_zero_speed(): + with pytest.raises(ValueError): assert calculate_bus_load( [ CANMessage(data_length=8, frequency=1000), @@ -47,11 +41,10 @@ def test_no_messages(): ], 0, ) == pytest.approx(0, rel=1e-5) - except ZeroDivisionError: - print("Cannot divide by zero baud") - # negative can speed - assert ( +def test_negative_speed(): + with pytest.raises(ValueError): + assert ( calculate_bus_load( [ CANMessage(data_length=8, frequency=1000), @@ -60,6 +53,3 @@ def test_no_messages(): -500000, ) ) == pytest.approx(0, rel=1e-5) - - # no message - assert (calculate_bus_load([], 500000)) == pytest.approx(0, rel=1e-5) From 20302476ec125f0cdaf385507e5496d15d5ea41f Mon Sep 17 00:00:00 2001 From: Manush Date: Tue, 24 Dec 2024 13:18:07 -0500 Subject: [PATCH 08/10] seems my terminal was bugged, didn't push the rest of my files --- docs/docs/firmware/can-traffic/index.md | 2 +- scripts/cangen/cangen/traffic.py | 23 +++++++---------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/docs/firmware/can-traffic/index.md b/docs/docs/firmware/can-traffic/index.md index a70c08333..f948c7d98 100644 --- a/docs/docs/firmware/can-traffic/index.md +++ b/docs/docs/firmware/can-traffic/index.md @@ -38,7 +38,7 @@ Each CAN frame consists of several fields with fixed bit lengths, plus a variabl The maximum total frame length is: -\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 9.6x = 44 + 8x \text{ bits}\] +\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 8x = 44 + 8x \text{ bits}\] where \(x\) represents the number of bytes in the variable data field. diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py index 0e4d89daf..155f4b8f8 100644 --- a/scripts/cangen/cangen/traffic.py +++ b/scripts/cangen/cangen/traffic.py @@ -14,11 +14,7 @@ class CANMessage: @property def bit_count(self): - fixed_length = 44 # fixed length of CAN frame - variable_length = self.data_length * 8.0 # accounted for bit stuffing - total_frame_length = variable_length + fixed_length - return total_frame_length - + return 44 + 8 * self.data_length def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float: """Calculates the bus load percentage based on CAN messages and bus speed @@ -26,23 +22,18 @@ def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float: Parameters ---------- messages : list[CANMessage] - A list of CANMessage objects, each containing `bit_count` and `frequency` attributes can_speed : int - The CAN bus speed in bits per second (bps). Returns ------- float Returns bus load as a percentage """ - if can_speed < 0: - print(f"Invalid CAN speed: {can_speed}") - return 0 - - total_bits = sum(message.bit_count * message.frequency for message in messages) + if can_speed <= 0: + raise ValueError(f"Invalid CAN speed {can_speed}. Must be positive.") - if total_bits < 0: - return 0 + total_bits = sum(message.bit_count * message.frequency for message in messages) *1.2 # 1.2x is for bit stuffing - bus_load = ((total_bits) * 1.2) / can_speed * 100 - return round(bus_load, 2) + bus_load = total_bits / can_speed * 100 + + return bus_load From b146bb6cef89930bb88eb445ecded3aff396f4b3 Mon Sep 17 00:00:00 2001 From: BlakeFreer Date: Thu, 26 Dec 2024 13:56:01 -0500 Subject: [PATCH 09/10] adds instruction for formatting code --- scripts/cangen/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/cangen/README.md b/scripts/cangen/README.md index 529107b9c..720f24766 100644 --- a/scripts/cangen/README.md +++ b/scripts/cangen/README.md @@ -55,3 +55,9 @@ To run the tests: ```bash poetry run pytest ``` + +To format the code using `ruff`: + +```bash +poetry run ruff format +``` From e7fef316ea9a0b4ed296b5ebd7bab282cfa8f00f Mon Sep 17 00:00:00 2001 From: Manush Date: Thu, 26 Dec 2024 14:05:46 -0500 Subject: [PATCH 10/10] ruff format implemented for test and traffic file --- scripts/cangen/cangen/traffic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/cangen/cangen/traffic.py b/scripts/cangen/cangen/traffic.py index 155f4b8f8..e89b4c328 100644 --- a/scripts/cangen/cangen/traffic.py +++ b/scripts/cangen/cangen/traffic.py @@ -16,6 +16,7 @@ class CANMessage: def bit_count(self): return 44 + 8 * self.data_length + def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float: """Calculates the bus load percentage based on CAN messages and bus speed @@ -32,8 +33,10 @@ def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float: if can_speed <= 0: raise ValueError(f"Invalid CAN speed {can_speed}. Must be positive.") - total_bits = sum(message.bit_count * message.frequency for message in messages) *1.2 # 1.2x is for bit stuffing + total_bits = ( + sum(message.bit_count * message.frequency for message in messages) * 1.2 + ) # 1.2x is for bit stuffing bus_load = total_bits / can_speed * 100 - + return bus_load