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

L2SS-391: Added StationControl device to control the lifecycle of the station...

L2SS-391: Added StationControl device to control the lifecycle of the station (and potentially other top-level functionality)
parent 09058976
No related branches found
No related tags found
1 merge request!144L2SS-391: Add boot device
...@@ -14,6 +14,13 @@ ...@@ -14,6 +14,13 @@
} }
} }
}, },
"station_control": {
"LTS": {
"StationControl": {
"LTS/StationControl/1": {}
}
}
},
"RECV": { "RECV": {
"LTS": { "LTS": {
"RECV": { "RECV": {
......
# -*- coding: utf-8 -*-
#
# This file is part of the RECV project
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
""" StationControl Device Server for LOFAR2.0
"""
# TODO(Corne): Remove sys.path.append hack once packaging is in place!
import os, sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
# PyTango imports
from tango import DebugIt
from tango.server import run, command
from tango.server import device_property, attribute
from tango import AttrWriteType, DeviceProxy, DevState
# Additional import
from device_decorators import *
from clients.attribute_wrapper import attribute_wrapper
from devices.hardware_device import hardware_device
from common.lofar_logging import device_logging_to_python, log_exceptions
from common.lofar_git import get_version
__all__ = ["StationControl", "main"]
class InitialisationException(Exception):
pass
@device_logging_to_python()
class StationControl(hardware_device):
"""
**Properties:**
- Device Property
OPC_Server_Name
- Type:'DevString'
OPC_Server_Port
- Type:'DevULong'
OPC_Time_Out
- Type:'DevDouble'
"""
# -----------------
# Device Properties
# -----------------
DeviceProxy_Time_Out = device_property(
dtype='DevDouble',
mandatory=False,
default=3.0,
)
# 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=True,
)
# ----------
# Attributes
# ----------
version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version())
initialising_station_R = attribute(dtype=numpy.bool_, access=AttrWriteType.READ, fget=lambda self: self.initialising_station)
initialisation_progress_R = attribute(dtype=numpy.int, access=AttrWriteType.READ, fget=lambda self: numpy.int(self.initialisation_progress))
@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).
"""
self.debug_stream("Shutting down...")
self.Off()
self.debug_stream("Shut down. Good bye.")
# --------
# overloaded functions
# --------
@log_exceptions()
def configure_for_off(self):
""" user code here. is called when the state is set to OFF """
# Stop keep-alive
try:
pass
except Exception as e:
self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
@log_exceptions()
def configure_for_initialise(self):
# all devices we're controlling
self.devices = {
"recv": DeviceProxy("LTS/RECV/1"),
"unb2": DeviceProxy("LTS/UNB2/1"),
"sdp": DeviceProxy("LTS/SDP/1"),
"sst": DeviceProxy("LTS/SST/1"),
"xst": DeviceProxy("LTS/XST/1"),
"docker": DeviceProxy("LTS/Docker/1"),
}
# restart these before any others, and in this order
self.restart_first = [
"docker", # needed to do a deep restart of devices in case of malfunction
"sdp", # we need the TR_fpga_mask to be set before starting the statistics devices
]
# set the timeout for all deviceproxies
for device in self.devices:
device.set_timeout_millis(self.DeviceProxy_Time_Out * 1000)
# setup initial state
self.initialising_station = False
self.initialisation_progress = 0
@command()
@DebugIt()
@only_when_on()
@fault_on_error()
def initialise_devices(self):
"""
Initialise or re-initialise all devices on the station.
This command will take a while to execute, so should be called asynchronously.
:return:None
"""
try:
# mark us as busy
self.set_state(DevState.RUNNING)
# reset initialisation parameters
self.initialising_station = True
self.initialisation_progress = 0
num_restarted_devices = 0
# determine initialisation order
devices_ordered = self.restart_first + [d for d in self.devices.keys() if d not in self.restart_first]
# First, stop all devices, to get a well defined state
for device in devices_ordered:
if self.is_available(device) or not self.Ignore_Unavailable_Devices:
self.stop_device(device)
# restart devices in order
for device in devices_ordered:
if self.is_available(device) or not self.Ignore_Unavailable_Devices:
self.start_device(device)
num_restarted_devices += 1
self.initialisation_progress = 100.0 * num_restarted_devices / len(self.devices)
# make sure we always finish at 100% in case of success
self.initialisation_progress = 100
except InitialisationException as e:
logger.log_exception("Error initialising station")
# Just because they go to FAULT, doesn't mean we have to.
# Note that the user can query the state of the devices from the devices themselves.
# The condition (initialisation_progress < 100 and not initialising_station)
# will be an indicator initialisation went wrong.
finally:
self.initialising_station = False
# revert to ON, which is the state we were called in. if an Exception happened by now,
# we'll go to FAULT. Both are guaranteed by our decorators.
self.set_state(DevState.ON)
def is_available(self, device_name: str):
""" Return whether the device 'device_name' is actually available on this server. """
proxy = self.devices[device_name]
try:
proxy.state()
except Exception as e:
return False
return True
@command()
@DebugIt()
@only_when_on()
def stop_device(self, device_name: str):
""" Stop device 'device_name'. """
if proxy.state() != DevState.OFF:
self.set_status(f"[restarting {device_name}] Turning off device.")
proxy.Off()
if proxy.state() != DevState.OFF:
raise InitialisationException(f"Could not turn off device {device_name}")
@command()
@DebugIt()
@only_when_on()
def start_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_name)
# setup connections to hardware
self.set_status(f"[restarting {device_name}] Initialising device.")
proxy.Initialise()
if proxy.state() != DevState.INIT:
raise InitialisationException(f"Could not initialise device {device_name}")
# configure the device
try:
self.set_status(f"[restarting {device_name}] Setting defaults.")
proxy.set_defaults()
self.set_status(f"[restarting {device_name}] Initialising hardware.")
proxy.initialise_hardware()
except Exception as e:
raise InitialisationException(f"Could not configure device {device_name}") from e
# 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}")
self.set_status(f"[restarting {device_name}] Succesfully restarted.")
# ----------
# Run server
# ----------
def main(args=None, **kwargs):
"""Main function of the RECV module."""
from common.lofar_logging import configure_logger
configure_logger()
return run((StationControl,), args=args, **kwargs)
if __name__ == '__main__':
main()
#
# Docker compose file that launches a LOFAR2.0 station's
# ObservationControl device. It also runs the dynamically
# created Observation devices.
#
# Defines:
# - device-observation_control: LOFAR2.0 station ObvservationControl
#
# Requires:
# - lofar-device-base.yml
#
version: '2'
services:
device-station_control:
image: device-station_control
# build explicitly, as docker-compose does not understand a local image
# being shared among services.
build:
context: lofar-device-base
args:
SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
container_name: ${CONTAINER_NAME_PREFIX}device-station_control
networks:
- control
ports:
- "5708:5708" # unique port for this DS
volumes:
- ${TANGO_LOFAR_CONTAINER_MOUNT}
environment:
- TANGO_HOST=${TANGO_HOST}
entrypoint:
- /usr/local/bin/wait-for-it.sh
- ${TANGO_HOST}
- --timeout=30
- --strict
- --
# configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
# can't know about our Docker port forwarding
- python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/station_control.py LTS -v -ORBendPoint giop:tcp:0:5708 -ORBendPointPublish giop:tcp:${HOSTNAME}:5708
restart: on-failure
...@@ -4,6 +4,7 @@ sdp = DeviceProxy("LTS/SDP/1") ...@@ -4,6 +4,7 @@ sdp = DeviceProxy("LTS/SDP/1")
sst = DeviceProxy("LTS/SST/1") sst = DeviceProxy("LTS/SST/1")
xst = DeviceProxy("LTS/XST/1") xst = DeviceProxy("LTS/XST/1")
unb2 = DeviceProxy("LTS/UNB2/1") unb2 = DeviceProxy("LTS/UNB2/1")
sc = DeviceProxy("LTS/StationControl/1")
# Put them in a list in case one wants to iterate # Put them in a list in case one wants to iterate
devices = [recv, sdp, sst, xst, unb2] devices = [recv, sdp, sst, xst, unb2, sc]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment