From 263caa9f52ffd13ed26f03238b1687313c64010d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Tue, 15 Feb 2022 09:24:32 +0100 Subject: [PATCH] L2SS-624: Add summarizing attributes for state, improve/fix boot procedure of various devices --- .../docs/source/devices/boot.rst | 12 +- .../docs/source/devices/using.rst | 21 +-- .../docs/source/installation.rst | 22 ++- .../tangostationcontrol/devices/apsct.py | 17 ++- .../tangostationcontrol/devices/apspu.py | 19 +++ .../tangostationcontrol/devices/boot.py | 133 +++++++----------- .../devices/lofar_device.py | 59 +++++++- .../tangostationcontrol/devices/recv.py | 102 +++++--------- .../tangostationcontrol/devices/sdp/sdp.py | 19 ++- .../tangostationcontrol/devices/unb2.py | 42 +++++- .../devices/test_device_boot.py | 21 +-- 11 files changed, 281 insertions(+), 186 deletions(-) diff --git a/tangostationcontrol/docs/source/devices/boot.rst b/tangostationcontrol/docs/source/devices/boot.rst index cbb76e487..a9d788d42 100644 --- a/tangostationcontrol/docs/source/devices/boot.rst +++ b/tangostationcontrol/docs/source/devices/boot.rst @@ -3,13 +3,19 @@ Boot ==================== -The ``boot == DeviceProxy("STAT/Boot/1")`` device is responsible for (re)starting and initialising the other devices. Devices which are not reachable, for example because their docker container is explicitly stopped, are skipped during initialisation. This device provides the following commands: +The ``boot == DeviceProxy("STAT/Boot/1")`` device is responsible for (re)starting and initialising the other devices. Devices which are not reachable, for example because their docker container is explicitly stopped, are skipped during initialisation. It is the only device that starts in the ``ON`` state, so it can be used immediately to initialise other devices. -:boot(): Stop and start the other devices in the correct order, set their default values, and command them to initialise their hardware. This procedure runs asynchronously, causing this command to return immediately. Initialisation is aborted if an error is encountered. +This device provides the following commands: + +:boot(): Start the other devices in the correct order, if they are not ON. Set their default values, and command them to initialise their hardware. This procedure runs asynchronously, causing this command to return immediately. Initialisation is aborted if an error is encountered. + + :returns: ``None`` + +:warm_boot(): Like ``boot()``, but do not initialise the hardware. Just start our software devices. :returns: ``None`` -:resume(): Resume an earlier boot attempt: start initialising devices from the first one that failed to initialise, instead of from scratch. +:reboot(): Like ``boot()``, but turn off all other devices first. :returns: ``None`` diff --git a/tangostationcontrol/docs/source/devices/using.rst b/tangostationcontrol/docs/source/devices/using.rst index d72fee2af..c04302062 100644 --- a/tangostationcontrol/docs/source/devices/using.rst +++ b/tangostationcontrol/docs/source/devices/using.rst @@ -20,17 +20,18 @@ The state of a device is then queried with ``device.state()``. Each device can b - ``DevState.OFF``: The device is not operating, - ``DevState.INIT``: The device is being initialised, - ``DevState.STANDBY``: The device is initialised and ready to be configured further, -- ``DevState.ON``: The device is operational. +- ``DevState.ON``: The device is operational, +- ``DevState.ALARM``: The device is operational, but one or more attributes are in alarm, - ``DevState.FAULT``: The device is malfunctioning. Functionality cannot be counted on. - The ``device.state()`` function can throw an error, if the device cannot be reached at all. For example, because it's docker container is not running. See the :ref:`docker` device on how to start it. Each device provides the following commands to change the state: -:off(): Turn the device ``OFF`` from any state. +:boot(): Turn on the device, and initialise the hardware. Moves from ``OFF`` to ``ON``. -:initialise(): Initialise the device from the ``OFF`` state, to bring it to the ``STANDBY`` state. +:warm_boot(): Turn on the device, but do not change the hardware. Moves from ``OFF`` to ``ON``. -:on(): Mark the device as operational, from the ``STANDBY`` state, bringing it to ``ON``. +:off(): Turn the device ``OFF`` from any state. The following procedure is a good way to bring a device to ``ON`` from any state:: @@ -38,9 +39,7 @@ The following procedure is a good way to bring a device to ``ON`` from any state if device.state() == DevState.FAULT: device.off() if device.state() == DevState.OFF: - device.initialise() - if device.state() == DevState.STANDBY: - device.on() + device.boot() return device.state() @@ -77,7 +76,13 @@ Most devices provide the following commands, in order to configure the hardware :initialise_hardware(): For devices that control hardware, this command runs the hardware initialisation procedure. -Typically, ``set_defaults()`` and ``initialise_hardware()`` are called in that order in the ``STANDBY`` state. See also :ref:`boot`, which provides functionality to initialise all the devices. +Typically, ``set_defaults()`` and ``initialise_hardware()`` are called in that order in the ``STANDBY`` state, during ``boot()``. To manually go through the boot sequence, use the following commands: + +:initialise(): Initialise the device (connect to the hardware). Moves from ``OFF`` to ``STANDBY``. + +:on(): Mark the device as operational. Moves from ``STANDBY`` to ``ON``. + +See also :ref:`boot`, which provides functionality to initialise all the devices. .. _attributes: diff --git a/tangostationcontrol/docs/source/installation.rst b/tangostationcontrol/docs/source/installation.rst index 58ccf2cd3..763b3e1e1 100644 --- a/tangostationcontrol/docs/source/installation.rst +++ b/tangostationcontrol/docs/source/installation.rst @@ -44,29 +44,25 @@ After bootstrapping, and after a reboot, the software and hardware of the statio The following commands start all the software devices to control the station hardware, and initialise the hardware with the configured default settings. Go to http://localhost:8888, start a new *Station Control* notebook, and initiate the software boot sequence:: - # reset our boot device - boot.off() - assert boot.state() == DevState.OFF - boot.initialise() - assert boot.state() == DevState.STANDBY - boot.on() - assert boot.state() == DevState.ON - # start and initialise the other devices - boot.initialise_station() + boot.boot() # wait for the devices to be initialised import time - while boot.initialising_station_R: - print(f"Still initialising station. {boot.initialisation_progress_R}% complete. State: {boot.initialisation_status_R}") + while boot.booting_R: + print(f"Still initialising station. {boot.progress_R}% complete. State: {boot.status_R}") time.sleep(1) # print conclusion - if boot.initialisation_progress_R == 100: + if boot.progress_R == 100: print("Done initialising station.") else: - print(f"Failed to initialise station: {boot.initialisation_status_R}") + print(f"Failed to initialise station: {boot.status_R}") + + # print what did and did not get initialised + print(f"Initialised devices: {boot.initialised_devices_R}") + print(f"Uninitialised devices: {boot.uninitialised_devices_R}") See :ref:`boot` for more information on the ``boot`` device. diff --git a/tangostationcontrol/tangostationcontrol/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py index 1a6fa655c..f75707676 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apsct.py +++ b/tangostationcontrol/tangostationcontrol/devices/apsct.py @@ -13,7 +13,7 @@ # PyTango imports from tango import DebugIt -from tango.server import command +from tango.server import command, attribute, device_property from tango import AttrWriteType, DevState import numpy # Additional import @@ -36,6 +36,12 @@ class APSCT(opcua_device): # Device Properties # ----------------- + APSCTTR_monitor_rate_RW_default = device_property( + dtype='DevLong64', + mandatory=False, + default_value=1 + ) + # ---------- # Attributes # ---------- @@ -66,6 +72,15 @@ class APSCT(opcua_device): APSCT_PWR_PPSDIST_3V3_R = attribute_wrapper(comms_annotation=["APSCT_PWR_PPSDIST_3V3_R" ],datatype=numpy.float64) APSCT_TEMP_R = attribute_wrapper(comms_annotation=["APSCT_TEMP_R" ],datatype=numpy.float64) + # ---------- + # Summarising Attributes + # ---------- + APSCT_error_R = attribute(dtype=bool) + + def read_APSCT_error_R(self): + return ((self.proxy.APSCTTR_I2C_error_R > 0) + | self.alarm_val("APSCT_PCB_ID_R")) + # -------- # overloaded functions # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py index 3a106ba1f..44b58c25e 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apspu.py +++ b/tangostationcontrol/tangostationcontrol/devices/apspu.py @@ -13,6 +13,7 @@ # PyTango imports from tango import AttrWriteType +from tango.server import attribute, device_property import numpy # Additional import @@ -30,6 +31,12 @@ class APSPU(opcua_device): # Device Properties # ----------------- + APSPUTR_monitor_rate_RW_default = device_property( + dtype='DevLong64', + mandatory=False, + default_value=1 + ) + # ---------- # Attributes # ---------- @@ -53,6 +60,18 @@ class APSPU(opcua_device): APSPU_RCU2D_TEMP_R = attribute_wrapper(comms_annotation=["APSPU_RCU2D_TEMP_R" ],datatype=numpy.float64) APSPU_RCU2D_VOUT_R = attribute_wrapper(comms_annotation=["APSPU_RCU2D_VOUT_R" ],datatype=numpy.float64) + # ---------- + # Summarising Attributes + # ---------- + APSPU_error_R = attribute(dtype=bool) + + def read_APSPU_error_R(self): + return ((self.proxy.APSPUTR_I2C_error_R > 0) + | self.alarm_val("APSPU_PCB_ID_R") + | self.alarm_val("APSPU_FAN1_RPM_R") + | self.alarm_val("APSPU_FAN2_RPM_R") + | self.alarm_val("APSPU_FAN3_RPM_R")) + # -------- # overloaded functions # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py index 2d5054f78..7dc95e748 100644 --- a/tangostationcontrol/tangostationcontrol/devices/boot.py +++ b/tangostationcontrol/tangostationcontrol/devices/boot.py @@ -17,7 +17,7 @@ Boots the rest of the station software. from tango import DebugIt from tango.server import command from tango.server import device_property, attribute -from tango import AttrWriteType, DeviceProxy, DevState +from tango import AttrWriteType, DeviceProxy, DevState, DevSource # Additional import import numpy @@ -40,17 +40,18 @@ class InitialisationException(Exception): class DevicesInitialiser(object): """ - Initialise devices on this station. + Initialise devices on this station which are not already on (reboot=False), + or all of them (reboot=True). Devices which are unreachable are assumed to be brought down explicitly, - and are ignored (unless ignore_unavailable_devices == False). + and are ignored. Initialisation happens in a separate thread. It is started by calling the start() method, and progress can be followed by inspecting the members progress (0-100), status (string), and is_running() (bool). """ - def __init__(self, device_names, ignore_unavailable_devices=True, initialise_hardware=True, proxy_timeout=10.0): - self.ignore_unavailable_devices = ignore_unavailable_devices + def __init__(self, device_names, reboot=False, initialise_hardware=True, proxy_timeout=10.0): + self.reboot = reboot self.initialise_hardware = initialise_hardware self.device_names = device_names @@ -74,10 +75,11 @@ class DevicesInitialiser(object): self.set_status(f"Obtaining a DeviceProxy to {name}") devices[name] = DeviceProxy(name) - # set the timeout for all proxies + # set the timeout for all proxies, and never read state from a cache self.set_status("Configuring DeviceProxies") for device in devices.values(): device.set_timeout_millis(int(self.proxy_timeout * 1000)) + device.set_source(DevSource.DEV) return devices @@ -150,8 +152,10 @@ class DevicesInitialiser(object): if self.device_initialised[device]: continue - if self.is_available(device) or not self.ignore_unavailable_devices: - self.start_device(device) + if self.is_available(device): + if self.reboot or self.devices[device].state() not in [DevState.ON, DevState.ALARM]: + self.stop_device(device) + self.boot_device(device) # mark device as initialised self.device_initialised[device] = True @@ -185,40 +189,25 @@ class DevicesInitialiser(object): proxy.Off() if proxy.state() != DevState.OFF: - raise InitialisationException(f"Could not turn off device {device_name}. Please look at its logs.") + raise InitialisationException(f"Could not turn off device {device_name}. It reports status: {proxy.status()}") self.set_status(f"[stopping {device_name}] Stopped device.") - def start_device(self, device_name: str): + def boot_device(self, device_name: str): """ Run the startup sequence for device 'device_name'. """ proxy = self.devices[device_name] - # go to a well-defined state, which may be needed if the user calls - # this function explicitly. - self.stop_device(device_name) - - # setup connections to hardware - self.set_status(f"[restarting {device_name}] Initialising device.") - proxy.Initialise() - if proxy.state() != DevState.STANDBY: - raise InitialisationException(f"Could not initialise device {device_name}. Please look at its logs.") - - # configure the device - self.set_status(f"[restarting {device_name}] Setting defaults.") - proxy.set_defaults() - + self.set_status(f"[restarting {device_name}] Booting device.") if self.initialise_hardware: - self.set_status(f"[restarting {device_name}] Initialising hardware.") - proxy.initialise_hardware() + proxy.boot() + else: + proxy.warm_boot() - # mark as ready for service - self.set_status(f"[restarting {device_name}] Turning on device.") - proxy.On() - if proxy.state() != DevState.ON: - raise InitialisationException(f"Could not turn on device {device_name}. Please look at its logs.") + if proxy.state() not in [DevState.ON, DevState.ALARM]: # ALARM still means booting was succesful + raise InitialisationException(f"Could not boot device {device_name}. It reports status: {proxy.status()}") - self.set_status(f"[restarting {device_name}] Succesfully started.") + self.set_status(f"[restarting {device_name}] Succesfully booted.") @device_logging_to_python() class Boot(lofar_device): @@ -255,16 +244,6 @@ class Boot(lofar_device): ], ) - # By default, we assume any device is not available - # because its docker container was not started, which - # is an explicit and thus intentional action. - # We ignore such devices when initialising the station. - Ignore_Unavailable_Devices = device_property( - dtype='DevBoolean', - mandatory=False, - default_value=True, - ) - # ---------- # Attributes # ---------- @@ -274,22 +253,17 @@ class Boot(lofar_device): initialised_devices_R = attribute(dtype=(str,), max_dim_x=128, access=AttrWriteType.READ, fget=lambda self: [name for name,initialised in self.initialiser.device_initialised.items() if initialised], doc="Which devices were initialised succesfully") uninitialised_devices_R = attribute(dtype=(str,), max_dim_x=128, access=AttrWriteType.READ, fget=lambda self: [name for name,initialised in self.initialiser.device_initialised.items() if not initialised], doc="Which devices have not been initialised or failed to initialise") - @log_exceptions() - def delete_device(self): - """Hook to delete resources allocated in init_device. - - This method allows for any memory or other resources allocated in the - init_device method to be released. This method is called by the device - destructor and by the device Init command (a Tango built-in). - """ - logger.debug("Shutting down...") - - self.Off() - logger.debug("Shut down. Good bye.") - # -------- # overloaded functions # -------- + def init_device(self): + super().init_device() + + # always turn on automatically, so the user doesn't have to boot the boot device + # note: we overloaded our own boot() to boot the station, so explicitly call our own initialisation + self.Initialise() + self.On() + @log_exceptions() def configure_for_off(self): """ user code here. is called when the state is set to OFF """ @@ -301,21 +275,16 @@ class Boot(lofar_device): @log_exceptions() def configure_for_initialise(self): # create an initialiser object so we can query it even before starting the (first) initialisation - self.initialiser = DevicesInitialiser(self.Device_Names, self.Ignore_Unavailable_Devices, self.Initialise_Hardware, self.DeviceProxy_Time_Out) + self.initialiser = DevicesInitialiser(self.Device_Names, False, self.Initialise_Hardware, self.DeviceProxy_Time_Out) - @command() - @DebugIt() - @only_in_states([DevState.ON]) - @fault_on_error() - @log_exceptions() - def boot(self): + def _boot(self, reboot=False, initialise_hardware=True): """ - Initialise or re-initialise all devices on the station. + Initialise all devices on the station that are not yet on (reboot=False), or shut them down first (reboot=True). - This command will take a while to execute, so should be called asynchronously. + If initialise_hardware is set, the hardware behind the devices is also explicitly reinitialised. Turn this + off to perform a warm boot. - If resume == True, a previously started attempt is resumed from the device - that failed to initialise earlier. + This command will take a while to execute, so should be called asynchronously. :return:None """ @@ -324,7 +293,6 @@ class Boot(lofar_device): # already initialising return - # join any previous attempt, if any try: self.initialiser.stop() @@ -332,30 +300,29 @@ class Boot(lofar_device): pass # start new initialisation attempt - self.initialiser = DevicesInitialiser(self.Device_Names, self.Ignore_Unavailable_Devices, self.Initialise_Hardware, self.DeviceProxy_Time_Out) + self.initialiser = DevicesInitialiser(self.Device_Names, reboot, initialise_hardware, self.DeviceProxy_Time_Out) self.initialiser.start() @command() @DebugIt() @only_in_states([DevState.ON]) - @fault_on_error() @log_exceptions() - def resume(self): - """ - Resume booting. A previously started boot() attempt is resumed from - the first device that failed to initialise. - - This command will take a while to execute, so should be called asynchronously. - - :return:None - """ + def boot(self): + self._boot(reboot=False, initialise_hardware=self.Initialise_Hardware) - if self.initialiser.is_running(): - # already initialising - return + @command() + @DebugIt() + @only_in_states([DevState.ON]) + @log_exceptions() + def reboot(self): + self._boot(reboot=True, initialise_hardware=self.Initialise_Hardware) - # just start it again - self.initialiser.start() + @command() + @DebugIt() + @only_in_states([DevState.ON]) + @log_exceptions() + def warm_boot(self): + self._boot(reboot=False, initialise_hardware=False) # ---------- # Run server diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index 5ca671d4c..9d68a5dec 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -13,7 +13,7 @@ # PyTango imports from tango.server import attribute, command, Device, DeviceMeta -from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy +from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy, AttrDataFormat import time import math @@ -266,10 +266,36 @@ class lofar_device(Device, metaclass=DeviceMeta): # This is just the command version of _initialise_hardware(). self._initialise_hardware() + def _boot_device(self, initialise_hardware=True): + # setup connections + self.Initialise() + + if initialise_hardware: + # initialise settings + self.set_defaults() + + # powercycle backing hardware + self.initialise_hardware() + + # make device available + self.On() + + + @only_in_states([DevState.OFF]) + @command() + def boot(self): + self._boot(initialise_hardware=True) + + @only_in_states([DevState.OFF]) + @command() + def warm_boot(self): + self._boot(initialise_hardware=False) + def _initialise_hardware(self): """ Override this method to initialise any hardware after configuring it. """ pass + def wait_attribute(self, attr_name, value, timeout=10, pollperiod=0.2): """ Wait until the given attribute obtains the given value. @@ -287,3 +313,34 @@ class lofar_device(Device, metaclass=DeviceMeta): time.sleep(pollperiod) raise Exception(f"{attr_name} != {value} after {timeout} seconds still.") + + def alarm_val(self, attr_name): + """ Returns whether min_alarm < attr_value < max_alarm for the given attribute, + if these values are set. For arrays, an array of booleans of the same shape + is returned. """ + + # fetch attribute configuration + attr_config = self.proxy.get_attribute_config(attr_name) + alarms = attr_config.alarms + is_scalar = attr_config.data_format == AttrDataFormat.SCALAR + + # fetch attribute value as an array + value = self.proxy.read_attribute(attr_name).value + if is_scalar: + value = numpy.array(value) + + # construct alarm state, in the same shape as the attribute + alarm_state = numpy.zeros(value.shape, dtype=bool) + + if alarms.max_alarm != 'Not specified': + alarm_state |= value >= value.dtype.type(alarms.max_alarm) + + if alarms.min_alarm != 'Not specified': + alarm_state |= value <= value.dtype.type(alarms.min_alarm) + + # return alarm state, as the same type as the attribute + if is_scalar: + return alarm_state[0] + else: + return alarm_state + diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index 22f2f8483..965f27648 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -49,6 +49,18 @@ class RECV(opcua_device): default_value=[True] * 32 ) + RCU_mask_lock = device_property( + dtype='DevVarBooleanArray', + mandatory=False, + default_value=[False] * 32 + ) + + RECVTR_monitor_rate_RW_default = device_property( + dtype='DevLong64', + mandatory=False, + default_value=1 + ) + HBAT_bf_delay_step_delays = device_property( dtype="DevVarFloatArray", mandatory=False, @@ -100,9 +112,6 @@ class RECV(opcua_device): # ---------- # Attributes # ---------- - ANT_status_R = attribute(dtype=(str,), max_dim_x=3, max_dim_y=32) - RCU_LED_colour_R = attribute(dtype=(numpy.uint32,), max_dim_x=32, fget=lambda self: (2 * self.proxy.RCU_LED_green_on_R + 4 * self.proxy.RCU_LED_red_on_R).astype(numpy.uint32)) - ANT_mask_RW = attribute_wrapper(comms_annotation=["ANT_mask_RW" ],datatype=numpy.bool_ , dims=(3,32), access=AttrWriteType.READ_WRITE) # The HBAT beamformer delays represent 32 delays for each of the 96 inputs. @@ -155,6 +164,28 @@ class RECV(opcua_device): dtype=((numpy.float,),), max_dim_x=3, max_dim_y=96, fget=lambda self: numpy.array(self.HBAT_reference_itrf).reshape(96,3)) + # ---------- + # Summarising Attributes + # ---------- + RCU_LED_colour_R = attribute(dtype=(numpy.uint32,), max_dim_x=32) + + def read_RCU_LED_colour_R(self): + return (2 * self.proxy.RCU_LED_green_on_R + 4 * self.proxy.RCU_LED_red_on_R).astype(numpy.uint32) + + RCU_error_R = attribute(dtype=(bool,), max_dim_x=32) + ANT_error_R = attribute(dtype=((bool,),), max_dim_x=3, max_dim_y=32) + + def read_RCU_error_R(self): + return self.proxy.RCU_mask_RW & ( + (self.proxy.RECVTR_I2C_error_R > 0) + | self.alarm_val("RCU_PCB_ID_R") + ) + + def read_ANT_error_R(self): + return self.proxy.ANT_mask_RW & ( + ~self.proxy.RCU_ADC_locked_R + ) + # -------- # overloaded functions # -------- @@ -248,68 +279,9 @@ class RECV(opcua_device): self.RCU_on() self.wait_attribute("RECVTR_translator_busy_R", False, 5) - def read_RCU_status_R(self): - """ Returns a set of strings denoting the status of each RCU. - - An empty string means no problems were detected. A non-empty - string means the RCU seems unusable. - - This function can be used as input to modify the RCU_mask_RW. """ - - rcu_mask = self.proxy.RCU_mask_RW - i2c_errors = self.proxy.RCU_I2C_STATUS_R - - nr_rcus = len(rcu_mask) - rcu_status = [""] * nr_rcus - - # construct status of each RCU - for rcu in range(nr_rcus): - status = [] - - if not i2c_status[rcu]: - status.append("[I2C error]") - - rcu_status[rcu] = " ".join(status) - - return rcu_status - - def read_ANT_status_R(self): - """ Returns a set of strings denoting the status of each antenna. - - An empty string means no problems were detected. A non-empty - string means the antenna seems unusable. - - This function can be used as input to modify the Ant_mask_RW. """ - - ant_mask = self.proxy.ANT_mask_RW - rcu_mask = self.proxy.RCU_mask_RW - adc_lock = self.proxy.RCU_ADC_locked_R - i2c_errors = self.proxy.RCU_I2C_STATUS_R - - nr_rcus = len(ant_mask) - nr_ants_per_rcu = len(ant_mask[0]) - - # Collect status, join them into a single string per antenna later - ant_status = [""] * nr_ants - - - for rcu in range(nr_rcus): - for ant in range(nr_ants_per_rcu): - status = [] - - if i2c_status[rcu] != 0: - status.append("[I2C error]") - - if not rcu_mask[rcu]: - status.append("[RCU masked out]") - - if not adc_lock[rcu][ant]: - status.append("[ADC lock error]") - - ant_status[rcu][ant] = " ".join(status) - - return ant_status - + # Turn off DTH by default + recv.RCU_DTH_off() + self.wait_attribute("RECVTR_translator_busy_R", False, 5) # ---------- # Run server diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py index f9e91e5e5..275d8fe8a 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -12,7 +12,7 @@ """ # PyTango imports -from tango.server import device_property +from tango.server import device_property, attribute from tango import AttrWriteType # Additional import @@ -205,7 +205,22 @@ class SDP(opcua_device): FPGA_bf_weights_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_R"], datatype=numpy.int16, dims=(A_pn * N_beamlets_ctrl, N_pn)) FPGA_bf_weights_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_RW"], datatype=numpy.int16, dims=(A_pn * N_beamlets_ctrl, N_pn), access=AttrWriteType.READ_WRITE) - + # ---------- + # Summarising Attributes + # ---------- + FPGA_error_R = attribute(dtype=(bool,), max_dim_x=16) + FPGA_processing_error_R = attribute(dtype=(bool,), max_dim_x=16) + + def read_FPGA_error_R(self): + return self.proxy.TR_fpga_mask_RW & ( + self.proxy.TR_fpga_communication_error_R + ) + + def read_FPGA_processing_error_R(self): + return self.proxy.TR_fpga_mask_RW & ( + ~self.proxy.FPGA_processing_enable_R + | ~self.proxy.FPGA_wg_enable_R.any(axis=1) + ) # -------- # overloaded functions diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py index cbad7fad1..de7f73658 100644 --- a/tangostationcontrol/tangostationcontrol/devices/unb2.py +++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py @@ -12,8 +12,7 @@ """ # PyTango imports -from tango.server import command -from tango.server import device_property +from tango.server import command, attribute, device_property from tango import AttrWriteType, DebugIt, DevState # Additional import @@ -39,6 +38,12 @@ class UNB2(opcua_device): default_value=[True] * 2 ) + UNB2TR_monitor_rate_RW_default = device_property( + dtype='DevLong64', + mandatory=False, + default_value=1 + ) + # ---------- # Attributes # ---------- @@ -114,6 +119,30 @@ class UNB2(opcua_device): UNB2_POL_SWITCH_PHY_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_POL_SWITCH_PHY_VOUT_R"],datatype=numpy.float64, dims=(2,)) UNB2_PWR_on_R = attribute_wrapper(comms_annotation=["UNB2_PWR_on_R" ],datatype=numpy.bool_ , dims=(2,)) + # ---------- + # Summarising Attributes + # ---------- + UNB2_error_R = attribute(dtype=(bool,), max_dim_x=2) + UNB2_FPGA_error_R = attribute(dtype=((bool,),), max_dim_x=4, max_dim_y=2) + UNB2_QSFP_error_R = attribute(dtype=((bool,),), max_dim_x=24, max_dim_y=2) + + def read_UNB2_error_R(self): + return self.proxy.UNB2_mask_RW & ( + (self.proxy.UNB2TR_I2C_bus_error_R > 0) + | self.alarm_val("UNB2_PCB_ID_R") + ) + + def read_UNB2_FPGA_error_R(self): + return self.proxy.UNB2_mask_RW & ( + (self.proxy.UNB2TR_I2C_bus_DDR4_error_R > 0) + | (self.proxy.UNB2TR_I2C_bus_FPGA_PS_error_R > 0) + ) + + def read_UNB2_QSFP_error_R(self): + return self.proxy.UNB2_mask_RW & ( + (self.proxy.UNB2TR_I2C_bus_QSFP_error_R > 0) + ) + # -------- # overloaded functions # -------- @@ -142,6 +171,15 @@ class UNB2(opcua_device): """ self.opcua_connection.call_method(["UNB2_on"]) + def _initialise_hardware(self): + """ Initialise the UNB2 hardware. """ + + # Cycle UNB2s + self.UNB2_off() + self.wait_attribute("UNB2TR_translator_busy_R", False, 5) + self.UNB2_on() + self.wait_attribute("UNB2TR_translator_busy_R", False, 5) + # ---------- # Run server # ---------- diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_boot.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_boot.py index 6d93080cc..d515bb29e 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_boot.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_boot.py @@ -9,21 +9,26 @@ import time -from .base import AbstractTestBases +from tango import DevState +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.integration_test import base -class TestDeviceBoot(AbstractTestBases.TestDeviceBase): + +class TestDeviceBoot(base.IntegrationTestCase): def setUp(self): - super().setUp("STAT/Boot/1") + self.proxy = TestDeviceProxy("STAT/Boot/1") + + def test_start_in_on(self): + """Test whether we start in the ON state""" - def test_device_boot_initialise_station(self): - """Test if we can initialise the station""" + self.assertEqual(DevState.ON, self.proxy.state()) - self.proxy.initialise() - self.proxy.on() + def test_reboot(self): + """Test if we can reinitialise the station""" - self.proxy.boot() + self.proxy.reboot() # wait for a few seconds for the station to initialise timeout = 10 -- GitLab