diff --git a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py index 073933ad7dc01576e3817505c81c970fbe87ff1d..f94ec4e16fa7d1baf117d8108bc3f896560df70f 100644 --- a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py +++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py @@ -243,6 +243,21 @@ class ProtocolAttribute: value = await self.node.get_value() try: + # Pytango strings are Latin-1, and will crash on receiving Unicode strings + # (see https://gitlab.com/tango-controls/pytango/-/issues/72) + # So we explicitly convert to Latin-1 and replace errors with '?'. + # + # Note that PyTango also accepts byte arrays as strings, so we don't + # have to actually decode() the result. + + def fix_string(s): + return s.encode('latin-1',errors="replace").decode('latin-1') + + if type(value) == list and len(value) > 0 and type(value[0]) == str: + value = [fix_string(v) for v in value] + elif type(value) == str: + value = fix_string(value) + if self.dim_y + self.dim_x == 1: # scalar return value diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index d62a49e368598ccf5868f5c74402233d5eb403cf..afcdf78871e321a2489339f747f61351643790b2 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -87,6 +87,13 @@ class Beam(lofar_device): # -------- # overloaded functions # -------- + + def init_device(self): + super().init_device() + + # thread to perform beam tracking + self.HBAT_beam_tracker = None + @log_exceptions() def configure_for_initialise(self): super().configure_for_initialise() @@ -126,8 +133,9 @@ class Beam(lofar_device): @log_exceptions() def configure_for_off(self): - # Stop thread object - self.HBAT_beam_tracker.stop() + if self.HBAT_beam_tracker: + # Stop thread object + self.HBAT_beam_tracker.stop() super().configure_for_off() diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py index a483aec43eacf571a6344ee1b1823cf62af96a92..5f0feb5e661dba53d8cd800273d15ae7c35692bd 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -112,6 +112,10 @@ class SDP(opcua_device): FPGA_boot_image_RW = attribute_wrapper(comms_annotation=["FPGA_boot_image_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_global_node_index_R = attribute_wrapper(comms_annotation=["FPGA_global_node_index_R"], datatype=numpy.uint32, dims=(16,)) FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["FPGA_hardware_version_R"], datatype=numpy.str, dims=(16,)) + FPGA_pps_present_R = attribute_wrapper(comms_annotation=["FPGA_pps_present_R"], datatype=numpy.bool_, dims=(16,)) + FPGA_pps_capture_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_capture_cnt_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_pps_expected_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_R"], datatype=numpy.uint32, dims=(16,)) + FPGA_pps_expected_cnt_RW = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["FPGA_processing_enable_R"], datatype=numpy.bool_, dims=(16,)) FPGA_processing_enable_RW = attribute_wrapper(comms_annotation=["FPGA_processing_enable_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_scrap_R = attribute_wrapper(comms_annotation=["FPGA_scrap_R"], datatype=numpy.int32, dims=(8192,)) @@ -128,6 +132,7 @@ class SDP(opcua_device): FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_subband_weights_R = attribute_wrapper(comms_annotation=["FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(12 * 512, 16)) FPGA_subband_weights_RW = attribute_wrapper(comms_annotation=["FPGA_subband_weights_RW"], datatype=numpy.uint32, dims=(12 * 512, 16), access=AttrWriteType.READ_WRITE) + FPGA_time_since_last_pps_R = attribute_wrapper(comms_annotation=["FPGA_time_since_last_pps_R"], datatype=numpy.uint32, dims=(16,)) FPGA_temp_R = attribute_wrapper(comms_annotation=["FPGA_temp_R"], datatype=numpy.float_, dims=(16,)) FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(12, 16)) FPGA_wg_amplitude_RW = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py index 25968deded93d8d45160c5a1949521784ab59e7a..4582c608888b2dbb2d0b26b5abd3f1a683c76c6c 100644 --- a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py @@ -178,6 +178,35 @@ class TestOPCua(base.AsyncTestCase): comp = val == get_test_value() self.assertTrue(comp.all(), "Read value unequal to expected value: \n\t{} \n\t{}".format(val, get_test_value())) + async def test_read_unicode(self): + """ + Test whether unicode characters are replaced by '?'. + """ + # test 0-2 dimensions of strings + for dims in range(0,2): + # wrap a value in the current number of dimensions + def wrap_dims(x): + if dims == 0: + return x + elif dims == 1: + return [x] + elif dims == 2: + return [[x]] + + # return a constructed value with unicode + async def get_value(): + return wrap_dims(b'foo \xef\xbf\xbd bar'.decode('utf-8')) + + m_node = asynctest.asynctest.CoroutineMock() + m_node.get_value = get_value + + # create the ProtocolAttribute to test + test = opcua_client.ProtocolAttribute(m_node, 1, 0, opcua_client.numpy_to_OPCua_dict[str]) + + # check if unicode is replaced by ? + val = await test.read_function() + self.assertEqual(wrap_dims("foo ? bar"), val) + def test_type_map(self): for numpy_type, opcua_type in opcua_client.numpy_to_OPCua_dict.items(): # derive a default value that can get lost in a type translation