Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • lofar2.0/tango
  • mckenna/tango
2 results
Show changes
Commits on Source (2)
......@@ -161,6 +161,9 @@ Next change the version in the following places:
# Release Notes
* 0.39.8 Allow station manager to use configurable state transition timeouts.
Use the `hibernate_transition_timeout_RW`, `standby_transition_timeout_RW`
and `on_transition_timeout_RW` attributes. The timeout is in seconds.
* 0.39.7 Fixed archiving of Antenna_Status and Antenna_Use
* 0.39.6 Fixed some multi-thread/multi-process race conditions
* 0.39.5 Remove stestr from integration test
......
0.39.7
0.39.8
......@@ -7,9 +7,8 @@
import asyncio
import logging
from tango import DebugIt
# pytango imports
import numpy
from tango import DebugIt, AttrWriteType
from tango.server import attribute, command, device_property
# Additional import
......@@ -62,6 +61,24 @@ class StationManager(AsyncDevice):
mandatory=False,
default_value=False,
)
Hibernate_Transition_Timeout = device_property(
doc="Timeout in seconds to transition to hibernate",
dtype="DevLong64",
mandatory=False,
default_value=60,
)
Standy_Transition_Timeout = device_property(
doc="Timeout in seconds to transition to standby",
dtype="DevLong64",
mandatory=False,
default_value=300,
)
On_Transition_Timeout = device_property(
doc="Timeout in seconds to transition to on",
dtype="DevLong64",
mandatory=False,
default_value=600,
)
# ----------
# Attributes
......@@ -94,6 +111,41 @@ class StationManager(AsyncDevice):
def last_requested_transition_ok_R(self):
return not self.last_requested_transition_exceptions
@attribute(
access=AttrWriteType.READ_WRITE,
dtype=numpy.int64,
)
def hibernate_transition_timeout_RW(self):
return self._hibernate_transition_timeout
@hibernate_transition_timeout_RW.write
def hibernate_transition_timeout_RW(self, value):
self._hibernate_transition_timeout = value
@attribute(
access=AttrWriteType.READ_WRITE,
dtype=numpy.int64,
min_value="0",
)
def standby_transition_timeout_RW(self):
return self._standby_transition_timeout
@standby_transition_timeout_RW.write
def standby_transition_timeout_RW(self, value):
self._standby_transition_timeout = value
@attribute(
access=AttrWriteType.READ_WRITE,
dtype=numpy.int64,
min_value="0",
)
def on_transition_timeout_RW(self):
return self._on_transition_timeout
@on_transition_timeout_RW.write
def on_transition_timeout_RW(self, value):
self._on_transition_timeout = value
# --------
# overloaded functions
# --------
......@@ -104,6 +156,10 @@ class StationManager(AsyncDevice):
self.last_requested_transition_exceptions = []
self.transition_lock = asyncio.Lock()
self._hibernate_transition_timeout = 0
self._standby_transition_timeout = 0
self._on_transition_timeout = 0
# Super must be called after variable assignment due to executing init_device!
super().__init__(cl, name)
......@@ -112,6 +168,10 @@ class StationManager(AsyncDevice):
# always turn on automatically, so the user doesn't have to boot the
# StationManager device.
self._hibernate_transition_timeout = self.Hibernate_Transition_Timeout
self._standby_transition_timeout = self.Standy_Transition_Timeout
self._on_transition_timeout = self.On_Transition_Timeout
"""Alternative method"""
# loop = asyncio.get_running_loop()
# loop.create_task(self.Initialise())
......@@ -164,7 +224,7 @@ class StationManager(AsyncDevice):
"""
await self.transition_lock.acquire()
try:
await self.station_state.station_off()
await self.station_state.station_off(0)
finally:
self.transition_lock.release()
......@@ -178,7 +238,9 @@ class StationManager(AsyncDevice):
"""
await self.transition_lock.acquire()
try:
await self.station_state.station_hibernate()
await self.station_state.station_hibernate(
timeout=self._hibernate_transition_timeout
)
finally:
self.transition_lock.release()
......@@ -192,7 +254,9 @@ class StationManager(AsyncDevice):
"""
await self.transition_lock.acquire()
try:
await self.station_state.station_standby()
await self.station_state.station_standby(
timeout=self._standby_transition_timeout
)
finally:
self.transition_lock.release()
......@@ -206,6 +270,6 @@ class StationManager(AsyncDevice):
"""
await self.transition_lock.acquire()
try:
await self.station_state.station_on()
await self.station_state.station_on(timeout=self._on_transition_timeout)
finally:
self.transition_lock.release()
......@@ -13,17 +13,16 @@ class HibernateState(StationState):
"""HIBERNATE station state"""
def __init__(self, station_manager, power_hierarchy) -> None:
self.timeout = self.get_timeout(StationStateEnum.HIBERNATE)
super().__init__(StationStateEnum.HIBERNATE, station_manager, power_hierarchy)
async def station_hibernate(self):
async def station_hibernate(self, timeout: int):
self.get_transition_current_state_error()
return
async def station_on(self):
async def station_on(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.ON)
async def station_off(self):
async def station_off(self, timeout: int):
"""Transition HIBERNATE -> OFF"""
from tangostationcontrol.states.off import OffState
......@@ -32,7 +31,7 @@ class HibernateState(StationState):
self._station_manager.requested_station_state = new_state.state.name
self._station_manager.set_station_state(new_state)
async def station_standby(self):
async def station_standby(self, timeout: int):
"""Transition HIBERNATE -> STANDBY"""
from tangostationcontrol.states.standby import StandbyState
......@@ -41,6 +40,7 @@ class HibernateState(StationState):
await self._transition(
target_state,
self._power_hierarchy.hibernate_to_standby,
timeout=timeout,
)
except DevFailed as exc:
self.generate_transition_error(target_state, logger, exc)
......
......@@ -16,26 +16,26 @@ class OffState(StationState):
def __init__(self, station_manager, power_hierarchy) -> None:
super().__init__(StationStateEnum.OFF, station_manager, power_hierarchy)
async def station_off(self):
async def station_off(self, timeout: int):
self.get_transition_current_state_error()
return
async def station_standby(self):
async def station_standby(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.STANDBY)
async def station_on(self):
async def station_on(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.ON)
async def station_hibernate(self) -> None:
async def station_hibernate(self, timeout: int) -> None:
"""Transition OFF -> HIBERNATE"""
from tangostationcontrol.states.hibernate import HibernateState
target_state = StationStateEnum.HIBERNATE
try:
await self._transition(
target_state,
self._power_hierarchy.off_to_hibernate,
target_state, self._power_hierarchy.off_to_hibernate, timeout=1
)
logger.error(self._power_hierarchy.off_to_hibernate)
except DevFailed as exc:
self.generate_transition_error(target_state, logger, exc)
self._station_manager.set_station_state(
......
......@@ -14,28 +14,26 @@ class OnState(StationState):
"""ON station state"""
def __init__(self, station_manager, power_hierarchy) -> None:
self.timeout = self.get_timeout(StationStateEnum.ON)
super().__init__(StationStateEnum.ON, station_manager, power_hierarchy)
async def station_off(self):
async def station_off(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.ON)
async def station_hibernate(self):
async def station_hibernate(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.HIBERNATE)
async def station_on(self):
async def station_on(self, timeout: int):
self.get_transition_current_state_error()
return
async def station_standby(self):
async def station_standby(self, timeout: int):
"""Transition ON -> STANDBY"""
from tangostationcontrol.states.standby import StandbyState
target_state = StationStateEnum.STANDBY
try:
await self._transition(
target_state,
self._power_hierarchy.on_to_standby,
target_state, self._power_hierarchy.on_to_standby, timeout=timeout
)
except DevFailed as exc:
self.generate_transition_error(target_state, logger, exc)
......
......@@ -13,17 +13,16 @@ class StandbyState(StationState):
"""STANDBY station state"""
def __init__(self, station_manager, power_hierarchy) -> None:
self.timeout = self.get_timeout(StationStateEnum.STANDBY)
super().__init__(StationStateEnum.STANDBY, station_manager, power_hierarchy)
async def station_off(self):
async def station_off(self, timeout: int):
return self.disallowed_transition_error(StationStateEnum.OFF)
async def station_standby(self):
async def station_standby(self, timeout: int):
self.get_transition_current_state_error()
return
async def station_hibernate(self):
async def station_hibernate(self, timeout: int):
"""Transition STANDBY -> HIBERNATE"""
from tangostationcontrol.states.hibernate import HibernateState
......@@ -32,6 +31,7 @@ class StandbyState(StationState):
await self._transition(
target_state,
self._power_hierarchy.standby_to_hibernate,
timeout=timeout,
)
except DevFailed as exc:
self.generate_transition_error(target_state, logger, exc)
......@@ -39,15 +39,14 @@ class StandbyState(StationState):
HibernateState(self._station_manager, self._power_hierarchy)
)
async def station_on(self):
async def station_on(self, timeout: int):
"""Transition STANDBY -> ON"""
from tangostationcontrol.states.on import OnState
target_state = StationStateEnum.ON
try:
await self._transition(
target_state,
self._power_hierarchy.standby_to_on,
target_state, self._power_hierarchy.standby_to_on, timeout=timeout
)
except DevFailed as exc:
self.generate_transition_error(target_state, logger, exc)
......
# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
# SPDX-License-Identifier: Apache-2.0
import asyncio
import logging
from abc import ABC, abstractmethod
......@@ -41,17 +42,8 @@ def run_if_device_on_in_station_state(
class StationState(ABC):
"""Abstract class for station state"""
TIMEOUT_DICT = {
StationStateEnum.HIBERNATE: 60.0,
StationStateEnum.STANDBY: 300.0,
StationStateEnum.ON: 600.0,
}
def __init__(
self,
state: StationStateEnum,
station_manager,
power_hierarchy,
self, state: StationStateEnum, station_manager, power_hierarchy
) -> None:
self._state = state
self._station_manager = station_manager
......@@ -67,11 +59,6 @@ class StationState(ABC):
def state(self, state: StationStateEnum) -> None:
self._state = state
@classmethod
def get_timeout(cls, state: StationStateEnum) -> float:
"""Return the timeout for a given station state"""
return cls.TIMEOUT_DICT[state]
def generate_transition_error(
self, target_state: StationStateEnum, log: logging.Logger, exc: DevFailed
):
......@@ -110,6 +97,7 @@ class StationState(ABC):
self,
target_state: StationStateEnum,
transition_func: Callable[[], Awaitable[None]],
timeout: int,
):
"""Transition to a station state using `transition_func`.
......@@ -135,8 +123,7 @@ class StationState(ABC):
self._station_manager.requested_station_state = target_state
self._station_manager.last_requested_transition_exceptions = (
await self.power_transition(
target_state,
transition_func,
target_state, transition_func, timeout=timeout
)
)
except Exception as ex:
......@@ -157,30 +144,31 @@ class StationState(ABC):
raise NotImplementedError
@abstractmethod
async def station_hibernate(self):
async def station_hibernate(self, timeout: int):
"""Abstract method for HIBERNATE transitions"""
raise NotImplementedError
@abstractmethod
async def station_standby(self):
async def station_standby(self, timeout: int):
"""Abstract method for STANDBY transitions"""
raise NotImplementedError
@abstractmethod
async def station_on(self):
async def station_on(self, timeout: int):
"""Abstract method for ON transitions"""
raise NotImplementedError
async def power_transition(self, target_state, pwr_func):
async def power_transition(self, target_state, pwr_func, timeout: int = 60):
"""Trigger the Power Hierarchy device to perform the transition
:param target_state : transition to be performed
:param pwr_func : power hierarchy function that implements the transition
:param timeout: Timeout in seconds
"""
try:
return await asyncio.wait_for(
asyncio.to_thread(pwr_func),
timeout=self.get_timeout(target_state),
timeout=timeout,
)
except asyncio.TimeoutError as exc:
raise TimeoutError(
......
......@@ -2,9 +2,15 @@
# SPDX-License-Identifier: Apache-2.0
import asyncio
import time
from timeout_decorator import timeout_decorator
from unittest import mock
from test.devices import device_base
from tango import DevState
from tango import DevState, DevFailed
from tango.test_context import DeviceTestContext
from tangostationcontrol.devices import station_manager
......@@ -25,6 +31,32 @@ class TestStationManagerDevice(device_base.DeviceTestCase):
self.assertEqual(proxy.state(), DevState.ON)
@staticmethod
def block():
time.sleep(30)
@staticmethod
def patched__initialise_power_hierarchy(self):
self.init(
self.get_name(), continue_on_failure=self.Suppress_State_Transition_Failures
)
self.off_to_hibernate = lambda: time.sleep(30)
@timeout_decorator.timeout(10)
def test_state_transition_timeout(self):
timeout = 1
with mock.patch.object(
station_manager.StationManager, "_initialise_power_hierarchy", autospec=True
) as m_off:
m_off.side_effect = self.patched__initialise_power_hierarchy
with DeviceTestContext(
station_manager.StationManager, process=False, timeout=60
) as proxy:
proxy.hibernate_transition_timeout_RW = timeout
self.assertEqual(timeout, proxy.hibernate_transition_timeout_RW)
self.assertRaises(DevFailed, proxy.station_hibernate)
def test_transitions_lock(self):
"""Test whether the lock mechanism ensure only one transition
at a time is executed"""
......