From f7b00f4ca22557b9f6cd5bac36199fc9e0f21835 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Wed, 29 Oct 2025 18:31:07 -0400 Subject: [PATCH 1/7] Added 2 tests for each message packet definition. --- tests/test_can_messages.py | 838 +++++++++++++++++++++++++------------ 1 file changed, 579 insertions(+), 259 deletions(-) diff --git a/tests/test_can_messages.py b/tests/test_can_messages.py index c453598..1b70517 100644 --- a/tests/test_can_messages.py +++ b/tests/test_can_messages.py @@ -1,11 +1,12 @@ import pytest import parsley -from bitstring import BitString -from fields import ASCII, Enum, Numeric -from message_definitions import TIMESTAMP_2, TIMESTAMP_3, CAN_MESSAGE +from pytest import approx +from parsley.bitstring import BitString +from parsley.fields import ASCII, Enum, Numeric, Floating, Bitfield +from parsley.message_definitions import TIMESTAMP_2, MESSAGES -import message_types as mt +import parsley.message_types as mt import test_utils as tu class TestCANMessage: @@ -13,264 +14,583 @@ class TestCANMessage: We are testing only the message body of CAN messages, which means that BitString won't contain a message_sid (ie. message_type | board_id where '|' represents bitwise OR) """ + @pytest.fixture() def bit_str2(self): bit_str2 = BitString() - bit_str2.push(*TIMESTAMP_2.encode(0)) + # TIMESTAMP_2 is the first field in the fields list, so it needs to be pushed first. + # It reads raw data in milliseconds and outputs seconds (scale=1/1000). + bit_str2.push(*TIMESTAMP_2.encode(3)) return bit_str2 - @pytest.fixture() - def bit_str3(self): - bit_str3 = BitString() - bit_str3.push(*TIMESTAMP_3.encode(0)) - return bit_str3 - - def test_general_cmd(self, bit_str3): - bit_str3.push(*Enum('command', 8, mt.gen_cmd).encode('BUS_DOWN_WARNING')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_CMD')[1:]) - assert res['command'] == 'BUS_DOWN_WARNING' - - def test_actuator_cmd(self, bit_str3): - bit_str3.push(*Enum('actuator', 8, mt.actuator_id).encode('ACTUATOR_VENT_VALVE')) - bit_str3.push(*Enum('req_state', 8, mt.actuator_states).encode('ACTUATOR_OFF')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('ACTUATOR_CMD')[1:]) - assert res['actuator'] == 'ACTUATOR_VENT_VALVE' - assert res['req_state'] == 'ACTUATOR_OFF' - - def test_alt_arm_cmd(self, bit_str3): - bit_str3.push(*Enum('state', 4, mt.arm_states).encode('ARMED')) - bit_str3.push(*Numeric('altimeter', 4).encode(7)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('ALT_ARM_CMD')[1:]) - assert res['state'] == 'ARMED' - assert res['altimeter'] == 7 - - def test_reset_cmd(self, bit_str3): - bit_str3.push(*Enum('board_id', 8, mt.board_id).encode('ANY')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('RESET_CMD')[1:]) - assert res['board_id'] == 'ANY' - - def test_debug_msg(self, bit_str3): - bit_str3.push(*Numeric('level', 4).encode(6)) - bit_str3.push(*Numeric('line', 12).encode(0x123)) - bit_str3.push(*ASCII('data', 24).encode('AC')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('DEBUG_MSG')[1:]) - assert res['level'] == 6 - assert res['line'] == 0x123 - assert res['data'] == 'AC' - - # don't need pytest fixtures since this message type doesn't contain a timestamp - def test_debug_printf(self): - bit_str = BitString() - bit_str.push(*ASCII('string', 64).encode('ABCDEFGH')) - res = parsley.parse_fields(bit_str, CAN_MESSAGE.get_fields('DEBUG_PRINTF')[1:]) - assert res['string'] == 'ABCDEFGH' - - # don't need pytest fixtures since this message type doesn't contain a timestamp - def test_debug_radio_cmd(self): - bit_str = BitString() - bit_str.push(*ASCII('string', 64).encode('RADIO')) - res = parsley.parse_fields(bit_str, CAN_MESSAGE.get_fields('DEBUG_RADIO_CMD')[1:]) - assert res['string'] == 'RADIO' - - def test_actuator_status(self, bit_str3): - bit_str3.push(*Enum('actuator', 8, mt.actuator_id).encode('ACTUATOR_INJECTOR_VALVE')) - bit_str3.push(*Enum('cur_state', 8, mt.actuator_states).encode('ACTUATOR_UNK')) - bit_str3.push(*Enum('req_state', 8, mt.actuator_states).encode('ACTUATOR_OFF')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('ACTUATOR_STATUS')[1:]) - assert res['actuator'] == 'ACTUATOR_INJECTOR_VALVE' - assert res['cur_state'] == 'ACTUATOR_UNK' - assert res['req_state'] == 'ACTUATOR_OFF' - - def test_alt_arm_status(self, bit_str3): - bit_str3.push(*Enum('state', 4, mt.arm_states).encode('DISARMED')) - bit_str3.push(*Numeric('altimeter', 4).encode(4)) - bit_str3.push(*Numeric('drogue_v', 16).encode(12345)) - bit_str3.push(*Numeric('main_v', 16).encode(54321)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('ALT_ARM_STATUS')[1:]) - assert res['state'] == 'DISARMED' - assert res['altimeter'] == 4 - assert res['drogue_v'] == 12345 - assert res['main_v'] == 54321 - - def test_board_status_nominal(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_NOMINAL')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_NOMINAL' - - def test_board_status_current(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_BUS_OVER_CURRENT')) - bit_str3.push(*Numeric('current', 16).encode(12345)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_BUS_OVER_CURRENT' - assert res['current'] == 12345 - - def test_board_status_voltage(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_BUS_UNDER_VOLTAGE')) - bit_str3.push(*Numeric('voltage', 16).encode(54321)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_BUS_UNDER_VOLTAGE' - assert res['voltage'] == 54321 - - def test_board_status_dead(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_BOARD_FEARED_DEAD')) - bit_str3.push(*Enum('board_id', 8, mt.board_id).encode('KALMAN')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - print(res) - assert res['status'] == 'E_BOARD_FEARED_DEAD' - assert res['board_id'] == 'KALMAN' - - def test_board_status_quiet(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_NO_CAN_TRAFFIC')) - bit_str3.push(*Numeric('err_time', 16).encode(54321)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_NO_CAN_TRAFFIC' - assert res['err_time'] == 54321 - - def test_board_status_actuator(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_ACTUATOR_STATE')) - bit_str3.push(*Enum('req_state', 8, mt.actuator_states).encode('ACTUATOR_OFF')) - bit_str3.push(*Enum('cur_state', 8, mt.actuator_states).encode('ACTUATOR_UNK')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_ACTUATOR_STATE' - assert res['req_state'] == 'ACTUATOR_OFF' - assert res['cur_state'] == 'ACTUATOR_UNK' - - def test_board_status_logging(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_LOGGING')) - bit_str3.push(*Enum('error', 8, mt.logger_error).encode('E_SYSLOG_ALL_BUFFERS_FULL')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_LOGGING' - assert res['error'] == 'E_SYSLOG_ALL_BUFFERS_FULL' - - def test_board_status_sensor(self, bit_str3): - bit_str3.push(*Enum('status', 8, mt.board_status).encode('E_SENSOR')) - bit_str3.push(*Enum('sensor_id', 8, mt.sensor_id).encode('SENSOR_BARO')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GENERAL_BOARD_STATUS')[1:]) - assert res['status'] == 'E_SENSOR' - assert res['sensor_id'] == 'SENSOR_BARO' - - def test_sensor_temp(self, bit_str3): - bit_str3.push(*Numeric('sensor_id', 8).encode(0x12)) - bit_str3.push(*Numeric('temperature', 24, scale=1/2**10, signed=True).encode(12.5)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('SENSOR_TEMP')[1:]) - assert res['sensor_id'] == 0x12 - assert res['temperature'] == tu.approx(12.5) - - def test_sensor_altitude(self, bit_str3): - bit_str3.push(*Numeric('altitude', 32, signed=True).encode(-12345)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('SENSOR_ALTITUDE')[1:]) - assert res['altitude'] == -12345 - - def test_sensor_acc(self, bit_str2): - bit_str2.push(*Numeric('x', 16, scale=8/2**16, signed=True).encode(-2)) - bit_str2.push(*Numeric('y', 16, scale=8/2**16, signed=True).encode(-3)) - bit_str2.push(*Numeric('z', 16, scale=8/2**16, signed=True).encode(-4)) - res = parsley.parse_fields(bit_str2, CAN_MESSAGE.get_fields('SENSOR_ACC')[1:]) - assert res['x'] == tu.approx(-2) - assert res['y'] == tu.approx(-3) - assert res['z'] == tu.approx(-4) - - def test_sensor_acc2(self, bit_str2): - bit_str2.push(*Numeric('x', 16, scale=16/2**16, signed=True).encode(3)) - bit_str2.push(*Numeric('y', 16, scale=16/2**16, signed=True).encode(0)) - bit_str2.push(*Numeric('z', 16, scale=16/2**16, signed=True).encode(-3)) - res = parsley.parse_fields(bit_str2, CAN_MESSAGE.get_fields('SENSOR_ACC2')[1:]) - assert res['x'] == tu.approx(3) - assert res['y'] == tu.approx(0) - assert res['z'] == tu.approx(-3) - - def test_sensor_gyro(self, bit_str2): - bit_str2.push(*Numeric('x', 16, scale=2000/2**16, signed=True).encode(3)) - bit_str2.push(*Numeric('y', 16, scale=2000/2**16, signed=True).encode(4)) - bit_str2.push(*Numeric('z', 16, scale=2000/2**16, signed=True).encode(5)) - res = parsley.parse_fields(bit_str2, CAN_MESSAGE.get_fields('SENSOR_GYRO')[1:]) - assert res['x'] == tu.approx(3) - assert res['y'] == tu.approx(4) - assert res['z'] == tu.approx(5) - def test_sensor_mag(self, bit_str2): - bit_str2.push(*Numeric('x', 16, signed=True).encode(-100)) - bit_str2.push(*Numeric('y', 16, signed=True).encode(-200)) - bit_str2.push(*Numeric('z', 16, signed=True).encode(-300)) - res = parsley.parse_fields(bit_str2, CAN_MESSAGE.get_fields('SENSOR_MAG')[1:]) - assert res['x'] == tu.approx(-100) - assert res['y'] == tu.approx(-200) - assert res['z'] == tu.approx(-300) - - def test_sensor_analog(self, bit_str2): - bit_str2.push(*Enum('sensor_id', 8, mt.sensor_id).encode('SENSOR_BARO')) - bit_str2.push(*Numeric('value', 16, signed=True).encode(-12345)) - res = parsley.parse_fields(bit_str2, CAN_MESSAGE.get_fields('SENSOR_ANALOG')[1:]) - assert res['sensor_id'] == 'SENSOR_BARO' - assert res['value'] == -12345 - - def test_gps_timestamp(self, bit_str3): - bit_str3.push(*Numeric('hrs', 8).encode(12)) - bit_str3.push(*Numeric('mins', 8).encode(23)) - bit_str3.push(*Numeric('secs', 8).encode(34)) - bit_str3.push(*Numeric('dsecs', 8).encode(45)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GPS_TIMESTAMP')[1:]) - assert res['hrs'] == 12 - assert res['mins'] == 23 - assert res['secs'] == 34 - assert res['dsecs'] == 45 - - def test_gps_latitude(self, bit_str3): - bit_str3.push(*Numeric('degs', 8).encode(12)) - bit_str3.push(*Numeric('mins', 8).encode(23)) - bit_str3.push(*Numeric('dmins', 16).encode(12345)) - bit_str3.push(*ASCII('direction', 8).encode('N')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GPS_LATITUDE')[1:]) - assert res['degs'] == 12 - assert res['mins'] == 23 - assert res['dmins'] == 12345 - assert res['direction'] == 'N' - - def test_gps_longitude(self, bit_str3): - bit_str3.push(*Numeric('degs', 8).encode(12)) - bit_str3.push(*Numeric('mins', 8).encode(23)) - bit_str3.push(*Numeric('dmins', 16).encode(12345)) - bit_str3.push(*ASCII('direction', 8).encode('W')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GPS_LONGITUDE')[1:]) - assert res['degs'] == 12 - assert res['mins'] == 23 - assert res['dmins'] == 12345 - assert res['direction'] == 'W' - - def test_gps_altitude(self, bit_str3): - bit_str3.push(*Numeric('altitude', 16).encode(12345)) - bit_str3.push(*Numeric('daltitude', 8).encode(12)) - bit_str3.push(*ASCII('unit', 8).encode('m')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GPS_ALTITUDE')[1:]) - assert res['altitude'] == 12345 - assert res['daltitude'] == 12 - assert res['unit'] == 'm' - - def test_gps_info(self, bit_str3): - bit_str3.push(*Numeric('num_sats', 8).encode(12)) - bit_str3.push(*Numeric('quality', 8).encode(23)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('GPS_INFO')[1:]) - assert res['num_sats'] == 12 - assert res['quality'] == 23 - - def test_fill_lvl(self, bit_str3): - bit_str3.push(*Numeric('level', 8).encode(9)) - bit_str3.push(*Enum('direction', 8, mt.fill_direction).encode('FILLING')) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('FILL_LVL')[1:]) - assert res['level'] == 9 - assert res['direction'] == 'FILLING' - - def test_radi_value(self, bit_str3): - bit_str3.push(*Numeric('radi_board', 8).encode(1)) - bit_str3.push(*Numeric('radi', 16).encode(500)) - res = parsley.parse_fields(bit_str3, CAN_MESSAGE.get_fields('RADI_VALUE')[1:]) - assert res['radi_board'] == 1 - assert res['radi'] == 500 - - def test_leds_on(self): - # LED_ON message has no message body - pass - - def test_leds_off(self): - # LED_OFF message has no message body - pass + def test1_general_board_status(self, bit_str2): + # 0b [...(32) 1011] [...(16)] + bit_str2.push(b"\x00\x00\x00\x0B" + b"\x00\x00", 48) # 0x0B = 00001011 + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Bitfield("general_board_status", 32, "E_NOMINAL", mt.general_board_status_offset), + Bitfield("board_error_bitfield", 16, "E_NOMINAL", mt.board_specific_status_offset)]) + + assert res["general_board_status"] == 'E_5V_OVER_CURRENT|E_5V_OVER_VOLTAGE|E_12V_OVER_CURRENT' + assert res["board_error_bitfield"] == 'E_NOMINAL' + + def test2_general_board_status(self, bit_str2): + # 0b [...(32) 10100101] [...(16) 0111] + bit_str2.push(b"\x00\x00\x00\xA5" + b"\x00\x07", 48) # 0xA5 = 10100101. 0x07 = 0000000000000111 + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Bitfield("general_board_status", 32, "E_NOMINAL", mt.general_board_status_offset), + Bitfield("board_error_bitfield", 16, "E_NOMINAL", mt.board_specific_status_offset)]) + + # E_5V_OVER_CURRENT(0)|E_5V_UNDER_VOLTAGE(2)|E_12V_UNDER_VOLTAGE(5)|E_BATT_OVER_VOLTAGE(7) + assert res["general_board_status"] == 'E_5V_OVER_CURRENT|E_5V_UNDER_VOLTAGE|E_12V_UNDER_VOLTAGE|E_BATT_OVER_VOLTAGE' + # E_12V_EFUSE_FAULT(0)|E_5V_EFUSE_FAULT(1)|E_PT_OUT_OF_RANGE(2) + assert res["board_error_bitfield"] == 'E_12V_EFUSE_FAULT|E_5V_EFUSE_FAULT|E_PT_OUT_OF_RANGE' + + # --- RESET_CMD Tests --- + def test1_reset_cmd(self, bit_str2): + bit_str2.push(b"\x0A\x00", 16) # 0x0A (ALTIMETER), 0x00 (ANY) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("board_type_id", 8, mt.board_type_id), + Enum("board_inst_id", 8, mt.board_inst_id)]) + + assert res["board_type_id"] == 'ALTIMETER' + assert res["board_inst_id"] == 'ANY' + + def test2_reset_cmd(self, bit_str2): + bit_str2.push(b"\x82\x08", 16) # 0x82 (DAQ), 0x08 (RECOVERY) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("board_type_id", 8, mt.board_type_id), + Enum("board_inst_id", 8, mt.board_inst_id)]) + + assert res["board_type_id"] == 'DAQ' + assert res["board_inst_id"] == 'RECOVERY' + + # --- DEBUG_RAW Tests --- + def test1_debug_raw(self, bit_str2): + bit_str2.push(b"rawmsg", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + ASCII('string', 48)]) + + assert res["string"] == "rawmsg" + + def test2_debug_raw(self, bit_str2): + bit_str2.push(b"\n 1.${", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + ASCII('string', 48)]) + + assert res["string"] == "\n 1.${" + + # --- CONFIG_SET Tests (Fixed Assertions) --- + def test1_config_set(self, bit_str2): + # 0x01 (INJ_SENSOR), 0x02 (ROCKET), 0x0304 (ID), 0x0506 (Value) + bit_str2.push(b"\x01\x02\x03\x04\x05\x06", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("board_type_id", 8, mt.board_type_id), + Enum("board_inst_id", 8, mt.board_inst_id), + Numeric("config_id", 16), + Numeric("config_value", 16)]) + + assert res["board_type_id"] == 'INJ_SENSOR' + assert res["board_inst_id"] == 'ROCKET' + assert res["config_id"] == 0x0304 + assert res["config_value"] == 0x0506 + + def test2_config_set(self, bit_str2): + # 0x80 (RLCS_GLS), 0x01 (GROUND), 0xABCD (ID), 0xEF00 (Value) + bit_str2.push(b"\x80\x01\xAB\xCD\xEF\x00", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("board_type_id", 8, mt.board_type_id), + Enum("board_inst_id", 8, mt.board_inst_id), + Numeric("config_id", 16), + Numeric("config_value", 16)]) + + assert res["board_type_id"] == 'RLCS_GLS' + assert res["board_inst_id"] == 'GROUND' + assert res["config_id"] == 0xABCD + assert res["config_value"] == 0xEF00 + + # --- CONFIG_STATUS Tests --- + def test1_config_status(self, bit_str2): + # 0x1122 (ID), 0x3344 (Value) + bit_str2.push(b"\x11\x22\x33\x44", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("config_id", 16), + Numeric("config_value", 16)]) + + assert res["config_id"] == 0x1122 + assert res["config_value"] == 0x3344 + + def test2_config_status(self, bit_str2): + # 0xEEFF (ID), 0xAABB (Value) + bit_str2.push(b"\xEE\xFF\xAA\xBB", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("config_id", 16), + Numeric("config_value", 16)]) + + assert res["config_id"] == 0xEEFF + assert res["config_value"] == 0xAABB + + # --- ACTUATOR_CMD Tests --- + def test1_actuator_cmd(self, bit_str2): + # 0x00 (ACTUATOR_OX_INJECTOR_VALVE), 0x00 (ACT_STATE_ON) + bit_str2.push(b"\x00\x00", 16) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Enum("cmd_state", 8, mt.actuator_state)]) + + assert res["actuator"] == "ACTUATOR_OX_INJECTOR_VALVE" + assert res["cmd_state"] == "ACT_STATE_ON" + + def test2_actuator_cmd(self, bit_str2): + # 0x0C (ACTUATOR_CAMERA_PAYLOAD), 0x01 (ACT_STATE_OFF) + bit_str2.push(b"\x0C\x01", 16) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Enum("cmd_state", 8, mt.actuator_state)]) + + assert res["actuator"] == "ACTUATOR_CAMERA_PAYLOAD" + assert res["cmd_state"] == "ACT_STATE_OFF" + + # --- ACTUATOR_ANALOG_CMD Tests --- + def test1_actuator_analog_cmd(self, bit_str2): + # 0x11 (ACTUATOR_CANARD_ANGLE), 0x0100 (cmd_state=256) + bit_str2.push(b"\x11\x01\x00", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Numeric("cmd_state", 16)]) + + assert res["actuator"] == "ACTUATOR_CANARD_ANGLE" + assert res["cmd_state"] == 256 + + def test2_actuator_analog_cmd(self, bit_str2): + # 0x10 (ACTUATOR_CANARD_ENABLE), 0xFFFE (cmd_state=65534) + bit_str2.push(b"\x10\xFF\xFE", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Numeric("cmd_state", 16)]) + + assert res["actuator"] == "ACTUATOR_CANARD_ENABLE" + assert res["cmd_state"] == 65534 + + # --- ACTUATOR_STATUS Tests --- + def test1_actuator_status(self, bit_str2): + # 0x01 (ACTUATOR_FUEL_INJECTOR_VALVE), 0x00 (curr_state=ON), 0x01 (cmd_state=OFF) + bit_str2.push(b"\x01\x00\x01", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Enum("curr_state", 8, mt.actuator_state), + Enum("cmd_state", 8, mt.actuator_state)]) + + assert res["actuator"] == "ACTUATOR_FUEL_INJECTOR_VALVE" + assert res["curr_state"] == "ACT_STATE_ON" + assert res["cmd_state"] == "ACT_STATE_OFF" + + def test2_actuator_status(self, bit_str2): + # 0x08 (ACTUATOR_TELEMETRY), 0x03 (curr_state=ILLEGAL), 0x02 (cmd_state=UNK) + bit_str2.push(b"\x08\x03\x02", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("actuator", 8, mt.actuator_id), + Enum("curr_state", 8, mt.actuator_state), + Enum("cmd_state", 8, mt.actuator_state)]) + + assert res["actuator"] == "ACTUATOR_TELEMETRY" + assert res["curr_state"] == "ACT_STATE_ILLEGAL" + assert res["cmd_state"] == "ACT_STATE_UNK" + + # --- ALT_ARM_CMD Tests --- + def test1_alt_arm_cmd(self, bit_str2): + # 0x02 (ALTIMETER_ROCKET_SRAD), 0x01 (ALT_ARM_STATE_ARMED) + bit_str2.push(b"\x02\x01", 16) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("alt_id", 8, mt.altimeter_id), + Enum("alt_arm_state", 8, mt.alt_arm_state)]) + + assert res["alt_id"] == "ALTIMETER_ROCKET_SRAD" + assert res["alt_arm_state"] == "ALT_ARM_STATE_ARMED" + + def test2_alt_arm_cmd(self, bit_str2): + # 0x04 (ALTIMETER_PAYLOAD_STRATOLOGGER), 0x00 (ALT_ARM_STATE_DISARMED) + bit_str2.push(b"\x04\x00", 16) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("alt_id", 8, mt.altimeter_id), + Enum("alt_arm_state", 8, mt.alt_arm_state)]) + + assert res["alt_id"] == "ALTIMETER_PAYLOAD_STRATOLOGGER" + assert res["alt_arm_state"] == "ALT_ARM_STATE_DISARMED" + + # --- ALT_ARM_STATUS Tests --- + def test1_alt_arm_status(self, bit_str2): + # 0x00 (ALTIMETER_ROCKET_RAVEN), 0x01 (ARMED), 0x0500 (drogue=1280), 0x0A00 (main=2560) + bit_str2.push(b"\x00\x01\x05\x00\x0A\x00", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("alt_id", 8, mt.altimeter_id), + Enum("alt_arm_state", 8, mt.alt_arm_state), + Numeric("drogue_v", 16), + Numeric("main_v", 16)]) + + assert res["alt_id"] == "ALTIMETER_ROCKET_RAVEN" + assert res["alt_arm_state"] == "ALT_ARM_STATE_ARMED" + assert res["drogue_v"] == 1280 + assert res["main_v"] == 2560 + + def test2_alt_arm_status(self, bit_str2): + # 0x03 (ALTIMETER_PAYLOAD_RAVEN), 0x00 (DISARMED), 0x0000 (drogue=0), 0xFFFF (main=65535) + bit_str2.push(b"\x03\x00\x00\x00\xFF\xFF", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum("alt_id", 8, mt.altimeter_id), + Enum("alt_arm_state", 8, mt.alt_arm_state), + Numeric("drogue_v", 16), + Numeric("main_v", 16)]) + + assert res["alt_id"] == "ALTIMETER_PAYLOAD_RAVEN" + assert res["alt_arm_state"] == "ALT_ARM_STATE_DISARMED" + assert res["drogue_v"] == 0 + assert res["main_v"] == 65535 + + # --- SENSOR_TEMP Tests (Fixed Data/Assertions) --- + def test1_sensor_temp(self, bit_str2): + # ID 1 (0x01), Temperature -128.0°C. Raw value for -128.0 * 1024 = -131072 = 0xFFFE0000 (signed 32-bit) + bit_str2.push(b"\x01\xFF\xFE\x00\x00", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("temp_sensor_id", 8), + Numeric("temperature", 32, scale=1/2**10, unit='°C', signed=True)]) + + assert res["temp_sensor_id"] == 1 + assert res["temperature"] == -128.0 + + def test2_sensor_temp(self, bit_str2): + # ID 2 (0x02), Temperature 25.0°C. Raw value for 25.0 * 1024 = 25600 = 0x00006400 + bit_str2.push(b"\x02\x00\x00\x64\x00", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("temp_sensor_id", 8), + Numeric("temperature", 32, scale=1/2**10, unit='°C', signed=True)]) + + assert res["temp_sensor_id"] == 2 + assert res["temperature"] == 25.0 + + # --- SENSOR_ALTITUDE Tests --- + def test1_sensor_altitude(self, bit_str2): + # Altitude 3000m (0x00000BB8), APOGEE_NOT_REACHED (0x01) + bit_str2.push(b"\x00\x00\x0B\xB8\x01", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("altitude", 32, signed=True), + Enum("apogee_state", 8, mt.apogee_state)]) + + assert res["altitude"] == 3000 + assert res["apogee_state"] == "APOGEE_NOT_REACHED" + + def test2_sensor_altitude(self, bit_str2): + # Altitude -500m (0xFFFFFE0C), APOGEE_REACHED (0x02) + bit_str2.push(b"\xFF\xFF\xFE\x0C\x02", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("altitude", 32, signed=True), + Enum("apogee_state", 8, mt.apogee_state)]) + + assert res["altitude"] == -500 + assert res["apogee_state"] == "APOGEE_REACHED" + + # --- SENSOR_IMU_X Tests --- + def test1_sensor_imu_x(self, bit_str2): + # 0x01 (IMU_PROC_MTI630), linear_accel 1024 (0x0400), angular_velocity 512 (0x0200) + bit_str2.push(b"\x01\x04\x00\x02\x00", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('linear_accel', 16), + Numeric('angular_velocity', 16)]) + + assert res["imu_id"] == "IMU_PROC_MTI630" + assert res["linear_accel"] == 1024 + assert res["angular_velocity"] == 512 + + def test2_sensor_imu_y(self, bit_str2): + # 0x03 (IMU_SRAD_ALT_ALTIMU10), linear_accel 65535 (0xFFFF), angular_velocity 0 (0x0000) + # Note: Testing SENSOR_IMU_Y with the same payload structure + bit_str2.push(b"\x03\xFF\xFF\x00\x00", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('linear_accel', 16), + Numeric('angular_velocity', 16)]) + + assert res["imu_id"] == "IMU_SRAD_ALT_ALTIMU10" + assert res["linear_accel"] == 65535 + assert res["angular_velocity"] == 0 + + # --- SENSOR_MAG_X Tests --- + def test1_sensor_mag_x(self, bit_str2): + # 0x00 (IMU_PROC_ALTIMU10), mag 1000 (0x03E8) + bit_str2.push(b"\x00\x03\xE8", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('mag', 16)]) + + assert res["imu_id"] == "IMU_PROC_ALTIMU10" + assert res["mag"] == 1000 + + def test2_sensor_mag_y(self, bit_str2): + # 0x02 (IMU_PROC_LSM6DSO32), mag 40000 (0x9C40) + # Note: Testing SENSOR_MAG_Y with the same payload structure + bit_str2.push(b"\x02\x9C\x40", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('mag', 16)]) + + assert res["imu_id"] == "IMU_PROC_LSM6DSO32" + assert res["mag"] == 40000 + + # --- SENSOR_BARO Tests --- + def test1_sensor_baro(self, bit_str2): + # 0x00 (IMU_PROC_ALTIMU10), pressure 101325 (0x018B6D), temp 298 (0x012A) + bit_str2.push(b"\x00\x01\x8B\x6D\x01\x2A", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('pressure', 24), + Numeric('temp', 16)]) + + assert res["imu_id"] == "IMU_PROC_ALTIMU10" + assert res["pressure"] == 101229 + assert res["temp"] == 298 + + def test2_sensor_baro(self, bit_str2): + # 0x03 (IMU_SRAD_ALT_ALTIMU10), pressure 50000 (0x00C350), temp 350 (0x015E) + bit_str2.push(b"\x03\x00\xC3\x50\x01\x5E", 48) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('imu_id', 8, mt.imu_id), + Numeric('pressure', 24), + Numeric('temp', 16)]) + + assert res["imu_id"] == "IMU_SRAD_ALT_ALTIMU10" + assert res["pressure"] == 50000 + assert res["temp"] == 350 + + # --- SENSOR_ANALOG Tests --- + def test1_sensor_analog(self, bit_str2): + # 0x07 (SENSOR_BATT_CURR), value 1000 (0x03E8) + bit_str2.push(b"\x07\x03\xE8", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('sensor_id', 8, mt.analog_sensor_id), + Numeric('value', 16)]) + + assert res["sensor_id"] == "SENSOR_BATT_CURR" + assert res["value"] == 1000 + + def test2_sensor_analog(self, bit_str2): + # 0x0F (SENSOR_PT_CHANNEL_3), value 65535 (0xFFFF) + bit_str2.push(b"\x0F\xFF\xFF", 24) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('sensor_id', 8, mt.analog_sensor_id), + Numeric('value', 16)]) + + assert res["sensor_id"] == "SENSOR_PT_CHANNEL_3" + assert res["value"] == 65535 + + # --- GPS_TIMESTAMP Tests --- + def test1_gps_timestamp(self, bit_str2): + # 10 hrs (0x0A), 30 mins (0x1E), 59 secs (0x3B), 99 dsecs (0x63) -> 10:30:59.99 + bit_str2.push(b"\x0A\x1E\x3B\x63", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('hrs', 8), + Numeric('mins', 8), + Numeric('secs', 8), + Numeric('dsecs', 8)]) + + assert res["hrs"] == 10 + assert res["mins"] == 30 + assert res["secs"] == 59 + assert res["dsecs"] == 99 + + def test2_gps_timestamp(self, bit_str2): + # 0 hrs (0x00), 0 mins (0x00), 0 secs (0x00), 0 dsecs (0x00) -> 00:00:00.00 + bit_str2.push(b"\x00\x00\x00\x00", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('hrs', 8), + Numeric('mins', 8), + Numeric('secs', 8), + Numeric('dsecs', 8)]) + + assert res["hrs"] == 0 + assert res["mins"] == 0 + assert res["secs"] == 0 + assert res["dsecs"] == 0 + + # --- GPS_LATITUDE Tests --- + def test1_gps_latitude(self, bit_str2): + # 43 degs (0x2B), 28 mins (0x1C), 1234 dmins (0x04D2), 'N' (0x4E) + bit_str2.push(b"\x2B\x1C\x04\xD2\x4E", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('degs', 8), + Numeric('mins', 8), + Numeric('dmins', 16), + ASCII('direction', 8)]) + + assert res["degs"] == 43 + assert res["mins"] == 28 + assert res["dmins"] == 1234 + assert res["direction"] == 'N' + + def test2_gps_latitude(self, bit_str2): + # 10 degs (0x0A), 59 mins (0x3B), 9999 dmins (0x270F), 'S' (0x53) + bit_str2.push(b"\x0A\x3B\x27\x0F\x53", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('degs', 8), + Numeric('mins', 8), + Numeric('dmins', 16), + ASCII('direction', 8)]) + + assert res["degs"] == 10 + assert res["mins"] == 59 + assert res["dmins"] == 9999 + assert res["direction"] == 'S' + + # --- GPS_LONGITUDE Tests --- + def test1_gps_longitude(self, bit_str2): + # 79 degs (0x4F), 59 mins (0x3B), 5678 dmins (0x162E), 'W' (0x57) + bit_str2.push(b"\x4F\x3B\x16\x2E\x57", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('degs', 8), + Numeric('mins', 8), + Numeric('dmins', 16), + ASCII('direction', 8)]) + + assert res["degs"] == 79 + assert res["mins"] == 59 + assert res["dmins"] == 5678 + assert res["direction"] == 'W' + + def test2_gps_longitude(self, bit_str2): + # 179 degs (0xB3), 0 mins (0x00), 1 dmins (0x0001), 'E' (0x45) + bit_str2.push(b"\xB3\x00\x00\x01\x45", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('degs', 8), + Numeric('mins', 8), + Numeric('dmins', 16), + ASCII('direction', 8)]) + + assert res["degs"] == 179 + assert res["mins"] == 0 + assert res["dmins"] == 1 + assert res["direction"] == 'E' + + # --- GPS_ALTITUDE Tests --- + def test1_gps_altitude(self, bit_str2): + # altitude 500 (0x01F4), daltitude 25 (0x19), 'M' (0x4D) -> 500.25 M + bit_str2.push(b"\x01\xF4\x19\x4D", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('altitude', 16), + Numeric('daltitude', 8), + ASCII('unit', 8)]) + + assert res["altitude"] == 500 + assert res["daltitude"] == 25 + assert res["unit"] == 'M' + + def test2_gps_altitude(self, bit_str2): + # altitude 10000 (0x2710), daltitude 99 (0x63), 'F' (0x46) -> 10000.99 F + bit_str2.push(b"\x27\x10\x63\x46", 32) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric('altitude', 16), + Numeric('daltitude', 8), + ASCII('unit', 8)]) + + assert res["altitude"] == 10000 + assert res["daltitude"] == 99 + assert res["unit"] == 'F' + + # --- GPS_INFO Tests --- + def test1_gps_info(self, bit_str2): + bit_str2.push(b"\x05\x02", 16) # 5 sats, quality 2 + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("num_sats", 8), + Numeric("quality", 8)]) + + assert res["num_sats"] == 5 + assert res["quality"] == 2 + + def test2_gps_info(self, bit_str2): + bit_str2.push(b"\x08\x03", 16) # 8 sats, quality 3 + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Numeric("num_sats", 8), + Numeric("quality", 8)]) + + assert res["num_sats"] == 8 + assert res["quality"] == 3 + + # --- STATE_EST_DATA Tests --- + def test1_state_est_data(self, bit_str2): + # 0x0A (STATE_ID_ALT), data 123.45 (approx 0x42F6E666 float) + bit_str2.push(b"\x0A\x42\xF6\xE6\x66", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('state_id', 8, mt.state_est_id), + Floating('data', big_endian=True)]) + + assert res["state_id"] == "STATE_ID_ALT" + assert res["data"] == approx(123.45, abs=1e-3) + + def test2_state_est_data(self, bit_str2): + # 0x04 (STATE_ID_RATE_WX), data -0.01 (approx 0xBCA3D70A float) + bit_str2.push(b"\x04\xBC\xA3\xD7\x0A", 40) + res = parsley.parse_fields(bit_str2, + [TIMESTAMP_2, + Enum('state_id', 8, mt.state_est_id), + Floating('data', big_endian=True)]) + + assert res["state_id"] == "STATE_ID_RATE_WX" + assert res["data"] == approx(-0.02, abs=1e-3) + + # --- LEDS_ON Tests --- + def test1_leds_on(self, bit_str2): + # LEDS_ON only has MESSAGE_PRIO, BOARD_TYPE_ID, BOARD_INST_ID in its definition. + # The parsley fields list is empty after TIMESTAMP_2. + # It's an empty message payload. + res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) # Only TIMESTAMP_2 in the payload + print(res["time"]) + assert res['time'] + + def test2_leds_off(self, bit_str2): + # LEDS_OFF only has MESSAGE_PRIO, BOARD_TYPE_ID, BOARD_INST_ID in its definition. + res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) + print(res["time"]) + assert res['time'] + From 8c78179ac0f13c89c193f779e31fdf8b07ebbcec Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Wed, 29 Oct 2025 18:38:39 -0400 Subject: [PATCH 2/7] Removed print statements --- tests/test_can_messages.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_can_messages.py b/tests/test_can_messages.py index 1b70517..46e2096 100644 --- a/tests/test_can_messages.py +++ b/tests/test_can_messages.py @@ -585,12 +585,10 @@ def test1_leds_on(self, bit_str2): # The parsley fields list is empty after TIMESTAMP_2. # It's an empty message payload. res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) # Only TIMESTAMP_2 in the payload - print(res["time"]) - assert res['time'] + assert res['time'] == approx(3, abs=1e-3) def test2_leds_off(self, bit_str2): # LEDS_OFF only has MESSAGE_PRIO, BOARD_TYPE_ID, BOARD_INST_ID in its definition. res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) - print(res["time"]) - assert res['time'] + assert res['time'] == approx(3, abs=1e-3) From ef07330499e44b56685933ac2d41dacd7180a7c7 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Wed, 29 Oct 2025 18:39:45 -0400 Subject: [PATCH 3/7] removed import test_utils --- tests/test_can_messages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_can_messages.py b/tests/test_can_messages.py index 46e2096..5746aa6 100644 --- a/tests/test_can_messages.py +++ b/tests/test_can_messages.py @@ -7,7 +7,6 @@ from parsley.message_definitions import TIMESTAMP_2, MESSAGES import parsley.message_types as mt -import test_utils as tu class TestCANMessage: """ From 6de9efebea4a34c1f341d24acfee7d8af9b2207f Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Tue, 4 Nov 2025 16:37:42 -0500 Subject: [PATCH 4/7] Fixed test_can_messages. Removed half the tests and uneccessary comments --- tests/test_can_messages.py | 336 +++---------------------------------- 1 file changed, 24 insertions(+), 312 deletions(-) diff --git a/tests/test_can_messages.py b/tests/test_can_messages.py index 5746aa6..a94dd70 100644 --- a/tests/test_can_messages.py +++ b/tests/test_can_messages.py @@ -17,15 +17,13 @@ class TestCANMessage: @pytest.fixture() def bit_str2(self): bit_str2 = BitString() - # TIMESTAMP_2 is the first field in the fields list, so it needs to be pushed first. - # It reads raw data in milliseconds and outputs seconds (scale=1/1000). bit_str2.push(*TIMESTAMP_2.encode(3)) return bit_str2 - def test1_general_board_status(self, bit_str2): + def test_general_board_status(self, bit_str2): # 0b [...(32) 1011] [...(16)] - bit_str2.push(b"\x00\x00\x00\x0B" + b"\x00\x00", 48) # 0x0B = 00001011 + bit_str2.push(b"\x00\x00\x00\x0B" + b"\x00\x00", 48) res = parsley.parse_fields(bit_str2, [TIMESTAMP_2, Bitfield("general_board_status", 32, "E_NOMINAL", mt.general_board_status_offset), @@ -34,21 +32,7 @@ def test1_general_board_status(self, bit_str2): assert res["general_board_status"] == 'E_5V_OVER_CURRENT|E_5V_OVER_VOLTAGE|E_12V_OVER_CURRENT' assert res["board_error_bitfield"] == 'E_NOMINAL' - def test2_general_board_status(self, bit_str2): - # 0b [...(32) 10100101] [...(16) 0111] - bit_str2.push(b"\x00\x00\x00\xA5" + b"\x00\x07", 48) # 0xA5 = 10100101. 0x07 = 0000000000000111 - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Bitfield("general_board_status", 32, "E_NOMINAL", mt.general_board_status_offset), - Bitfield("board_error_bitfield", 16, "E_NOMINAL", mt.board_specific_status_offset)]) - - # E_5V_OVER_CURRENT(0)|E_5V_UNDER_VOLTAGE(2)|E_12V_UNDER_VOLTAGE(5)|E_BATT_OVER_VOLTAGE(7) - assert res["general_board_status"] == 'E_5V_OVER_CURRENT|E_5V_UNDER_VOLTAGE|E_12V_UNDER_VOLTAGE|E_BATT_OVER_VOLTAGE' - # E_12V_EFUSE_FAULT(0)|E_5V_EFUSE_FAULT(1)|E_PT_OUT_OF_RANGE(2) - assert res["board_error_bitfield"] == 'E_12V_EFUSE_FAULT|E_5V_EFUSE_FAULT|E_PT_OUT_OF_RANGE' - - # --- RESET_CMD Tests --- - def test1_reset_cmd(self, bit_str2): + def test_reset_cmd(self, bit_str2): bit_str2.push(b"\x0A\x00", 16) # 0x0A (ALTIMETER), 0x00 (ANY) res = parsley.parse_fields(bit_str2, [TIMESTAMP_2, @@ -58,18 +42,7 @@ def test1_reset_cmd(self, bit_str2): assert res["board_type_id"] == 'ALTIMETER' assert res["board_inst_id"] == 'ANY' - def test2_reset_cmd(self, bit_str2): - bit_str2.push(b"\x82\x08", 16) # 0x82 (DAQ), 0x08 (RECOVERY) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("board_type_id", 8, mt.board_type_id), - Enum("board_inst_id", 8, mt.board_inst_id)]) - - assert res["board_type_id"] == 'DAQ' - assert res["board_inst_id"] == 'RECOVERY' - - # --- DEBUG_RAW Tests --- - def test1_debug_raw(self, bit_str2): + def test_debug_raw(self, bit_str2): bit_str2.push(b"rawmsg", 48) res = parsley.parse_fields(bit_str2, [TIMESTAMP_2, @@ -77,16 +50,7 @@ def test1_debug_raw(self, bit_str2): assert res["string"] == "rawmsg" - def test2_debug_raw(self, bit_str2): - bit_str2.push(b"\n 1.${", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - ASCII('string', 48)]) - - assert res["string"] == "\n 1.${" - - # --- CONFIG_SET Tests (Fixed Assertions) --- - def test1_config_set(self, bit_str2): + def test_config_set(self, bit_str2): # 0x01 (INJ_SENSOR), 0x02 (ROCKET), 0x0304 (ID), 0x0506 (Value) bit_str2.push(b"\x01\x02\x03\x04\x05\x06", 48) res = parsley.parse_fields(bit_str2, @@ -100,24 +64,8 @@ def test1_config_set(self, bit_str2): assert res["board_inst_id"] == 'ROCKET' assert res["config_id"] == 0x0304 assert res["config_value"] == 0x0506 - - def test2_config_set(self, bit_str2): - # 0x80 (RLCS_GLS), 0x01 (GROUND), 0xABCD (ID), 0xEF00 (Value) - bit_str2.push(b"\x80\x01\xAB\xCD\xEF\x00", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("board_type_id", 8, mt.board_type_id), - Enum("board_inst_id", 8, mt.board_inst_id), - Numeric("config_id", 16), - Numeric("config_value", 16)]) - assert res["board_type_id"] == 'RLCS_GLS' - assert res["board_inst_id"] == 'GROUND' - assert res["config_id"] == 0xABCD - assert res["config_value"] == 0xEF00 - - # --- CONFIG_STATUS Tests --- - def test1_config_status(self, bit_str2): + def test_config_status(self, bit_str2): # 0x1122 (ID), 0x3344 (Value) bit_str2.push(b"\x11\x22\x33\x44", 32) res = parsley.parse_fields(bit_str2, @@ -128,19 +76,7 @@ def test1_config_status(self, bit_str2): assert res["config_id"] == 0x1122 assert res["config_value"] == 0x3344 - def test2_config_status(self, bit_str2): - # 0xEEFF (ID), 0xAABB (Value) - bit_str2.push(b"\xEE\xFF\xAA\xBB", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("config_id", 16), - Numeric("config_value", 16)]) - - assert res["config_id"] == 0xEEFF - assert res["config_value"] == 0xAABB - - # --- ACTUATOR_CMD Tests --- - def test1_actuator_cmd(self, bit_str2): + def test_actuator_cmd(self, bit_str2): # 0x00 (ACTUATOR_OX_INJECTOR_VALVE), 0x00 (ACT_STATE_ON) bit_str2.push(b"\x00\x00", 16) res = parsley.parse_fields(bit_str2, @@ -151,19 +87,7 @@ def test1_actuator_cmd(self, bit_str2): assert res["actuator"] == "ACTUATOR_OX_INJECTOR_VALVE" assert res["cmd_state"] == "ACT_STATE_ON" - def test2_actuator_cmd(self, bit_str2): - # 0x0C (ACTUATOR_CAMERA_PAYLOAD), 0x01 (ACT_STATE_OFF) - bit_str2.push(b"\x0C\x01", 16) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Enum("cmd_state", 8, mt.actuator_state)]) - - assert res["actuator"] == "ACTUATOR_CAMERA_PAYLOAD" - assert res["cmd_state"] == "ACT_STATE_OFF" - - # --- ACTUATOR_ANALOG_CMD Tests --- - def test1_actuator_analog_cmd(self, bit_str2): + def test_actuator_analog_cmd(self, bit_str2): # 0x11 (ACTUATOR_CANARD_ANGLE), 0x0100 (cmd_state=256) bit_str2.push(b"\x11\x01\x00", 24) res = parsley.parse_fields(bit_str2, @@ -174,19 +98,7 @@ def test1_actuator_analog_cmd(self, bit_str2): assert res["actuator"] == "ACTUATOR_CANARD_ANGLE" assert res["cmd_state"] == 256 - def test2_actuator_analog_cmd(self, bit_str2): - # 0x10 (ACTUATOR_CANARD_ENABLE), 0xFFFE (cmd_state=65534) - bit_str2.push(b"\x10\xFF\xFE", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Numeric("cmd_state", 16)]) - - assert res["actuator"] == "ACTUATOR_CANARD_ENABLE" - assert res["cmd_state"] == 65534 - - # --- ACTUATOR_STATUS Tests --- - def test1_actuator_status(self, bit_str2): + def test_actuator_status(self, bit_str2): # 0x01 (ACTUATOR_FUEL_INJECTOR_VALVE), 0x00 (curr_state=ON), 0x01 (cmd_state=OFF) bit_str2.push(b"\x01\x00\x01", 24) res = parsley.parse_fields(bit_str2, @@ -212,8 +124,7 @@ def test2_actuator_status(self, bit_str2): assert res["curr_state"] == "ACT_STATE_ILLEGAL" assert res["cmd_state"] == "ACT_STATE_UNK" - # --- ALT_ARM_CMD Tests --- - def test1_alt_arm_cmd(self, bit_str2): + def test_alt_arm_cmd(self, bit_str2): # 0x02 (ALTIMETER_ROCKET_SRAD), 0x01 (ALT_ARM_STATE_ARMED) bit_str2.push(b"\x02\x01", 16) res = parsley.parse_fields(bit_str2, @@ -224,19 +135,7 @@ def test1_alt_arm_cmd(self, bit_str2): assert res["alt_id"] == "ALTIMETER_ROCKET_SRAD" assert res["alt_arm_state"] == "ALT_ARM_STATE_ARMED" - def test2_alt_arm_cmd(self, bit_str2): - # 0x04 (ALTIMETER_PAYLOAD_STRATOLOGGER), 0x00 (ALT_ARM_STATE_DISARMED) - bit_str2.push(b"\x04\x00", 16) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("alt_id", 8, mt.altimeter_id), - Enum("alt_arm_state", 8, mt.alt_arm_state)]) - - assert res["alt_id"] == "ALTIMETER_PAYLOAD_STRATOLOGGER" - assert res["alt_arm_state"] == "ALT_ARM_STATE_DISARMED" - - # --- ALT_ARM_STATUS Tests --- - def test1_alt_arm_status(self, bit_str2): + def test_alt_arm_status(self, bit_str2): # 0x00 (ALTIMETER_ROCKET_RAVEN), 0x01 (ARMED), 0x0500 (drogue=1280), 0x0A00 (main=2560) bit_str2.push(b"\x00\x01\x05\x00\x0A\x00", 48) res = parsley.parse_fields(bit_str2, @@ -251,23 +150,7 @@ def test1_alt_arm_status(self, bit_str2): assert res["drogue_v"] == 1280 assert res["main_v"] == 2560 - def test2_alt_arm_status(self, bit_str2): - # 0x03 (ALTIMETER_PAYLOAD_RAVEN), 0x00 (DISARMED), 0x0000 (drogue=0), 0xFFFF (main=65535) - bit_str2.push(b"\x03\x00\x00\x00\xFF\xFF", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("alt_id", 8, mt.altimeter_id), - Enum("alt_arm_state", 8, mt.alt_arm_state), - Numeric("drogue_v", 16), - Numeric("main_v", 16)]) - - assert res["alt_id"] == "ALTIMETER_PAYLOAD_RAVEN" - assert res["alt_arm_state"] == "ALT_ARM_STATE_DISARMED" - assert res["drogue_v"] == 0 - assert res["main_v"] == 65535 - - # --- SENSOR_TEMP Tests (Fixed Data/Assertions) --- - def test1_sensor_temp(self, bit_str2): + def test_sensor_temp(self, bit_str2): # ID 1 (0x01), Temperature -128.0°C. Raw value for -128.0 * 1024 = -131072 = 0xFFFE0000 (signed 32-bit) bit_str2.push(b"\x01\xFF\xFE\x00\x00", 40) res = parsley.parse_fields(bit_str2, @@ -278,19 +161,7 @@ def test1_sensor_temp(self, bit_str2): assert res["temp_sensor_id"] == 1 assert res["temperature"] == -128.0 - def test2_sensor_temp(self, bit_str2): - # ID 2 (0x02), Temperature 25.0°C. Raw value for 25.0 * 1024 = 25600 = 0x00006400 - bit_str2.push(b"\x02\x00\x00\x64\x00", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("temp_sensor_id", 8), - Numeric("temperature", 32, scale=1/2**10, unit='°C', signed=True)]) - - assert res["temp_sensor_id"] == 2 - assert res["temperature"] == 25.0 - - # --- SENSOR_ALTITUDE Tests --- - def test1_sensor_altitude(self, bit_str2): + def test_sensor_altitude(self, bit_str2): # Altitude 3000m (0x00000BB8), APOGEE_NOT_REACHED (0x01) bit_str2.push(b"\x00\x00\x0B\xB8\x01", 40) res = parsley.parse_fields(bit_str2, @@ -301,19 +172,7 @@ def test1_sensor_altitude(self, bit_str2): assert res["altitude"] == 3000 assert res["apogee_state"] == "APOGEE_NOT_REACHED" - def test2_sensor_altitude(self, bit_str2): - # Altitude -500m (0xFFFFFE0C), APOGEE_REACHED (0x02) - bit_str2.push(b"\xFF\xFF\xFE\x0C\x02", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("altitude", 32, signed=True), - Enum("apogee_state", 8, mt.apogee_state)]) - - assert res["altitude"] == -500 - assert res["apogee_state"] == "APOGEE_REACHED" - - # --- SENSOR_IMU_X Tests --- - def test1_sensor_imu_x(self, bit_str2): + def test_sensor_imu_x(self, bit_str2): # 0x01 (IMU_PROC_MTI630), linear_accel 1024 (0x0400), angular_velocity 512 (0x0200) bit_str2.push(b"\x01\x04\x00\x02\x00", 40) res = parsley.parse_fields(bit_str2, @@ -326,22 +185,7 @@ def test1_sensor_imu_x(self, bit_str2): assert res["linear_accel"] == 1024 assert res["angular_velocity"] == 512 - def test2_sensor_imu_y(self, bit_str2): - # 0x03 (IMU_SRAD_ALT_ALTIMU10), linear_accel 65535 (0xFFFF), angular_velocity 0 (0x0000) - # Note: Testing SENSOR_IMU_Y with the same payload structure - bit_str2.push(b"\x03\xFF\xFF\x00\x00", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('linear_accel', 16), - Numeric('angular_velocity', 16)]) - - assert res["imu_id"] == "IMU_SRAD_ALT_ALTIMU10" - assert res["linear_accel"] == 65535 - assert res["angular_velocity"] == 0 - - # --- SENSOR_MAG_X Tests --- - def test1_sensor_mag_x(self, bit_str2): + def test_sensor_mag_x(self, bit_str2): # 0x00 (IMU_PROC_ALTIMU10), mag 1000 (0x03E8) bit_str2.push(b"\x00\x03\xE8", 24) res = parsley.parse_fields(bit_str2, @@ -352,20 +196,7 @@ def test1_sensor_mag_x(self, bit_str2): assert res["imu_id"] == "IMU_PROC_ALTIMU10" assert res["mag"] == 1000 - def test2_sensor_mag_y(self, bit_str2): - # 0x02 (IMU_PROC_LSM6DSO32), mag 40000 (0x9C40) - # Note: Testing SENSOR_MAG_Y with the same payload structure - bit_str2.push(b"\x02\x9C\x40", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('mag', 16)]) - - assert res["imu_id"] == "IMU_PROC_LSM6DSO32" - assert res["mag"] == 40000 - - # --- SENSOR_BARO Tests --- - def test1_sensor_baro(self, bit_str2): + def test_sensor_baro(self, bit_str2): # 0x00 (IMU_PROC_ALTIMU10), pressure 101325 (0x018B6D), temp 298 (0x012A) bit_str2.push(b"\x00\x01\x8B\x6D\x01\x2A", 48) res = parsley.parse_fields(bit_str2, @@ -378,21 +209,7 @@ def test1_sensor_baro(self, bit_str2): assert res["pressure"] == 101229 assert res["temp"] == 298 - def test2_sensor_baro(self, bit_str2): - # 0x03 (IMU_SRAD_ALT_ALTIMU10), pressure 50000 (0x00C350), temp 350 (0x015E) - bit_str2.push(b"\x03\x00\xC3\x50\x01\x5E", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('pressure', 24), - Numeric('temp', 16)]) - - assert res["imu_id"] == "IMU_SRAD_ALT_ALTIMU10" - assert res["pressure"] == 50000 - assert res["temp"] == 350 - - # --- SENSOR_ANALOG Tests --- - def test1_sensor_analog(self, bit_str2): + def test_sensor_analog(self, bit_str2): # 0x07 (SENSOR_BATT_CURR), value 1000 (0x03E8) bit_str2.push(b"\x07\x03\xE8", 24) res = parsley.parse_fields(bit_str2, @@ -403,19 +220,7 @@ def test1_sensor_analog(self, bit_str2): assert res["sensor_id"] == "SENSOR_BATT_CURR" assert res["value"] == 1000 - def test2_sensor_analog(self, bit_str2): - # 0x0F (SENSOR_PT_CHANNEL_3), value 65535 (0xFFFF) - bit_str2.push(b"\x0F\xFF\xFF", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('sensor_id', 8, mt.analog_sensor_id), - Numeric('value', 16)]) - - assert res["sensor_id"] == "SENSOR_PT_CHANNEL_3" - assert res["value"] == 65535 - - # --- GPS_TIMESTAMP Tests --- - def test1_gps_timestamp(self, bit_str2): + def test_gps_timestamp(self, bit_str2): # 10 hrs (0x0A), 30 mins (0x1E), 59 secs (0x3B), 99 dsecs (0x63) -> 10:30:59.99 bit_str2.push(b"\x0A\x1E\x3B\x63", 32) res = parsley.parse_fields(bit_str2, @@ -430,23 +235,7 @@ def test1_gps_timestamp(self, bit_str2): assert res["secs"] == 59 assert res["dsecs"] == 99 - def test2_gps_timestamp(self, bit_str2): - # 0 hrs (0x00), 0 mins (0x00), 0 secs (0x00), 0 dsecs (0x00) -> 00:00:00.00 - bit_str2.push(b"\x00\x00\x00\x00", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('hrs', 8), - Numeric('mins', 8), - Numeric('secs', 8), - Numeric('dsecs', 8)]) - - assert res["hrs"] == 0 - assert res["mins"] == 0 - assert res["secs"] == 0 - assert res["dsecs"] == 0 - - # --- GPS_LATITUDE Tests --- - def test1_gps_latitude(self, bit_str2): + def test_gps_latitude(self, bit_str2): # 43 degs (0x2B), 28 mins (0x1C), 1234 dmins (0x04D2), 'N' (0x4E) bit_str2.push(b"\x2B\x1C\x04\xD2\x4E", 40) res = parsley.parse_fields(bit_str2, @@ -461,23 +250,7 @@ def test1_gps_latitude(self, bit_str2): assert res["dmins"] == 1234 assert res["direction"] == 'N' - def test2_gps_latitude(self, bit_str2): - # 10 degs (0x0A), 59 mins (0x3B), 9999 dmins (0x270F), 'S' (0x53) - bit_str2.push(b"\x0A\x3B\x27\x0F\x53", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('degs', 8), - Numeric('mins', 8), - Numeric('dmins', 16), - ASCII('direction', 8)]) - - assert res["degs"] == 10 - assert res["mins"] == 59 - assert res["dmins"] == 9999 - assert res["direction"] == 'S' - - # --- GPS_LONGITUDE Tests --- - def test1_gps_longitude(self, bit_str2): + def test_gps_longitude(self, bit_str2): # 79 degs (0x4F), 59 mins (0x3B), 5678 dmins (0x162E), 'W' (0x57) bit_str2.push(b"\x4F\x3B\x16\x2E\x57", 40) res = parsley.parse_fields(bit_str2, @@ -492,23 +265,7 @@ def test1_gps_longitude(self, bit_str2): assert res["dmins"] == 5678 assert res["direction"] == 'W' - def test2_gps_longitude(self, bit_str2): - # 179 degs (0xB3), 0 mins (0x00), 1 dmins (0x0001), 'E' (0x45) - bit_str2.push(b"\xB3\x00\x00\x01\x45", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('degs', 8), - Numeric('mins', 8), - Numeric('dmins', 16), - ASCII('direction', 8)]) - - assert res["degs"] == 179 - assert res["mins"] == 0 - assert res["dmins"] == 1 - assert res["direction"] == 'E' - - # --- GPS_ALTITUDE Tests --- - def test1_gps_altitude(self, bit_str2): + def test_gps_altitude(self, bit_str2): # altitude 500 (0x01F4), daltitude 25 (0x19), 'M' (0x4D) -> 500.25 M bit_str2.push(b"\x01\xF4\x19\x4D", 32) res = parsley.parse_fields(bit_str2, @@ -521,21 +278,7 @@ def test1_gps_altitude(self, bit_str2): assert res["daltitude"] == 25 assert res["unit"] == 'M' - def test2_gps_altitude(self, bit_str2): - # altitude 10000 (0x2710), daltitude 99 (0x63), 'F' (0x46) -> 10000.99 F - bit_str2.push(b"\x27\x10\x63\x46", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('altitude', 16), - Numeric('daltitude', 8), - ASCII('unit', 8)]) - - assert res["altitude"] == 10000 - assert res["daltitude"] == 99 - assert res["unit"] == 'F' - - # --- GPS_INFO Tests --- - def test1_gps_info(self, bit_str2): + def test_gps_info(self, bit_str2): bit_str2.push(b"\x05\x02", 16) # 5 sats, quality 2 res = parsley.parse_fields(bit_str2, [TIMESTAMP_2, @@ -545,18 +288,7 @@ def test1_gps_info(self, bit_str2): assert res["num_sats"] == 5 assert res["quality"] == 2 - def test2_gps_info(self, bit_str2): - bit_str2.push(b"\x08\x03", 16) # 8 sats, quality 3 - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("num_sats", 8), - Numeric("quality", 8)]) - - assert res["num_sats"] == 8 - assert res["quality"] == 3 - - # --- STATE_EST_DATA Tests --- - def test1_state_est_data(self, bit_str2): + def test_state_est_data(self, bit_str2): # 0x0A (STATE_ID_ALT), data 123.45 (approx 0x42F6E666 float) bit_str2.push(b"\x0A\x42\xF6\xE6\x66", 40) res = parsley.parse_fields(bit_str2, @@ -567,27 +299,7 @@ def test1_state_est_data(self, bit_str2): assert res["state_id"] == "STATE_ID_ALT" assert res["data"] == approx(123.45, abs=1e-3) - def test2_state_est_data(self, bit_str2): - # 0x04 (STATE_ID_RATE_WX), data -0.01 (approx 0xBCA3D70A float) - bit_str2.push(b"\x04\xBC\xA3\xD7\x0A", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('state_id', 8, mt.state_est_id), - Floating('data', big_endian=True)]) - - assert res["state_id"] == "STATE_ID_RATE_WX" - assert res["data"] == approx(-0.02, abs=1e-3) - - # --- LEDS_ON Tests --- - def test1_leds_on(self, bit_str2): - # LEDS_ON only has MESSAGE_PRIO, BOARD_TYPE_ID, BOARD_INST_ID in its definition. - # The parsley fields list is empty after TIMESTAMP_2. - # It's an empty message payload. + def test_leds_on(self, bit_str2): res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) # Only TIMESTAMP_2 in the payload assert res['time'] == approx(3, abs=1e-3) - def test2_leds_off(self, bit_str2): - # LEDS_OFF only has MESSAGE_PRIO, BOARD_TYPE_ID, BOARD_INST_ID in its definition. - res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) - assert res['time'] == approx(3, abs=1e-3) - From a5ec22805594383c82c6932e6baedc413bdd6646 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Tue, 4 Nov 2025 16:53:57 -0500 Subject: [PATCH 5/7] Rename test_can_messages.py to test_message_definitions.py --- tests/{test_can_messages.py => test_message_definitions.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_can_messages.py => test_message_definitions.py} (100%) diff --git a/tests/test_can_messages.py b/tests/test_message_definitions.py similarity index 100% rename from tests/test_can_messages.py rename to tests/test_message_definitions.py From c16f98c8bf2d8b82631707376daf4d65667aa889 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Tue, 4 Nov 2025 19:58:06 -0500 Subject: [PATCH 6/7] Replaced hard coded definitions with MESSAGES from message_definitions.py --- tests/test_message_definitions.py | 146 ++++++------------------------ 1 file changed, 26 insertions(+), 120 deletions(-) diff --git a/tests/test_message_definitions.py b/tests/test_message_definitions.py index a94dd70..7c7a793 100644 --- a/tests/test_message_definitions.py +++ b/tests/test_message_definitions.py @@ -1,6 +1,6 @@ import pytest import parsley - + from pytest import approx from parsley.bitstring import BitString from parsley.fields import ASCII, Enum, Numeric, Floating, Bitfield @@ -24,41 +24,27 @@ def bit_str2(self): def test_general_board_status(self, bit_str2): # 0b [...(32) 1011] [...(16)] bit_str2.push(b"\x00\x00\x00\x0B" + b"\x00\x00", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Bitfield("general_board_status", 32, "E_NOMINAL", mt.general_board_status_offset), - Bitfield("board_error_bitfield", 16, "E_NOMINAL", mt.board_specific_status_offset)]) - + res = parsley.parse_fields(bit_str2, MESSAGES["GENERAL_BOARD_STATUS"][3:]) # [3:] to skip prio, type, inst assert res["general_board_status"] == 'E_5V_OVER_CURRENT|E_5V_OVER_VOLTAGE|E_12V_OVER_CURRENT' assert res["board_error_bitfield"] == 'E_NOMINAL' def test_reset_cmd(self, bit_str2): bit_str2.push(b"\x0A\x00", 16) # 0x0A (ALTIMETER), 0x00 (ANY) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("board_type_id", 8, mt.board_type_id), - Enum("board_inst_id", 8, mt.board_inst_id)]) + res = parsley.parse_fields(bit_str2, MESSAGES["RESET_CMD"][3:]) assert res["board_type_id"] == 'ALTIMETER' assert res["board_inst_id"] == 'ANY' def test_debug_raw(self, bit_str2): bit_str2.push(b"rawmsg", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - ASCII('string', 48)]) + res = parsley.parse_fields(bit_str2, MESSAGES["DEBUG_RAW"][3:]) assert res["string"] == "rawmsg" def test_config_set(self, bit_str2): # 0x01 (INJ_SENSOR), 0x02 (ROCKET), 0x0304 (ID), 0x0506 (Value) bit_str2.push(b"\x01\x02\x03\x04\x05\x06", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("board_type_id", 8, mt.board_type_id), - Enum("board_inst_id", 8, mt.board_inst_id), - Numeric("config_id", 16), - Numeric("config_value", 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["CONFIG_SET"][3:]) assert res["board_type_id"] == 'INJ_SENSOR' assert res["board_inst_id"] == 'ROCKET' @@ -68,10 +54,7 @@ def test_config_set(self, bit_str2): def test_config_status(self, bit_str2): # 0x1122 (ID), 0x3344 (Value) bit_str2.push(b"\x11\x22\x33\x44", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("config_id", 16), - Numeric("config_value", 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["CONFIG_STATUS"][3:]) assert res["config_id"] == 0x1122 assert res["config_value"] == 0x3344 @@ -79,10 +62,7 @@ def test_config_status(self, bit_str2): def test_actuator_cmd(self, bit_str2): # 0x00 (ACTUATOR_OX_INJECTOR_VALVE), 0x00 (ACT_STATE_ON) bit_str2.push(b"\x00\x00", 16) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Enum("cmd_state", 8, mt.actuator_state)]) + res = parsley.parse_fields(bit_str2, MESSAGES["ACTUATOR_CMD"][3:]) assert res["actuator"] == "ACTUATOR_OX_INJECTOR_VALVE" assert res["cmd_state"] == "ACT_STATE_ON" @@ -90,10 +70,7 @@ def test_actuator_cmd(self, bit_str2): def test_actuator_analog_cmd(self, bit_str2): # 0x11 (ACTUATOR_CANARD_ANGLE), 0x0100 (cmd_state=256) bit_str2.push(b"\x11\x01\x00", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Numeric("cmd_state", 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["ACTUATOR_ANALOG_CMD"][3:]) assert res["actuator"] == "ACTUATOR_CANARD_ANGLE" assert res["cmd_state"] == 256 @@ -101,36 +78,16 @@ def test_actuator_analog_cmd(self, bit_str2): def test_actuator_status(self, bit_str2): # 0x01 (ACTUATOR_FUEL_INJECTOR_VALVE), 0x00 (curr_state=ON), 0x01 (cmd_state=OFF) bit_str2.push(b"\x01\x00\x01", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Enum("curr_state", 8, mt.actuator_state), - Enum("cmd_state", 8, mt.actuator_state)]) + res = parsley.parse_fields(bit_str2, MESSAGES["ACTUATOR_STATUS"][3:]) assert res["actuator"] == "ACTUATOR_FUEL_INJECTOR_VALVE" assert res["curr_state"] == "ACT_STATE_ON" assert res["cmd_state"] == "ACT_STATE_OFF" - def test2_actuator_status(self, bit_str2): - # 0x08 (ACTUATOR_TELEMETRY), 0x03 (curr_state=ILLEGAL), 0x02 (cmd_state=UNK) - bit_str2.push(b"\x08\x03\x02", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("actuator", 8, mt.actuator_id), - Enum("curr_state", 8, mt.actuator_state), - Enum("cmd_state", 8, mt.actuator_state)]) - - assert res["actuator"] == "ACTUATOR_TELEMETRY" - assert res["curr_state"] == "ACT_STATE_ILLEGAL" - assert res["cmd_state"] == "ACT_STATE_UNK" - def test_alt_arm_cmd(self, bit_str2): # 0x02 (ALTIMETER_ROCKET_SRAD), 0x01 (ALT_ARM_STATE_ARMED) bit_str2.push(b"\x02\x01", 16) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("alt_id", 8, mt.altimeter_id), - Enum("alt_arm_state", 8, mt.alt_arm_state)]) + res = parsley.parse_fields(bit_str2, MESSAGES["ALT_ARM_CMD"][3:]) assert res["alt_id"] == "ALTIMETER_ROCKET_SRAD" assert res["alt_arm_state"] == "ALT_ARM_STATE_ARMED" @@ -138,12 +95,7 @@ def test_alt_arm_cmd(self, bit_str2): def test_alt_arm_status(self, bit_str2): # 0x00 (ALTIMETER_ROCKET_RAVEN), 0x01 (ARMED), 0x0500 (drogue=1280), 0x0A00 (main=2560) bit_str2.push(b"\x00\x01\x05\x00\x0A\x00", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum("alt_id", 8, mt.altimeter_id), - Enum("alt_arm_state", 8, mt.alt_arm_state), - Numeric("drogue_v", 16), - Numeric("main_v", 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["ALT_ARM_STATUS"][3:]) assert res["alt_id"] == "ALTIMETER_ROCKET_RAVEN" assert res["alt_arm_state"] == "ALT_ARM_STATE_ARMED" @@ -153,10 +105,7 @@ def test_alt_arm_status(self, bit_str2): def test_sensor_temp(self, bit_str2): # ID 1 (0x01), Temperature -128.0°C. Raw value for -128.0 * 1024 = -131072 = 0xFFFE0000 (signed 32-bit) bit_str2.push(b"\x01\xFF\xFE\x00\x00", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("temp_sensor_id", 8), - Numeric("temperature", 32, scale=1/2**10, unit='°C', signed=True)]) + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_TEMP"][3:]) assert res["temp_sensor_id"] == 1 assert res["temperature"] == -128.0 @@ -164,10 +113,7 @@ def test_sensor_temp(self, bit_str2): def test_sensor_altitude(self, bit_str2): # Altitude 3000m (0x00000BB8), APOGEE_NOT_REACHED (0x01) bit_str2.push(b"\x00\x00\x0B\xB8\x01", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("altitude", 32, signed=True), - Enum("apogee_state", 8, mt.apogee_state)]) + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_ALTITUDE"][3:]) assert res["altitude"] == 3000 assert res["apogee_state"] == "APOGEE_NOT_REACHED" @@ -175,11 +121,7 @@ def test_sensor_altitude(self, bit_str2): def test_sensor_imu_x(self, bit_str2): # 0x01 (IMU_PROC_MTI630), linear_accel 1024 (0x0400), angular_velocity 512 (0x0200) bit_str2.push(b"\x01\x04\x00\x02\x00", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('linear_accel', 16), - Numeric('angular_velocity', 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_IMU_X"][3:]) assert res["imu_id"] == "IMU_PROC_MTI630" assert res["linear_accel"] == 1024 @@ -188,22 +130,15 @@ def test_sensor_imu_x(self, bit_str2): def test_sensor_mag_x(self, bit_str2): # 0x00 (IMU_PROC_ALTIMU10), mag 1000 (0x03E8) bit_str2.push(b"\x00\x03\xE8", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('mag', 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_MAG_X"][3:]) assert res["imu_id"] == "IMU_PROC_ALTIMU10" assert res["mag"] == 1000 def test_sensor_baro(self, bit_str2): # 0x00 (IMU_PROC_ALTIMU10), pressure 101325 (0x018B6D), temp 298 (0x012A) - bit_str2.push(b"\x00\x01\x8B\x6D\x01\x2A", 48) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('imu_id', 8, mt.imu_id), - Numeric('pressure', 24), - Numeric('temp', 16)]) + bit_str2.push(b"\x00\x01\x8B\x6D\x01\x2A", 48) # imu_id, pressure, temp + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_BARO"][3:]) assert res["imu_id"] == "IMU_PROC_ALTIMU10" assert res["pressure"] == 101229 @@ -212,10 +147,7 @@ def test_sensor_baro(self, bit_str2): def test_sensor_analog(self, bit_str2): # 0x07 (SENSOR_BATT_CURR), value 1000 (0x03E8) bit_str2.push(b"\x07\x03\xE8", 24) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('sensor_id', 8, mt.analog_sensor_id), - Numeric('value', 16)]) + res = parsley.parse_fields(bit_str2, MESSAGES["SENSOR_ANALOG"][3:]) assert res["sensor_id"] == "SENSOR_BATT_CURR" assert res["value"] == 1000 @@ -223,12 +155,7 @@ def test_sensor_analog(self, bit_str2): def test_gps_timestamp(self, bit_str2): # 10 hrs (0x0A), 30 mins (0x1E), 59 secs (0x3B), 99 dsecs (0x63) -> 10:30:59.99 bit_str2.push(b"\x0A\x1E\x3B\x63", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('hrs', 8), - Numeric('mins', 8), - Numeric('secs', 8), - Numeric('dsecs', 8)]) + res = parsley.parse_fields(bit_str2, MESSAGES["GPS_TIMESTAMP"][3:]) assert res["hrs"] == 10 assert res["mins"] == 30 @@ -238,12 +165,7 @@ def test_gps_timestamp(self, bit_str2): def test_gps_latitude(self, bit_str2): # 43 degs (0x2B), 28 mins (0x1C), 1234 dmins (0x04D2), 'N' (0x4E) bit_str2.push(b"\x2B\x1C\x04\xD2\x4E", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('degs', 8), - Numeric('mins', 8), - Numeric('dmins', 16), - ASCII('direction', 8)]) + res = parsley.parse_fields(bit_str2, MESSAGES["GPS_LATITUDE"][3:]) assert res["degs"] == 43 assert res["mins"] == 28 @@ -253,12 +175,7 @@ def test_gps_latitude(self, bit_str2): def test_gps_longitude(self, bit_str2): # 79 degs (0x4F), 59 mins (0x3B), 5678 dmins (0x162E), 'W' (0x57) bit_str2.push(b"\x4F\x3B\x16\x2E\x57", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('degs', 8), - Numeric('mins', 8), - Numeric('dmins', 16), - ASCII('direction', 8)]) + res = parsley.parse_fields(bit_str2, MESSAGES["GPS_LONGITUDE"][3:]) assert res["degs"] == 79 assert res["mins"] == 59 @@ -268,11 +185,7 @@ def test_gps_longitude(self, bit_str2): def test_gps_altitude(self, bit_str2): # altitude 500 (0x01F4), daltitude 25 (0x19), 'M' (0x4D) -> 500.25 M bit_str2.push(b"\x01\xF4\x19\x4D", 32) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric('altitude', 16), - Numeric('daltitude', 8), - ASCII('unit', 8)]) + res = parsley.parse_fields(bit_str2, MESSAGES["GPS_ALTITUDE"][3:]) assert res["altitude"] == 500 assert res["daltitude"] == 25 @@ -280,10 +193,7 @@ def test_gps_altitude(self, bit_str2): def test_gps_info(self, bit_str2): bit_str2.push(b"\x05\x02", 16) # 5 sats, quality 2 - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Numeric("num_sats", 8), - Numeric("quality", 8)]) + res = parsley.parse_fields(bit_str2, MESSAGES["GPS_INFO"][3:]) assert res["num_sats"] == 5 assert res["quality"] == 2 @@ -291,15 +201,11 @@ def test_gps_info(self, bit_str2): def test_state_est_data(self, bit_str2): # 0x0A (STATE_ID_ALT), data 123.45 (approx 0x42F6E666 float) bit_str2.push(b"\x0A\x42\xF6\xE6\x66", 40) - res = parsley.parse_fields(bit_str2, - [TIMESTAMP_2, - Enum('state_id', 8, mt.state_est_id), - Floating('data', big_endian=True)]) + res = parsley.parse_fields(bit_str2, MESSAGES["STATE_EST_DATA"][3:]) assert res["state_id"] == "STATE_ID_ALT" assert res["data"] == approx(123.45, abs=1e-3) def test_leds_on(self, bit_str2): - res = parsley.parse_fields(bit_str2, [TIMESTAMP_2]) # Only TIMESTAMP_2 in the payload - assert res['time'] == approx(3, abs=1e-3) - + res = parsley.parse_fields(bit_str2, []) + assert res == {} From 2603c961913814cf222a20aefb89945e3a2846a9 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Tue, 4 Nov 2025 20:30:04 -0500 Subject: [PATCH 7/7] Added coverage to dev dependencies --- pyproject.toml | 1 + uv.lock | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3dd024f..10338e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ "flake8", "pycodestyle", "pytest", + "pytest-cov>=7.0.0", ] [project.urls] diff --git a/uv.lock b/uv.lock index f6a1cc1..9938486 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,98 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", size = 215912, upload-time = "2025-10-15T15:12:40.665Z" }, + { url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", size = 216310, upload-time = "2025-10-15T15:12:42.461Z" }, + { url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", size = 246706, upload-time = "2025-10-15T15:12:44.001Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", size = 248634, upload-time = "2025-10-15T15:12:45.768Z" }, + { url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", size = 250741, upload-time = "2025-10-15T15:12:47.222Z" }, + { url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", size = 246837, upload-time = "2025-10-15T15:12:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", size = 248429, upload-time = "2025-10-15T15:12:50.73Z" }, + { url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", size = 246490, upload-time = "2025-10-15T15:12:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", size = 246208, upload-time = "2025-10-15T15:12:54.586Z" }, + { url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", size = 247126, upload-time = "2025-10-15T15:12:56.485Z" }, + { url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c", size = 218314, upload-time = "2025-10-15T15:12:58.365Z" }, + { url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", size = 219203, upload-time = "2025-10-15T15:12:59.902Z" }, + { url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", size = 217879, upload-time = "2025-10-15T15:13:01.35Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, + { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, + { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, + { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, + { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "crc8" version = "0.2.1" @@ -74,6 +166,7 @@ dev = [ { name = "flake8" }, { name = "pycodestyle" }, { name = "pytest" }, + { name = "pytest-cov" }, ] [package.metadata] @@ -84,6 +177,7 @@ dev = [ { name = "flake8" }, { name = "pycodestyle" }, { name = "pytest" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, ] [[package]] @@ -137,3 +231,66 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +]