Skip to content
Snippets Groups Projects
Commit 7fa05349 authored by Jan David Mol's avatar Jan David Mol
Browse files

Merge branch 'minor-improvements' into 'master'

L2SS-661: Read all attributes and fix bugs

See merge request !254
parents 3bc4d9f9 c34b2b82
Branches
Tags
1 merge request!254L2SS-661: Read all attributes and fix bugs
Showing
with 150 additions and 85 deletions
......@@ -79,12 +79,12 @@ class APSCT(opcua_device):
def read_APSCT_error_R(self):
return ((self.proxy.APSCTTR_I2C_error_R > 0)
| self.alarm_val("APSCT_PCB_ID_R")
| ~self.proxy.APSCT_INPUT_10MHz_good_R
| (~self.proxy.APSCT_INPUT_PPS_good_R & ~self.proxy.ASPCT_PPS_ignore_R)
| (~self.proxy.APSCT_PLL_160MHz_locked_R & ~self.proxy.APSCT_PLL_200MHz_locked_R)
| (self.proxy.APSCT_PLL_200MHz_locked_R & self.proxy.APSCT_PLL_200MHz_error_R)
| (self.proxy.APSCT_PLL_160MHz_locked_R & self.proxy.APSCT_PLL_160MHz_error_R)
or self.alarm_val("APSCT_PCB_ID_R")
or (not self.proxy.APSCT_INPUT_10MHz_good_R)
or (not self.proxy.APSCT_INPUT_PPS_good_R and not self.proxy.APSCT_PPS_ignore_R)
or (not self.proxy.APSCT_PLL_160MHz_locked_R and not self.proxy.APSCT_PLL_200MHz_locked_R)
or (self.proxy.APSCT_PLL_200MHz_locked_R and self.proxy.APSCT_PLL_200MHz_error_R)
or (self.proxy.APSCT_PLL_160MHz_locked_R and self.proxy.APSCT_PLL_160MHz_error_R)
)
APSCT_TEMP_error_R = attribute(dtype=bool)
......@@ -95,13 +95,13 @@ class APSCT(opcua_device):
def read_APSCT_VOUT_error_R(self):
return ( self.alarm_val("APSCT_PWR_PPSDIST_3V3_R")
| self.alarm_val("APSCT_PWR_CLKDIST1_3V3_R")
| self.alarm_val("APSCT_PWR_CLKDIST2_3V3_R")
| self.alarm_val("APSCT_PWR_CTRL_3V3_R")
| self.alarm_val("APSCT_PWR_INPUT_3V3_R")
| (self.proxy.APSCT_PWR_PLL_160MHz_on_R & self.alarm_val("APSCT_PWR_PLL_160MHz_3V3_R"))
| (self.proxy.APSCT_PWR_PLL_200MHz_on_R & self.alarm_val("APSCT_PWR_PLL_200MHz_3V3_R"))
| ~self.proxy.APSCT_PWR_on_R
or self.alarm_val("APSCT_PWR_CLKDIST1_3V3_R")
or self.alarm_val("APSCT_PWR_CLKDIST2_3V3_R")
or self.alarm_val("APSCT_PWR_CTRL_3V3_R")
or self.alarm_val("APSCT_PWR_INPUT_3V3_R")
or (self.proxy.APSCT_PWR_PLL_160MHz_on_R and self.alarm_val("APSCT_PWR_PLL_160MHz_3V3_R"))
or (self.proxy.APSCT_PWR_PLL_200MHz_on_R and self.alarm_val("APSCT_PWR_PLL_200MHz_3V3_R"))
or (not self.proxy.APSCT_PWR_on_R)
)
# --------
......
......@@ -67,10 +67,10 @@ class APSPU(opcua_device):
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"))
or self.alarm_val("APSPU_PCB_ID_R")
or self.alarm_val("APSPU_FAN1_RPM_R")
or self.alarm_val("APSPU_FAN2_RPM_R")
or self.alarm_val("APSPU_FAN3_RPM_R"))
APSPU_IOUT_error_R = attribute(dtype=bool)
APSPU_TEMP_error_R = attribute(dtype=bool)
......@@ -78,20 +78,20 @@ class APSPU(opcua_device):
def read_APSPU_IOUT_error_R(self):
return ( self.alarm_val("APSPU_LBA_IOUT_R")
| self.alarm_val("APSPU_RCU2A_IOUT_R")
| self.alarm_val("APSPU_RCU2D_IOUT_R")
or self.alarm_val("APSPU_RCU2A_IOUT_R")
or self.alarm_val("APSPU_RCU2D_IOUT_R")
)
def read_APSPU_TEMP_error_R(self):
return ( self.alarm_val("APSPU_LBA_TEMP_R")
| self.alarm_val("APSPU_RCU2A_TEMP_R")
| self.alarm_val("APSPU_RCU2D_TEMP_R")
or self.alarm_val("APSPU_RCU2A_TEMP_R")
or self.alarm_val("APSPU_RCU2D_TEMP_R")
)
def read_APSPU_VOUT_error_R(self):
return ( self.alarm_val("APSPU_LBA_VOUT_R")
| self.alarm_val("APSPU_RCU2A_VOUT_R")
| self.alarm_val("APSPU_RCU2D_VOUT_R")
or self.alarm_val("APSPU_RCU2A_VOUT_R")
or self.alarm_val("APSPU_RCU2D_VOUT_R")
)
# --------
......
......@@ -12,7 +12,7 @@ import datetime
from json import loads
from tango.server import attribute, command, device_property
from tango import AttrWriteType, DebugIt, DevState, DeviceProxy, DevVarStringArray, DevVarDoubleArray, DevString
from tango import AttrWriteType, DebugIt, DevState, DeviceProxy, DevVarStringArray, DevVarDoubleArray, DevString, DevSource
from threading import Thread, Lock, Condition
# Additional import
......@@ -68,10 +68,12 @@ class Beam(lofar_device):
fget=lambda self: self._hbat_pointing_timestamp_r)
HBAT_tracking_enabled_R = attribute(access=AttrWriteType.READ,
doc="Whether the HBAT tile beam is updated periodically",
dtype=numpy.bool,
fget=lambda self: self.HBAT_beam_tracker.is_alive())
HBAT_tracking_enabled_RW = attribute(access=AttrWriteType.READ_WRITE,
doc="Whether the HBAT tile beam should be updated periodically",
dtype=numpy.bool,
fget=lambda self: self._hbat_tracking_enabled_rw)
......@@ -99,6 +101,7 @@ class Beam(lofar_device):
# Set a reference of RECV device
self.recv_proxy = DeviceProxy("STAT/RECV/1")
self.recv_proxy.set_source(DevSource.DEV)
# Retrieve positions from RECV device
HBAT_reference_itrf = self.recv_proxy.HBAT_reference_itrf_R
......@@ -121,7 +124,7 @@ class Beam(lofar_device):
if self._hbat_tracking_enabled_rw:
self.HBAT_beam_tracker.start()
@log_exceptions
@log_exceptions()
def configure_for_off(self):
# Stop thread object
self.HBAT_beam_tracker.stop()
......@@ -186,7 +189,7 @@ class Beam(lofar_device):
# Record where we now point to, now that we've updated the weights.
# Only the entries within the mask have been updated
mask = self.recv_proxy.Ant_mask_RW.flatten()
mask = self.recv_proxy.ANT_mask_RW.flatten()
for rcu in range(96):
if mask[rcu]:
self._hbat_pointing_direction_r[rcu] = pointing_direction[rcu]
......@@ -303,6 +306,9 @@ class BeamTracker():
self.update_lock = Lock()
self.update_condition = Condition(self.update_lock)
# Whether the pointing has to be forced updated
self.stale_pointing = True
def start(self):
""" Starts the Beam Tracking thread """
if self.thread:
......@@ -313,6 +319,8 @@ class BeamTracker():
self.thread = Thread(target=self._update_HBAT_pointing_direction, name=f"BeamTracker of {self.device.get_name()}")
self.thread.start()
logger.info("BeamTracking thread started")
def is_alive(self):
""" Returns True just before the Thread run() method starts until just after the Thread run() method terminates. """
return self.thread and self.thread.is_alive()
......@@ -320,6 +328,10 @@ class BeamTracker():
def force_update(self):
""" Force the pointing to be updated. """
self.stale_pointing = True
self.unlock_thread()
def unlock_thread(self):
# inform the thread to stop waiting
with self.update_lock:
self.update_condition.notify()
......@@ -330,6 +342,8 @@ class BeamTracker():
if not self.thread:
return
logger.info("BeamTracking thread stopping")
self.done = True
self.force_update()
......@@ -341,6 +355,8 @@ class BeamTracker():
self.thread = None
logger.info("BeamTracking thread stopped")
def _get_sleep_time(self):
""" Computes the sleep time (in seconds) that needs to be waited for the next beam tracking update """
now = datetime.datetime.now().timestamp()
......@@ -356,15 +372,21 @@ class BeamTracker():
else:
return sleep_time
# @fault_on_error routes errors here. we forward them to our device
def Fault(self, msg):
self.device.Fault(msg)
@log_exceptions()
@fault_on_error()
def _update_HBAT_pointing_direction(self):
""" Updates the beam weights using a fixed interval of time """
# Check if flag beamtracking is true
with self.update_lock:
while not self.done:
# TODO: Occasionally this still gets called when the beam device is in OFF?
self.device.HBAT_set_pointing(numpy.array(self.device.proxy.HBAT_pointing_direction_RW).flatten())
self.stale_pointing = False
self.device._HBAT_set_pointing(self.device._hbat_pointing_direction_rw, datetime.datetime.now())
# sleep until the next update, or when interrupted (this releases the lock, allowing for notification)
self.update_condition.wait(self._get_sleep_time())
# note that we need wait_for as conditions can be triggered multiple times in succession
self.update_condition.wait_for(lambda: self.done or self.stale_pointing, self._get_sleep_time())
......@@ -430,5 +430,8 @@ class lofar_device(Device, metaclass=DeviceMeta):
alarm_state |= value <= value.dtype.type(alarms.min_alarm)
# return alarm state, with the same dimensions as the attribute
if is_scalar:
return alarm_state.item()
else:
return alarm_state
......@@ -196,9 +196,9 @@ class RECV(opcua_device):
RECV_VOUT_error_R = attribute(dtype=(bool,), max_dim_x=32)
def read_RECV_IOUT_error_R(self):
return self.proxy.ANT_mask_RW & (
return (self.proxy.ANT_mask_RW & (
self.alarm_val("RCU_PWR_ANT_IOUT_R")
).any(axis=1)
)).any(axis=1)
def read_RECV_TEMP_error_R(self):
# Don't apply the mask here --- we always want to know if things get too hot!
......@@ -210,13 +210,13 @@ class RECV(opcua_device):
return (self.proxy.ANT_mask_RW & (
self.alarm_val("RCU_PWR_ANT_VIN_R")
| self.alarm_val("RCU_PWR_ANT_VOUT_R")
).any(axis=1) | (self.proxy.RCU_mask_RW & (
)).any(axis=1) | (self.proxy.RCU_mask_RW & (
self.alarm_val("RCU_PWR_1V8_R")
| self.alarm_val("RCU_PWR_2V5_R")
| self.alarm_val("RCU_PWR_3V3_R")
| ~self.proxy.RCU_PWR_DIGITAL_on_R
| ~self.proxy.RCU_PWR_good_R
)))
))
# --------
# overloaded functions
......
......@@ -129,8 +129,6 @@ class SDP(opcua_device):
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_temp_R = attribute_wrapper(comms_annotation=["FPGA_temp_R"], datatype=numpy.float_, dims=(16,))
FPGA_weights_R = attribute_wrapper(comms_annotation=["FPGA_weights_R"], datatype=numpy.int16, dims=(12 * 488 * 2, 16))
FPGA_weights_RW = attribute_wrapper(comms_annotation=["FPGA_weights_RW"], datatype=numpy.int16, dims=(12 * 488 * 2, 16), access=AttrWriteType.READ_WRITE)
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)
FPGA_wg_enable_R = attribute_wrapper(comms_annotation=["FPGA_wg_enable_R"], datatype=numpy.bool_, dims=(12, 16))
......@@ -211,11 +209,12 @@ class SDP(opcua_device):
# ----------
FPGA_error_R = attribute(dtype=(bool,), max_dim_x=16)
FPGA_processing_error_R = attribute(dtype=(bool,), max_dim_x=16)
FPGA_input_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
| self.proxy.FPGA_firmware_version_R != ""
| (self.proxy.FPGA_firmware_version_R != "")
| (self.proxy.FPGA_jesd204b_csr_dev_syncn_R == 0).any(axis=1)
)
......@@ -223,8 +222,12 @@ class SDP(opcua_device):
return self.proxy.TR_fpga_mask_RW & (
~self.proxy.FPGA_processing_enable_R
| (self.proxy.FPGA_boot_image_R == 0)
| self.proxy.FPGA_wg_enable_R.any(axis=1)
| (self.proxy.FPGA_signal_input_rms_R == 0)
)
def read_FPGA_input_error_R(self):
return self.proxy.TR_fpga_mask_RW & (
self.proxy.FPGA_wg_enable_R.any(axis=1)
| (self.proxy.FPGA_signal_input_rms_R == 0).any(axis=1)
)
# --------
......
......@@ -13,7 +13,7 @@
# PyTango imports
from tango.server import device_property
from tango import DeviceProxy
from tango import DeviceProxy, DevSource
# Additional import
import asyncio
......@@ -126,6 +126,7 @@ class Statistics(opcua_device):
# proxy the SDP device in case we need the FPGA mask
self.sdp_proxy = DeviceProxy("STAT/SDP/1")
self.sdp_proxy.set_source(DevSource.DEV)
async def _connect_statistics(self):
# map an access helper class
......
......@@ -246,7 +246,7 @@ class XSTCollector(StatisticsCollector):
xst_blocks = self.parameters["xst_blocks"]
xst_conjugated = self.parameters["xst_conjugated"]
for subband_index in subband_indices:
for matrix_idx, subband_index in enumerate(subband_indices):
for block_index in range(self.MAX_BLOCKS):
# convert real/imag int to complex float values. this works as real/imag come in pairs
block = xst_blocks[subband_index][block_index].astype(numpy.float32).view(numpy.complex64)
......@@ -263,7 +263,7 @@ class XSTCollector(StatisticsCollector):
first_baseline = (first_baseline[0] * self.BLOCK_LENGTH, first_baseline[1] * self.BLOCK_LENGTH)
# copy block into matrix
matrix[subband_index][first_baseline[0]:first_baseline[0]+self.BLOCK_LENGTH, first_baseline[1]:first_baseline[1]+self.BLOCK_LENGTH] = block
matrix[matrix_idx][first_baseline[0]:first_baseline[0]+self.BLOCK_LENGTH, first_baseline[1]:first_baseline[1]+self.BLOCK_LENGTH] = block
return matrix
......
......@@ -61,9 +61,30 @@ class AbstractTestBases:
self.self.assertListEqual([], self.proxy.opcua_missing_attributes_R)
def test_device_on(self):
"""Test if we can transition to on"""
"""Test if we can transition off -> standby -> on"""
self.proxy.initialise()
self.proxy.on()
self.assertEqual(DevState.ON, self.proxy.state())
def test_device_warm_boot(self):
"""Test if we can transition off -> on using a warm boot"""
self.proxy.warm_boot()
self.assertEqual(DevState.ON, self.proxy.state())
def test_device_read_all_attributes(self):
"""Test if we can read all of the exposed attributes in the ON state.
This test covers the reading logic of all attributes. """
self.proxy.initialise()
self.proxy.on()
for attribute_name in self.proxy.get_attribute_list():
try:
_ = self.proxy.read_attribute(attribute_name).value
except Exception as e:
raise AssertionError(f"Could not read attribute {attribute_name} from device {self.name}") from e
......@@ -12,7 +12,6 @@ import numpy
import datetime
import json
from tango import DevState
from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
from .base import AbstractTestBases
......@@ -36,11 +35,8 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
# setup RECV
recv_proxy = TestDeviceProxy("STAT/RECV/1")
recv_proxy.off()
recv_proxy.initialise()
self.assertEqual(DevState.STANDBY, recv_proxy.state())
recv_proxy.warm_boot()
recv_proxy.set_defaults()
recv_proxy.on()
self.assertEqual(DevState.ON, recv_proxy.state())
return recv_proxy
def test_HBAT_delays_dims(self):
......@@ -48,12 +44,7 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
self.setup_recv_proxy()
# setup BEAM
self.proxy.init()
self.proxy.Initialise()
self.assertEqual(DevState.STANDBY, self.proxy.state())
self.proxy.set_defaults()
self.proxy.on()
self.assertEqual(DevState.ON, self.proxy.state())
self.proxy.warm_boot()
# verify HBAT_delays method returns the correct dimensions
HBAT_delays = self.proxy.HBAT_delays(self.pointing_direction)
......@@ -64,13 +55,8 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
recv_proxy = self.setup_recv_proxy()
# setup BEAM
self.proxy.init()
self.proxy.Initialise()
self.assertEqual(DevState.STANDBY, self.proxy.state())
self.proxy.set_defaults()
self.proxy.warm_boot()
self.proxy.HBAT_tracking_enabled_RW = False
self.proxy.on()
self.assertEqual(DevState.ON, self.proxy.state())
# Verify attribute is present (all zeros if never used before)
HBAT_delays_r1 = numpy.array(recv_proxy.read_attribute('HBAT_BF_delay_steps_RW').value)
......@@ -90,9 +76,8 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
# setup RECV as well
recv_proxy = self.setup_recv_proxy()
self.proxy.initialise()
self.proxy.warm_boot()
self.proxy.HBAT_tracking_enabled_RW = False
self.proxy.on()
# Point to Zenith
self.proxy.HBAT_set_pointing(numpy.array([["AZELGEO","0deg","90deg"]] * 96).flatten())
......@@ -107,9 +92,8 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
# setup RECV as well
recv_proxy = self.setup_recv_proxy()
self.proxy.initialise()
self.proxy.warm_boot()
self.proxy.HBAT_tracking_enabled_RW = False
self.proxy.on()
# point at north on the horizon
self.proxy.HBAT_set_pointing(["AZELGEO","0deg","0deg"] * 96)
......@@ -135,9 +119,8 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
# setup RECV as well
recv_proxy = self.setup_recv_proxy()
self.proxy.initialise()
self.proxy.warm_boot()
self.proxy.HBAT_tracking_enabled_RW = False
self.proxy.on()
# Point to LOFAR 1 ref pointing (0.929342, 0.952579, J2000)
pointings = numpy.array([["J2000", "0.929342rad", "0.952579rad"]] * 96).flatten()
......@@ -167,8 +150,7 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
# setup RECV as well
recv_proxy = self.setup_recv_proxy()
self.proxy.initialise()
self.proxy.on()
self.proxy.warm_boot()
# check if we're really tracking
self.assertTrue(self.proxy.HBAT_tracking_enabled_R)
......@@ -177,9 +159,6 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase):
new_pointings = [("J2000",f"{tile}deg","0deg") for tile in range(96)]
self.proxy.HBAT_pointing_direction_RW = new_pointings
# wait for tracking thread to pick up and set. should be almost instant
time.sleep(0.5)
# check pointing
self.assertListEqual(new_pointings, list(self.proxy.HBAT_pointing_direction_R))
......@@ -19,7 +19,6 @@ class TestDeviceSDP(AbstractTestBases.TestDeviceBase):
def test_device_sdp_read_attribute(self):
"""Test if we can read an attribute obtained over OPC-UA"""
self.proxy.initialise()
self.proxy.on()
self.proxy.warm_boot()
self.assertListEqual([True]*16, list(self.proxy.TR_fpga_communication_error_R))
......@@ -12,6 +12,7 @@ import sys
import time
from tango._tango import DevState
from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
from .base import AbstractTestBases
......@@ -22,14 +23,24 @@ class TestDeviceSST(AbstractTestBases.TestDeviceBase):
"""Intentionally recreate the device object in each test"""
super().setUp("STAT/SST/1")
def test_device_read_all_attributes(self):
# We need to connect to SDP first to read some of our attributes
self.sdp_proxy = self.setup_sdp()
super().test_device_read_all_attributes()
def setup_sdp(self):
# setup SDP, on which this device depends
sdp_proxy = TestDeviceProxy("STAT/SDP/1")
sdp_proxy.off()
sdp_proxy.warm_boot()
sdp_proxy.set_defaults()
return sdp_proxy
def test_device_sst_send_udp(self):
port_property = {"Statistics_Client_TCP_Port": "4998"}
self.proxy.put_property(port_property)
self.proxy.initialise()
self.assertEqual(DevState.STANDBY, self.proxy.state())
self.proxy.on()
self.proxy.warm_boot()
self.assertEqual(DevState.ON, self.proxy.state())
......@@ -44,13 +55,7 @@ class TestDeviceSST(AbstractTestBases.TestDeviceBase):
def test_device_sst_connect_tcp_receive(self):
port_property = {"Statistics_Client_TCP_Port": "5101"}
self.proxy.put_property(port_property)
self.proxy.initialise()
self.assertEqual(DevState.STANDBY, self.proxy.state())
self.proxy.on()
self.assertEqual(DevState.ON, self.proxy.state())
self.proxy.warm_boot()
time.sleep(2)
......
# -*- coding: utf-8 -*-
#
# This file is part of the LOFAR 2.0 Station Software
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
from .base import AbstractTestBases
from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
class TestDeviceSST(AbstractTestBases.TestDeviceBase):
def setUp(self):
"""Intentionally recreate the device object in each test"""
super().setUp("STAT/XST/1")
def test_device_read_all_attributes(self):
# We need to connect to SDP first to read some of our attributes
self.sdp_proxy = self.setup_sdp()
super().test_device_read_all_attributes()
def setup_sdp(self):
# setup SDP, on which this device depends
sdp_proxy = TestDeviceProxy("STAT/SDP/1")
sdp_proxy.off()
sdp_proxy.warm_boot()
sdp_proxy.set_defaults()
return sdp_proxy
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment