|
| 1 | +from unittest.mock import Mock |
| 2 | +import struct |
| 3 | + |
| 4 | +import numpy as np |
| 5 | + |
| 6 | +from sciopy.ISX_3 import ISXMeasurement, ISX_3 |
| 7 | +from sciopy.sciopy_dataclasses import EisMeasurementSetup |
| 8 | + |
| 9 | + |
| 10 | +def command_device(): |
| 11 | + device = ISX_3() |
| 12 | + device.send_command = Mock(return_value=[]) |
| 13 | + return device |
| 14 | + |
| 15 | + |
| 16 | +def test_frame_builder_uses_sciospec_framing(): |
| 17 | + assert ISX_3.build_frame(0xB0, [0x02, 0x01, 0x00, 0x01]) == bytearray( |
| 18 | + [0xB0, 0x04, 0x02, 0x01, 0x00, 0x01, 0xB0] |
| 19 | + ) |
| 20 | + |
| 21 | + |
| 22 | +def test_stream_parser_handles_partial_and_multiple_frames(): |
| 23 | + device = ISX_3() |
| 24 | + first = bytes([0x18, 0x01, 0x83, 0x18]) |
| 25 | + second = bytes([0xBA, 0x04, 0x00, 0x00, 0x03, 0xE8, 0xBA]) |
| 26 | + |
| 27 | + assert device.parse_received_bytes(first[:2]) == [] |
| 28 | + assert device.parse_received_bytes(first[2:] + second) == [ |
| 29 | + list(first), |
| 30 | + list(second), |
| 31 | + ] |
| 32 | + |
| 33 | + |
| 34 | +def test_set_and_get_frontend_settings_follow_manual(): |
| 35 | + device = command_device() |
| 36 | + |
| 37 | + device.SetFE_Settings("4-point", "bnc", "auto", "1v") |
| 38 | + device.GetFE_Settings() |
| 39 | + |
| 40 | + assert device.send_command.call_args_list == [ |
| 41 | + ((0xB0, [0x02, 0x01, 0x00, 0x01]),), |
| 42 | + ((0xB1,),), |
| 43 | + ] |
| 44 | + assert device.decode_frame([0xB1, 0x04, 0x02, 0x01, 0x04, 0x02, 0xB1]) == { |
| 45 | + "measurement_mode": 0x02, |
| 46 | + "measurement_channel": 0x01, |
| 47 | + "current_range": 0x04, |
| 48 | + "voltage_range": 0x02, |
| 49 | + } |
| 50 | + |
| 51 | + |
| 52 | +def test_options_update_measurement_layout_state(): |
| 53 | + device = command_device() |
| 54 | + |
| 55 | + device.SetOptions("timestamp_us", True) |
| 56 | + device.SetOptions("current_range", True) |
| 57 | + |
| 58 | + assert device.timestamp_mode == "us" |
| 59 | + assert device.current_range_output is True |
| 60 | + assert device.send_command.call_args_list == [ |
| 61 | + ((0x97, [0x02, 0x01]),), |
| 62 | + ((0x97, [0x04, 0x01]),), |
| 63 | + ] |
| 64 | + |
| 65 | + |
| 66 | +def test_add_frequency_point_encodes_float_and_extended_options(): |
| 67 | + device = command_device() |
| 68 | + |
| 69 | + device.AddFrequencyPoint( |
| 70 | + 32_000, |
| 71 | + 1.0, |
| 72 | + 0.25, |
| 73 | + point_delay=1000, |
| 74 | + phase_sync=True, |
| 75 | + excitation_type="voltage", |
| 76 | + ) |
| 77 | + |
| 78 | + payload = device.send_command.call_args.args[1] |
| 79 | + assert device.send_command.call_args.args[0] == 0xB6 |
| 80 | + assert payload[0] == 0x02 |
| 81 | + assert struct.unpack(">fff", payload[1:13]) == (32_000.0, 1.0, 0.25) |
| 82 | + assert payload[13:] == bytearray( |
| 83 | + [0x01, 0x00, 0x00, 0x03, 0xE8, 0x02, 0, 0, 0, 1, 0x03, 0, 0, 0, 1] |
| 84 | + ) |
| 85 | + |
| 86 | + |
| 87 | +def test_add_frequency_list_encodes_documented_setup(): |
| 88 | + device = command_device() |
| 89 | + |
| 90 | + device.AddFrequencyList(1_000, 10_000, 10, "log", 1.0, 0.25) |
| 91 | + |
| 92 | + tag, payload = device.send_command.call_args.args |
| 93 | + assert tag == 0xB6 |
| 94 | + assert payload[0] == 0x03 |
| 95 | + assert struct.unpack(">fff", payload[1:13]) == (1_000.0, 10_000.0, 10.0) |
| 96 | + assert payload[13] == 0x01 |
| 97 | + assert struct.unpack(">ff", payload[14:22]) == (1.0, 0.25) |
| 98 | + |
| 99 | + |
| 100 | +def test_eis_dataclass_configures_setup(): |
| 101 | + device = command_device() |
| 102 | + setup = EisMeasurementSetup( |
| 103 | + start=100, |
| 104 | + stop=100_000, |
| 105 | + step=20, |
| 106 | + stepmode="log", |
| 107 | + avg=1, |
| 108 | + amplitude=0.1, |
| 109 | + precision=1, |
| 110 | + measurement_time=0, |
| 111 | + ) |
| 112 | + |
| 113 | + device.SetMeasurementSetup(setup) |
| 114 | + |
| 115 | + assert device.setup is setup |
| 116 | + assert device.send_command.call_args_list[0] == ((0xB6, [0x01]),) |
| 117 | + assert device.send_command.call_args_list[1].args[0] == 0xB6 |
| 118 | + |
| 119 | + |
| 120 | +def test_measurement_without_optional_fields_is_decoded(): |
| 121 | + device = ISX_3() |
| 122 | + payload = [0x00, 0x02, *struct.pack(">ff", 123.5, -4.25)] |
| 123 | + |
| 124 | + result = device.decode_frame([0xB8, len(payload), *payload, 0xB8]) |
| 125 | + |
| 126 | + assert result == ISXMeasurement(frequency_id=2, impedance=123.5 - 4.25j) |
| 127 | + |
| 128 | + |
| 129 | +def test_measurement_with_microsecond_timestamp_and_range_is_decoded(): |
| 130 | + device = ISX_3() |
| 131 | + device.timestamp_mode = "us" |
| 132 | + device.current_range_output = True |
| 133 | + payload = [0x00, 0x03, 0, 0, 0, 1, 2, 0x04, *struct.pack(">ff", 10, 2)] |
| 134 | + |
| 135 | + result = device.decode_frame([0xB8, len(payload), *payload, 0xB8]) |
| 136 | + |
| 137 | + assert result == ISXMeasurement( |
| 138 | + frequency_id=3, |
| 139 | + impedance=10 + 2j, |
| 140 | + timestamp=258, |
| 141 | + timestamp_unit="us", |
| 142 | + current_range=0x04, |
| 143 | + ) |
| 144 | + |
| 145 | + |
| 146 | +def test_setup_response_decoders(): |
| 147 | + device = ISX_3() |
| 148 | + frequencies = struct.pack(">ff", 100.0, 1_000.0) |
| 149 | + |
| 150 | + assert device.decode_frame([0xB7, 0x03, 0x01, 0x00, 0x10, 0xB7]) == { |
| 151 | + "frequency_count": 16 |
| 152 | + } |
| 153 | + result = device.decode_frame([0xB7, 0x09, 0x04, *frequencies, 0xB7]) |
| 154 | + assert np.allclose(result["frequencies_hz"], [100, 1_000]) |
| 155 | + |
| 156 | + |
| 157 | +def test_frequency_point_response_decodes_extended_options(): |
| 158 | + device = ISX_3() |
| 159 | + data = [ |
| 160 | + 0x02, |
| 161 | + *struct.pack(">fff", 32_000, 1.0, 0.25), |
| 162 | + 0x01, |
| 163 | + 0, |
| 164 | + 0, |
| 165 | + 3, |
| 166 | + 0xE8, |
| 167 | + 0x03, |
| 168 | + 0, |
| 169 | + 0, |
| 170 | + 0, |
| 171 | + 1, |
| 172 | + ] |
| 173 | + |
| 174 | + result = device.decode_frame([0xB7, len(data), *data, 0xB7]) |
| 175 | + |
| 176 | + assert result == { |
| 177 | + "frequency_hz": 32_000.0, |
| 178 | + "precision": 1.0, |
| 179 | + "amplitude": 0.25, |
| 180 | + "point_delay_us": 1_000, |
| 181 | + "excitation_type": 1, |
| 182 | + } |
| 183 | + |
| 184 | + |
| 185 | +def test_compensation_record_encodes_three_complex_values(): |
| 186 | + device = command_device() |
| 187 | + |
| 188 | + device.SetCompensationData(2, 3, 1 + 2j, 3 + 4j, 5 + 6j) |
| 189 | + |
| 190 | + tag, payload = device.send_command.call_args.args |
| 191 | + assert tag == 0xB6 |
| 192 | + assert payload[:5] == bytearray([0x17, 0x02, 0x02, 0x00, 0x03]) |
| 193 | + assert struct.unpack(">ffffff", payload[5:]) == (1, 2, 3, 4, 5, 6) |
| 194 | + |
| 195 | + |
| 196 | +def test_sync_ethernet_and_watchdog_commands(): |
| 197 | + device = command_device() |
| 198 | + |
| 199 | + device.SetSyncTime(1_000) |
| 200 | + device.SetEthernetConfiguration(ip_address="192.168.1.25") |
| 201 | + device.SetEthernetConfiguration(dhcp=True) |
| 202 | + device.GetEthernetConfiguration("mac_address") |
| 203 | + device.SetTCPWatchdog(60) |
| 204 | + |
| 205 | + assert device.send_command.call_args_list == [ |
| 206 | + ((0xB9, bytes([0, 0, 3, 0xE8])),), |
| 207 | + ((0xBD, [0x01, 192, 168, 1, 25]),), |
| 208 | + ((0xBD, [0x03, 1]),), |
| 209 | + ((0xBE, [0x02]),), |
| 210 | + ((0xCF, [0x00, 0, 0, 0, 60]),), |
| 211 | + ] |
| 212 | + |
| 213 | + |
| 214 | +def test_firmware_and_device_information_are_decoded(): |
| 215 | + device = ISX_3() |
| 216 | + |
| 217 | + arm = device.decode_frame([0xD0, 0x06, 1, 2, 0, 3, 0, 4, 0xD0]) |
| 218 | + identity = device.decode_frame([0xD1, 0x07, 1, 0, 7, 0x12, 0x34, 14, 9, 0xD1]) |
| 219 | + |
| 220 | + assert arm == {"developer_information": "0102", "revision": 3, "build": 4} |
| 221 | + assert identity == { |
| 222 | + "information_version": 1, |
| 223 | + "device_identifier": 7, |
| 224 | + "serial_number": 0x1234, |
| 225 | + "delivery_year": 2024, |
| 226 | + "delivery_month": 9, |
| 227 | + "developer_information": "", |
| 228 | + } |
| 229 | + |
| 230 | + |
| 231 | +def test_invalid_protocol_values_are_rejected(): |
| 232 | + device = command_device() |
| 233 | + |
| 234 | + for action in ( |
| 235 | + lambda: device.SetFE_Settings("invalid", "bnc", "auto"), |
| 236 | + lambda: device.SetSyncTime(180_000_001), |
| 237 | + lambda: device.SetTCPWatchdog(0), |
| 238 | + lambda: device.SetDCBiasValue(1.1), |
| 239 | + ): |
| 240 | + try: |
| 241 | + action() |
| 242 | + except ValueError: |
| 243 | + pass |
| 244 | + else: |
| 245 | + raise AssertionError("invalid protocol value was accepted") |
0 commit comments