def test_McCommunicationProcess__sends_handshake_every_5_seconds__and_includes_correct_timestamp__and_processes_response( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker, ): mc_process = four_board_mc_comm_process["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] spied_write = mocker.spy(simulator, "write") expected_durs = [ 0, MICRO_TO_BASE_CONVERSION * SERIAL_COMM_HANDSHAKE_PERIOD_SECONDS, ] mocker.patch.object(mc_comm, "get_serial_comm_timestamp", autospec=True, side_effect=expected_durs) mocker.patch.object( mc_comm, "_get_secs_since_last_handshake", autospec=True, side_effect=[ 0, SERIAL_COMM_HANDSHAKE_PERIOD_SECONDS, SERIAL_COMM_HANDSHAKE_PERIOD_SECONDS - 1, 1, ], ) set_connection_and_register_simulator(four_board_mc_comm_process, mantarray_mc_simulator_no_beacon) # send handshake invoke_process_run_and_check_errors(mc_process) expected_handshake_1 = create_data_packet( expected_durs[0], SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_HANDSHAKE_PACKET_TYPE, bytes(0), ) assert spied_write.call_args[0][0] == expected_handshake_1 # process handshake on simulator invoke_process_run_and_check_errors(simulator) # process handshake response invoke_process_run_and_check_errors(mc_process) # assert handshake response was read assert simulator.in_waiting == 0 # repeat, 5 seconds since previous beacon invoke_process_run_and_check_errors(mc_process) expected_handshake_2 = create_data_packet( expected_durs[1], SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_HANDSHAKE_PACKET_TYPE, bytes(0), ) assert spied_write.call_args[0][0] == expected_handshake_2 invoke_process_run_and_check_errors(simulator) invoke_process_run_and_check_errors(mc_process) assert simulator.in_waiting == 0
def test_McCommunicationProcess__raises_error_if_unrecognized_packet_type_sent_from_instrument( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker, patch_print): mc_process = four_board_mc_comm_process["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] dummy_timestamp = 0 test_packet_type = 254 test_packet = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, test_packet_type, DEFAULT_SIMULATOR_STATUS_CODE, ) put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "add_read_bytes", "read_bytes": test_packet, }, testing_queue, ) invoke_process_run_and_check_errors(simulator) board_idx = 0 mc_process.set_board_connection(board_idx, simulator) with pytest.raises(UnrecognizedSerialCommPacketTypeError) as exc_info: invoke_process_run_and_check_errors(mc_process) assert str(SERIAL_COMM_MAIN_MODULE_ID) in str(exc_info.value) assert str(test_packet_type) in str(exc_info.value)
def test_McCommunicationProcess__includes_correct_timestamp_in_packets_sent_to_instrument( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker): mc_process = four_board_mc_comm_process["mc_process"] board_queues = four_board_mc_comm_process["board_queues"] input_queue = board_queues[0][0] expected_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) mocker.patch.object( mc_comm, "get_serial_comm_timestamp", autospec=True, return_value=expected_timestamp, ) simulator = mantarray_mc_simulator_no_beacon["simulator"] spied_write = mocker.spy(simulator, "write") set_connection_and_register_simulator(four_board_mc_comm_process, mantarray_mc_simulator_no_beacon) test_command = { "communication_type": "metadata_comm", "command": "get_metadata", } put_object_into_queue_and_raise_error_if_eventually_still_empty( copy.deepcopy(test_command), input_queue) # run mc_process one iteration to send the command invoke_process_run_and_check_errors(mc_process) expected_data_packet = create_data_packet( expected_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_GET_METADATA_PACKET_TYPE, ) spied_write.assert_called_with(expected_data_packet)
def test_McCommunicationProcess_register_magic_word__registers_magic_word_in_serial_comm_from_board__when_first_packet_is_not_truncated__and_handles_reads_correctly_afterward( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker): mc_process = four_board_mc_comm_process["mc_process"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] simulator = mantarray_mc_simulator_no_beacon["simulator"] board_idx = 0 timestamp = 0 mc_process.set_board_connection(board_idx, simulator) assert mc_process.is_registered_with_serial_comm(board_idx) is False test_bytes = create_data_packet( timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) test_item = { "command": "add_read_bytes", "read_bytes": [test_bytes, test_bytes] } put_object_into_queue_and_raise_error_if_eventually_still_empty( test_item, testing_queue) invoke_process_run_and_check_errors(simulator) invoke_process_run_and_check_errors(mc_process) assert mc_process.is_registered_with_serial_comm(board_idx) is True # make sure no errors reading next packet invoke_process_run_and_check_errors(mc_process) # make sure no errors when no more bytes available invoke_process_run_and_check_errors(mc_process)
def test_McCommunicationProcess_register_magic_word__registers_magic_word_in_serial_comm_from_board__when_first_packet_is_truncated_to_more_than_8_bytes( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker): mc_process = four_board_mc_comm_process["mc_process"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] simulator = mantarray_mc_simulator_no_beacon["simulator"] board_idx = 0 mc_process.set_board_connection(board_idx, simulator) assert mc_process.is_registered_with_serial_comm(board_idx) is False test_bytes = SERIAL_COMM_MAGIC_WORD_BYTES[3:] + bytes(8) dummy_timestamp = 0 test_bytes += create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) test_item = {"command": "add_read_bytes", "read_bytes": test_bytes} put_object_into_queue_and_raise_error_if_eventually_still_empty( test_item, testing_queue) invoke_process_run_and_check_errors(simulator) invoke_process_run_and_check_errors(mc_process) assert mc_process.is_registered_with_serial_comm(board_idx) is True # make sure no errors in next iteration invoke_process_run_and_check_errors(mc_process)
def test_MantarrayMcSimulator__processes_start_stimulation_command__before_protocols_have_been_set( mantarray_mc_simulator_no_beacon, ): simulator = mantarray_mc_simulator_no_beacon["simulator"] set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) expected_stim_running_statuses = { convert_module_id_to_well_name(module_id): False for module_id in range(1, 25) } # send start stim command expected_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) start_stimulation_command = create_data_packet( expected_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_START_STIM_PACKET_TYPE, ) simulator.write(start_stimulation_command) invoke_process_run_and_check_errors(simulator) # assert that stimulation was not started on any wells assert simulator.get_stim_running_statuses() == expected_stim_running_statuses # assert command response is correct expected_size = get_full_packet_size_from_packet_body_size(SERIAL_COMM_TIMESTAMP_LENGTH_BYTES + 1) stim_command_response = simulator.read(size=expected_size) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=convert_to_timestamp_bytes(expected_pc_timestamp) + bytes([SERIAL_COMM_COMMAND_FAILURE_BYTE]), )
def test_handle_data_packets__parses_single_stim_data_packet_with_a_single_status_correctly( ): base_global_time = randint(0, 100) test_time_index = random_time_index() test_well_idx = randint(0, 23) test_subprotocol_idx = randint(0, 5) stim_packet_body = ( bytes([1]) # num status updates in packet + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_idx]]) + bytes([StimStatuses.ACTIVE]) + test_time_index.to_bytes(8, byteorder="little") + bytes([test_subprotocol_idx])) test_data_packet = create_data_packet( random_timestamp(), SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STIM_STATUS_PACKET_TYPE, stim_packet_body, ) parsed_data_dict = handle_data_packets(bytearray(test_data_packet), [], base_global_time) actual_stim_data = parsed_data_dict["stim_data"] assert list(actual_stim_data.keys()) == [test_well_idx] assert actual_stim_data[test_well_idx].dtype == np.int64 np.testing.assert_array_equal(actual_stim_data[test_well_idx], [[test_time_index], [test_subprotocol_idx]]) # make sure no magnetometer data was returned assert not any(parsed_data_dict["magnetometer_data"].values())
def test_McCommunicationProcess__raises_error_when_receiving_untracked_command_response_from_instrument( four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon, mocker, patch_print, ): mc_process = four_board_mc_comm_process_no_handshake["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] test_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) test_timestamp_bytes = bytes( 8) # 8 arbitrary bytes in place of timestamp of command sent from PC test_command_response = create_data_packet( test_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, test_timestamp_bytes, ) put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "add_read_bytes", "read_bytes": test_command_response }, testing_queue, ) invoke_process_run_and_check_errors(simulator) mc_process.set_board_connection(0, simulator) with pytest.raises(SerialCommUntrackedCommandResponseError) as exc_info: invoke_process_run_and_check_errors(mc_process) assert str(SERIAL_COMM_MAIN_MODULE_ID) in str(exc_info.value) assert str(SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE) in str(exc_info.value) assert str(test_timestamp_bytes) in str(exc_info.value)
def test_McCommunicationProcess__raises_error_if_unrecognized_module_id_sent_from_instrument( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker, patch_print): mc_process = four_board_mc_comm_process["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] dummy_timestamp = 0 dummy_packet_type = 1 test_module_id = 254 test_packet = create_data_packet( dummy_timestamp, test_module_id, dummy_packet_type, DEFAULT_SIMULATOR_STATUS_CODE, ) put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "add_read_bytes", "read_bytes": test_packet, }, testing_queue, ) invoke_process_run_and_check_errors(simulator) board_idx = 0 mc_process.set_board_connection(board_idx, simulator) with pytest.raises(UnrecognizedSerialCommModuleIdError, match=str(test_module_id)): invoke_process_run_and_check_errors(mc_process)
def test_MantarrayMcSimulator__processes_set_stimulation_protocol_command__when_stimulation_not_running_on_any_wells( mantarray_mc_simulator_no_beacon, ): simulator = mantarray_mc_simulator_no_beacon["simulator"] set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) test_protocol_ids = ("A", "B", "E", None) stim_info_dict = { "protocols": [ { "protocol_id": protocol_id, "stimulation_type": choice(["C", "V"]), "run_until_stopped": choice([True, False]), "subprotocols": [ choice([get_random_subprotocol(), get_null_subprotocol(600)]) for _ in range(randint(1, 3)) ], } for protocol_id in test_protocol_ids[:-1] ], "protocol_assignments": { GENERIC_24_WELL_DEFINITION.get_well_name_from_well_index(well_idx): choice(test_protocol_ids) for well_idx in range(24) }, } expected_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) set_protocols_command = create_data_packet( expected_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_SET_STIM_PROTOCOL_PACKET_TYPE, convert_stim_dict_to_bytes(stim_info_dict), ) simulator.write(set_protocols_command) invoke_process_run_and_check_errors(simulator) # assert that stim info was stored actual = simulator.get_stim_info() for protocol_idx in range(len(test_protocol_ids) - 1): del stim_info_dict["protocols"][protocol_idx][ "protocol_id" ] # the actual protocol ID letter is not included assert actual["protocols"][protocol_idx] == stim_info_dict["protocols"][protocol_idx], protocol_idx assert actual["protocol_assignments"] == { # indices of the protocol are used instead well_name: (None if protocol_id is None else test_protocol_ids.index(protocol_id)) for well_name, protocol_id in stim_info_dict["protocol_assignments"].items() } # assert command response is correct stim_command_response = simulator.read( size=get_full_packet_size_from_packet_body_size(SERIAL_COMM_TIMESTAMP_LENGTH_BYTES + 1) ) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=convert_to_timestamp_bytes(expected_pc_timestamp) + bytes([SERIAL_COMM_COMMAND_SUCCESS_BYTE]), )
def test_MantarrayMcSimulator__processes_start_stimulation_command__after_protocols_have_been_set( mantarray_mc_simulator_no_beacon, mocker ): simulator = mantarray_mc_simulator_no_beacon["simulator"] set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) spied_global_timer = mocker.spy(simulator, "_get_global_timer") # mock so no protocol status packets are sent mocker.patch.object(mc_simulator, "_get_us_since_subprotocol_start", autospec=True, return_value=0) # set single arbitrary protocol applied to wells randomly stim_info = simulator.get_stim_info() stim_info["protocols"] = [ { "protocol_id": "A", "stimulation_type": "C", "run_until_stopped": True, "subprotocols": [get_random_subprotocol()], } ] stim_info["protocol_assignments"] = { GENERIC_24_WELL_DEFINITION.get_well_name_from_well_index(well_idx): choice(["A", None]) if well_idx else "A" for well_idx in range(24) } expected_stim_running_statuses = { well_name: bool(protocol_id) for well_name, protocol_id in stim_info["protocol_assignments"].items() } for response_byte_value in ( SERIAL_COMM_COMMAND_SUCCESS_BYTE, SERIAL_COMM_COMMAND_FAILURE_BYTE, ): # send start stim command expected_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) start_stimulation_command = create_data_packet( expected_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_START_STIM_PACKET_TYPE, ) simulator.write(start_stimulation_command) invoke_process_run_and_check_errors(simulator) # assert that stimulation was started on wells that were assigned a protocol assert simulator.get_stim_running_statuses() == expected_stim_running_statuses # assert command response is correct additional_bytes = convert_to_timestamp_bytes(expected_pc_timestamp) + bytes([response_byte_value]) if not response_byte_value: additional_bytes += spied_global_timer.spy_return.to_bytes(8, byteorder="little") expected_size = get_full_packet_size_from_packet_body_size(len(additional_bytes)) stim_command_response = simulator.read(size=expected_size) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=additional_bytes, )
def test_McCommunicationProcess__raises_error_if_checksum_in_data_packet_sent_from_mantarray_is_invalid( is_command_awaiting, four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon, mocker, patch_print, ): mc_process = four_board_mc_comm_process_no_handshake["mc_process"] input_queue = four_board_mc_comm_process_no_handshake["board_queues"][0][0] simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] set_connection_and_register_simulator( four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon) if is_command_awaiting: test_prev_command = { "communication_type": "metadata_comm", "command": "get_metadata" } put_object_into_queue_and_raise_error_if_eventually_still_empty( test_prev_command, input_queue) invoke_process_run_and_check_errors(mc_process) else: test_prev_command = None # add packet with bad checksum to be sent from simulator dummy_timestamp = 0 test_bytes = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) # set checksum bytes to an arbitrary incorrect value bad_checksum = 1234 bad_checksum_bytes = bad_checksum.to_bytes( SERIAL_COMM_CHECKSUM_LENGTH_BYTES, byteorder="little") test_bytes = test_bytes[:-SERIAL_COMM_CHECKSUM_LENGTH_BYTES] + bad_checksum_bytes put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "add_read_bytes", "read_bytes": test_bytes, }, testing_queue, ) invoke_process_run_and_check_errors(simulator) with pytest.raises( SerialCommIncorrectChecksumFromInstrumentError) as exc_info: invoke_process_run_and_check_errors(mc_process) expected_checksum = int.from_bytes( test_bytes[-SERIAL_COMM_CHECKSUM_LENGTH_BYTES:], byteorder="little") assert str(bad_checksum) in exc_info.value.args[0] assert str(expected_checksum) in exc_info.value.args[0] assert str(test_bytes) in exc_info.value.args[0] assert f"Previous Command: {test_prev_command}" in exc_info.value.args[0]
def test_McCommunicationProcess_register_magic_word__registers_with_magic_word_in_serial_comm_from_board__when_first_packet_is_truncated_to_less_than_8_bytes__and_calls_read_with_correct_size__and_calls_sleep_correctly( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker): # mock sleep to speed up the test mocked_sleep = mocker.patch.object(mc_comm, "sleep", autospec=True) mc_process = four_board_mc_comm_process["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] # Arbitrarily slice the magic word across multiple reads and add empty reads to simulate no bytes being available to read test_read_values = [SERIAL_COMM_MAGIC_WORD_BYTES[:4]] test_read_values.extend([ bytes(0) for _ in range(SERIAL_COMM_REGISTRATION_TIMEOUT_SECONDS - 1) ]) test_read_values.append(SERIAL_COMM_MAGIC_WORD_BYTES[4:]) # add a real data packet after but remove magic word dummy_timestamp = 0 test_packet = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) packet_length_bytes = test_packet[len(SERIAL_COMM_MAGIC_WORD_BYTES ):len(SERIAL_COMM_MAGIC_WORD_BYTES) + SERIAL_COMM_PACKET_INFO_LENGTH_BYTES] test_read_values.append(packet_length_bytes) test_read_values.append(test_packet[len(SERIAL_COMM_MAGIC_WORD_BYTES) + SERIAL_COMM_PACKET_INFO_LENGTH_BYTES:]) mocked_read = mocker.patch.object(simulator, "read", autospec=True, side_effect=test_read_values) board_idx = 0 mc_process.set_board_connection(board_idx, simulator) assert mc_process.is_registered_with_serial_comm(board_idx) is False invoke_process_run_and_check_errors(mc_process) assert mc_process.is_registered_with_serial_comm(board_idx) is True # Assert it reads once initially then once per second until status beacon period is reached (a new packet should be available by then). Tanner (3/16/21): changed == to >= in the next line because others parts of mc_comm may call read after the magic word is registered assert len(mocked_read.call_args_list ) >= SERIAL_COMM_REGISTRATION_TIMEOUT_SECONDS + 1 assert mocked_read.call_args_list[0] == mocker.call(size=8) assert mocked_read.call_args_list[1] == mocker.call(size=4) assert mocked_read.call_args_list[2] == mocker.call(size=4) assert mocked_read.call_args_list[3] == mocker.call(size=4) assert mocked_read.call_args_list[4] == mocker.call(size=4) # Assert sleep is called with correct value and correct number of times expected_sleep_secs = 1 for sleep_call_num in range(SERIAL_COMM_REGISTRATION_TIMEOUT_SECONDS - 1): sleep_iter_call = mocked_sleep.call_args_list[sleep_call_num][0][0] assert (sleep_call_num, sleep_iter_call) == ( sleep_call_num, expected_sleep_secs, )
def test_MantarrayMcSimulator__processes_set_stimulation_protocol_command__when_an_incorrect_amount_of_module_assignments_are_given( mantarray_mc_simulator_no_beacon, test_num_module_assignments, test_description ): simulator = mantarray_mc_simulator_no_beacon["simulator"] set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) stim_info_dict = { "protocols": [ { "protocol_id": "V", "stimulation_type": choice(["C", "V"]), "run_until_stopped": choice([True, False]), "subprotocols": [get_random_subprotocol()], } ], "protocol_assignments": { GENERIC_24_WELL_DEFINITION.get_well_name_from_well_index(well_idx): "V" for well_idx in range(24) }, } stim_info_bytes = convert_stim_dict_to_bytes(stim_info_dict) # add or remove an assignment if test_num_module_assignments > 24: stim_info_bytes += bytes([0]) else: stim_info_bytes = stim_info_bytes[:-1] expected_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) set_protocols_command = create_data_packet( expected_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_SET_STIM_PROTOCOL_PACKET_TYPE, stim_info_bytes, ) simulator.write(set_protocols_command) invoke_process_run_and_check_errors(simulator) # assert stim info was not updated assert simulator.get_stim_info() == {} # assert command response is correct stim_command_response = simulator.read( size=get_full_packet_size_from_packet_body_size(SERIAL_COMM_TIMESTAMP_LENGTH_BYTES + 1) ) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=convert_to_timestamp_bytes(expected_pc_timestamp) + bytes([SERIAL_COMM_COMMAND_FAILURE_BYTE]), )
def test_McCommunicationProcess__handles_plate_event( test_barcode, expected_valid_flag, test_description, four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon, ): mc_process = four_board_mc_comm_process_no_handshake["mc_process"] simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] set_connection_and_register_simulator( four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon) to_main_queue = four_board_mc_comm_process_no_handshake["board_queues"][0][ 1] # send plate event packet from simulator was_plate_placed_byte = expected_valid_flag is not None plate_event_packet = create_data_packet( randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE), SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_PLATE_EVENT_PACKET_TYPE, bytes([was_plate_placed_byte]) + bytes(test_barcode, encoding="ascii"), ) put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "add_read_bytes", "read_bytes": plate_event_packet }, testing_queue, ) invoke_process_run_and_check_errors(simulator) # process plate event packet and send barcode comm to main invoke_process_run_and_check_errors(mc_process) confirm_queue_is_eventually_of_size(to_main_queue, 1) # check that comm was sent correctly expected_barcode_comm = { "communication_type": "barcode_comm", "board_idx": 0, "barcode": test_barcode, } if expected_valid_flag is not None: expected_barcode_comm["valid"] = expected_valid_flag actual = to_main_queue.get(timeout=QUEUE_CHECK_TIMEOUT_SECONDS) assert actual == expected_barcode_comm
def test_MantarrayMcSimulator__processes_testing_commands_during_reboot( mantarray_mc_simulator_no_beacon, mocker): simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] mocker.patch.object( mc_simulator, "_get_secs_since_reboot_command", autospec=True, return_value=AVERAGE_MC_REBOOT_DURATION_SECONDS - 1, ) spied_get_absolute_timer = mocker.spy(simulator, "_get_absolute_timer") # send reboot command test_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) simulator.write( create_data_packet( test_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_SIMPLE_COMMAND_PACKET_TYPE, bytes([SERIAL_COMM_REBOOT_COMMAND_BYTE]), )) invoke_process_run_and_check_errors(simulator) # remove reboot response packet invoke_process_run_and_check_errors(simulator) reboot_response_size = get_full_packet_size_from_packet_body_size( SERIAL_COMM_TIMESTAMP_LENGTH_BYTES) reboot_response = simulator.read(size=reboot_response_size) assert_serial_packet_is_expected( reboot_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=convert_to_timestamp_bytes(test_timestamp), timestamp=spied_get_absolute_timer.spy_return, ) # add read bytes as test command expected_item = b"the_item" test_dict = {"command": "add_read_bytes", "read_bytes": expected_item} put_object_into_queue_and_raise_error_if_eventually_still_empty( test_dict, testing_queue) invoke_process_run_and_check_errors(simulator) actual = simulator.read(size=len(expected_item)) assert actual == expected_item
def test_handle_data_packets__parses_single_stim_data_packet_with_multiple_statuses_correctly( ): base_global_time = randint(0, 100) test_time_indices = [ random_time_index(), random_time_index(), random_time_index() ] test_well_idx = randint(0, 23) test_subprotocol_indices = [randint(0, 5), randint(0, 5), 0] test_statuses = [ StimStatuses.ACTIVE, StimStatuses.NULL, StimStatuses.RESTARTING ] stim_packet_body = bytes([3]) # num status updates in packet for packet_idx in range(3): stim_packet_body += ( bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_idx]]) + bytes([test_statuses[packet_idx]]) + test_time_indices[packet_idx].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[packet_idx]])) test_data_packet = create_data_packet( random_timestamp(), SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STIM_STATUS_PACKET_TYPE, stim_packet_body, ) parsed_data_dict = handle_data_packets( bytearray(test_data_packet), [1, 2, 3, 4], base_global_time # arbitrary channel list, ) actual_stim_data = parsed_data_dict["stim_data"] assert list(actual_stim_data.keys()) == [test_well_idx] np.testing.assert_array_equal( actual_stim_data[test_well_idx], # removing last item in these lists since restarting status info is not needed [np.array(test_time_indices[:-1]), test_subprotocol_indices[:-1]], )
def test_create_data_packet__creates_data_packet_bytes_correctly(): test_timestamp = 100 test_module_id = 0 test_packet_type = 1 test_data = bytes([1, 5, 3]) expected_data_packet_bytes = SERIAL_COMM_MAGIC_WORD_BYTES expected_data_packet_bytes += (17).to_bytes(2, byteorder="little") expected_data_packet_bytes += test_timestamp.to_bytes( SERIAL_COMM_TIMESTAMP_LENGTH_BYTES, byteorder="little") expected_data_packet_bytes += bytes([test_module_id, test_packet_type]) expected_data_packet_bytes += test_data expected_data_packet_bytes += crc32(expected_data_packet_bytes).to_bytes( SERIAL_COMM_CHECKSUM_LENGTH_BYTES, byteorder="little") actual = create_data_packet( test_timestamp, test_module_id, test_packet_type, test_data, ) assert actual == expected_data_packet_bytes
def test_MantarrayMcSimulator__automatically_sends_plate_barcode_after_time_is_synced( mantarray_mc_simulator_no_beacon, ): simulator = mantarray_mc_simulator_no_beacon["simulator"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] # put simulator in time sync ready status put_object_into_queue_and_raise_error_if_eventually_still_empty( { "command": "set_status_code", "status_code": SERIAL_COMM_TIME_SYNC_READY_CODE }, testing_queue, ) invoke_process_run_and_check_errors(simulator) # send set time command test_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) test_set_time_command = create_data_packet( test_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_SIMPLE_COMMAND_PACKET_TYPE, bytes([SERIAL_COMM_SET_TIME_COMMAND_BYTE]) + convert_to_timestamp_bytes(test_pc_timestamp), ) simulator.write(test_set_time_command) # process set time command and remove response invoke_process_run_and_check_errors(simulator) simulator.read(size=get_full_packet_size_from_packet_body_size( SERIAL_COMM_TIMESTAMP_LENGTH_BYTES)) barcode_packet_size = get_full_packet_size_from_packet_body_size( len(MantarrayMcSimulator.default_barcode)) # assert barcode packet sent correctly barcode_packet = simulator.read(size=barcode_packet_size) assert_serial_packet_is_expected( barcode_packet, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_BARCODE_FOUND_PACKET_TYPE, bytes(MantarrayMcSimulator.default_barcode, encoding="ascii"), )
def test_MantarrayMcSimulator__raises_error_when_magnetometer_config_command_received_with_invalid_sampling_period( mantarray_mc_simulator_no_beacon, mocker, patch_print): set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) simulator = mantarray_mc_simulator_no_beacon["simulator"] bad_sampling_period = 1001 magnetometer_config_bytes = create_magnetometer_config_bytes( MantarrayMcSimulator.default_24_well_magnetometer_config) # send command with invalid sampling period dummy_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) change_config_command = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_SIMPLE_COMMAND_PACKET_TYPE, bytes([SERIAL_COMM_MAGNETOMETER_CONFIG_COMMAND_BYTE]) + bad_sampling_period.to_bytes(2, byteorder="little") + magnetometer_config_bytes, ) simulator.write(change_config_command) # process command and raise error with given sampling period with pytest.raises(SerialCommInvalidSamplingPeriodError, match=str(bad_sampling_period)): invoke_process_run_and_check_errors(simulator)
def test_McCommunicationProcess__raises_error_if_length_of_additional_bytes_read_is_smaller_than_size_specified_in_packet_header( four_board_mc_comm_process_no_handshake, mantarray_mc_simulator_no_beacon, mocker, patch_print): mc_process = four_board_mc_comm_process_no_handshake["mc_process"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] simulator = mantarray_mc_simulator_no_beacon["simulator"] # create valid packet dummy_timestamp = 0 test_bytes = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) # cut off checksum bytes so that the remaining packet size is less than the specified packet length truncated_test_bytes = test_bytes[:-SERIAL_COMM_CHECKSUM_LENGTH_BYTES] test_item = { "command": "add_read_bytes", "read_bytes": truncated_test_bytes } put_object_into_queue_and_raise_error_if_eventually_still_empty( test_item, testing_queue) invoke_process_run_and_check_errors(simulator) board_idx = 0 mc_process.set_board_connection(board_idx, simulator) num_bytes_not_counted_in_packet_len = len(SERIAL_COMM_MAGIC_WORD_BYTES) + 2 with pytest.raises( SerialCommNotEnoughAdditionalBytesReadError) as exc_info: invoke_process_run_and_check_errors(mc_process) assert f"Expected Size: {len(test_bytes) - num_bytes_not_counted_in_packet_len}" in exc_info.value.args[ 0] assert ( f"Actual Size: {len(truncated_test_bytes) - num_bytes_not_counted_in_packet_len}" in exc_info.value.args[0])
def test_McCommunicationProcess__raises_error_if_magic_word_is_incorrect_in_packet_after_previous_magic_word_has_been_registered( four_board_mc_comm_process, mantarray_mc_simulator_no_beacon, mocker, patch_print, ): mc_process = four_board_mc_comm_process["mc_process"] testing_queue = mantarray_mc_simulator_no_beacon["testing_queue"] simulator = mantarray_mc_simulator_no_beacon["simulator"] board_idx = 0 dummy_timestamp = 0 mc_process.set_board_connection(board_idx, simulator) assert mc_process.is_registered_with_serial_comm(board_idx) is False test_bytes_1 = create_data_packet( dummy_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STATUS_BEACON_PACKET_TYPE, DEFAULT_SIMULATOR_STATUS_CODE, ) # Add arbitrary incorrect value into magic word slot bad_magic_word = b"NANOSURF" test_bytes_2 = bad_magic_word + test_bytes_1[ len(SERIAL_COMM_MAGIC_WORD_BYTES):] test_item = { "command": "add_read_bytes", "read_bytes": [test_bytes_1, test_bytes_2], } put_object_into_queue_and_raise_error_if_eventually_still_empty( test_item, testing_queue) invoke_process_run_and_check_errors(simulator) with pytest.raises(SerialCommIncorrectMagicWordFromMantarrayError, match=str(bad_magic_word)): # First iteration registers magic word, next iteration receive incorrect magic word invoke_process_run_and_check_errors(mc_process, num_iterations=2)
from mantarray_desktop_app import SERIAL_COMM_MAX_TIMESTAMP_VALUE from mantarray_desktop_app.constants import GENERIC_24_WELL_DEFINITION import pytest from stdlib_utils import drain_queue from stdlib_utils import invoke_process_run_and_check_errors from stdlib_utils import put_object_into_queue_and_raise_error_if_eventually_still_empty from stdlib_utils import QUEUE_CHECK_TIMEOUT_SECONDS from stdlib_utils import TestingQueue STATUS_BEACON_SIZE_BYTES = 28 HANDSHAKE_RESPONSE_SIZE_BYTES = 36 TEST_HANDSHAKE_TIMESTAMP = 12345 TEST_HANDSHAKE = create_data_packet( TEST_HANDSHAKE_TIMESTAMP, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_HANDSHAKE_PACKET_TYPE, bytes(0), ) DEFAULT_SIMULATOR_STATUS_CODE = convert_to_status_code_bytes( SERIAL_COMM_BOOT_UP_CODE) def random_time_index(): return randint(100, 0xFFFFFFFFFF) def random_time_offset(): return randint(0, 0xFFFF)
def test_MantarrayMcSimulator__processes_stop_stimulation_command(mantarray_mc_simulator_no_beacon, mocker): simulator = mantarray_mc_simulator_no_beacon["simulator"] set_simulator_idle_ready(mantarray_mc_simulator_no_beacon) spied_global_timer = mocker.spy(simulator, "_get_global_timer") # set single arbitrary protocol applied to wells randomly stim_info = simulator.get_stim_info() stim_info["protocols"] = [ { "protocol_id": "B", "stimulation_type": "V", "run_until_stopped": True, "subprotocols": [get_random_subprotocol()], } ] stim_info["protocol_assignments"] = { GENERIC_24_WELL_DEFINITION.get_well_name_from_well_index(well_idx): choice(["B", None]) if well_idx else "B" for well_idx in range(24) } initial_stim_running_statuses = { well_name: bool(protocol_id) for well_name, protocol_id in stim_info["protocol_assignments"].items() } simulator.get_stim_running_statuses().update(initial_stim_running_statuses) for response_byte_value in ( SERIAL_COMM_COMMAND_SUCCESS_BYTE, SERIAL_COMM_COMMAND_FAILURE_BYTE, ): # send start stim command expected_pc_timestamp = randint(0, SERIAL_COMM_MAX_TIMESTAMP_VALUE) stop_stimulation_command = create_data_packet( expected_pc_timestamp, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STOP_STIM_PACKET_TYPE, ) simulator.write(stop_stimulation_command) invoke_process_run_and_check_errors(simulator) # make sure finished status updates are sent if command succeeded if response_byte_value == SERIAL_COMM_COMMAND_SUCCESS_BYTE: status_update_bytes = bytes([sum(initial_stim_running_statuses.values())]) for well_name in simulator.get_stim_running_statuses().keys(): if not initial_stim_running_statuses[well_name]: continue well_idx = GENERIC_24_WELL_DEFINITION.get_well_index_from_well_name(well_name) status_update_bytes += ( bytes([STIM_WELL_IDX_TO_MODULE_ID[well_idx]]) + bytes([StimStatuses.FINISHED]) + (spied_global_timer.spy_return).to_bytes(8, byteorder="little") + bytes([STIM_COMPLETE_SUBPROTOCOL_IDX]) ) expected_size = get_full_packet_size_from_packet_body_size(len(status_update_bytes)) stim_command_response = simulator.read(size=expected_size) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STIM_STATUS_PACKET_TYPE, additional_bytes=status_update_bytes, ) # assert that stimulation was stopped on all wells assert simulator.get_stim_running_statuses() == { convert_module_id_to_well_name(module_id): False for module_id in range(1, 25) } # assert command response is correct additional_bytes = convert_to_timestamp_bytes(expected_pc_timestamp) + bytes([response_byte_value]) expected_size = get_full_packet_size_from_packet_body_size(len(additional_bytes)) stim_command_response = simulator.read(size=expected_size) assert_serial_packet_is_expected( stim_command_response, SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_COMMAND_RESPONSE_PACKET_TYPE, additional_bytes=additional_bytes, )
def test_handle_data_packets__parses_multiple_stim_data_packet_with_multiple_wells_and_statuses_correctly( ): base_global_time = randint(0, 100) test_well_indices = [randint(0, 11), randint(12, 23)] test_time_indices = [ [random_time_index(), random_time_index()], [random_time_index(), random_time_index(), random_time_index()], ] test_subprotocol_indices = [ [0, randint(1, 5)], [randint(0, 5), randint(0, 5), STIM_COMPLETE_SUBPROTOCOL_IDX], ] test_statuses = [ [StimStatuses.RESTARTING, StimStatuses.NULL], [StimStatuses.ACTIVE, StimStatuses.NULL, StimStatuses.FINISHED], ] stim_packet_body_1 = ( bytes([2]) # num status updates in packet + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_indices[0]]]) + bytes([test_statuses[0][0]]) + test_time_indices[0][0].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[0][0]]) + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_indices[1]]]) + bytes([test_statuses[1][0]]) + test_time_indices[1][0].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[1][0]])) stim_packet_body_2 = ( bytes([3]) # num status updates in packet + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_indices[1]]]) + bytes([test_statuses[1][1]]) + test_time_indices[1][1].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[1][1]]) + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_indices[0]]]) + bytes([test_statuses[0][1]]) + test_time_indices[0][1].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[0][1]]) + bytes([STIM_WELL_IDX_TO_MODULE_ID[test_well_indices[1]]]) + bytes([test_statuses[1][2]]) + test_time_indices[1][2].to_bytes(8, byteorder="little") + bytes([test_subprotocol_indices[1][2]])) test_data_packet_1 = create_data_packet( random_timestamp(), SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STIM_STATUS_PACKET_TYPE, stim_packet_body_1, ) test_data_packet_2 = create_data_packet( random_timestamp(), SERIAL_COMM_MAIN_MODULE_ID, SERIAL_COMM_STIM_STATUS_PACKET_TYPE, stim_packet_body_2, ) parsed_data_dict = handle_data_packets( bytearray(test_data_packet_1 + test_data_packet_2), [], base_global_time) actual_stim_data = parsed_data_dict["stim_data"] assert sorted(list(actual_stim_data.keys())) == sorted(test_well_indices) np.testing.assert_array_equal( actual_stim_data[test_well_indices[0]], # removing first item in these lists since restarting status info is not needed [np.array(test_time_indices[0][1:]), test_subprotocol_indices[0][1:]], ) np.testing.assert_array_equal( actual_stim_data[test_well_indices[1]], [np.array(test_time_indices[1]), test_subprotocol_indices[1]], )