Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Joshc slac/reset ioc by tree #72

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion beams/tests/artifacts/egg_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
from apischema import serialize

from beams.tree_config.action import IncPVActionItem, SetPVActionItem
from beams.tree_config.base import BehaviorTreeItem, EPICSValue, FixedValue
from beams.tree_config.base import BehaviorTreeItem
from beams.tree_config.composite import SequenceItem
from beams.tree_config.condition import (BinaryConditionItem,
BoundedConditionItem,
ConditionOperator)
from beams.tree_config.idiom import CheckAndDoItem
from beams.tree_config.py_trees import (RunningItem, StatusQueueItem,
SuccessItem)
from beams.tree_config.value import EPICSValue, FixedValue


# egg 1
Expand Down
39 changes: 39 additions & 0 deletions beams/tests/mock_iocs/SysResetIOC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
from textwrap import dedent

from caproto.server import PVGroup, ioc_arg_parser, pvproperty, run


class MockSysResetIOC(PVGroup):
"""
IOC to mock SLACEPICS IOCs with respect to how they increment heartbeat and are reset
"""

heartbeat = pvproperty(
name="HEARTBEAT",
value=4000,
dtype=int,
doc="Represents heartbeat of IOC, monotonically increasing (until reset)")
sys_reset = pvproperty(
name="SysReset",
value=0,
dtype=int,
doc="Rising edge indicates requested reset, put high to request reset")

@sys_reset.putter
async def sys_reset(self, instance, value):
await self.heartbeat.write(0)
return 0

@heartbeat.scan(period=1.0)
async def heartbeat(self, instance, async_lib):
print(instance.value)
await instance.write(instance.value+1)


if __name__ == "__main__":
ioc_options, run_options = ioc_arg_parser(
default_prefix="SysResetTest:", desc=dedent(MockSysResetIOC.__doc__)
)
ioc = MockSysResetIOC(**ioc_options)
run(ioc.pvdb, **run_options)
2 changes: 1 addition & 1 deletion beams/tests/test_bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
from beams.bin.main import main
from beams.tests.conftest import cli_args, restore_logging
from beams.tree_config import save_tree_item_to_path
from beams.tree_config.base import EPICSValue
from beams.tree_config.composite import SequenceItem
from beams.tree_config.condition import BinaryConditionItem
from beams.tree_config.value import EPICSValue

logger = logging.getLogger(__name__)

Expand Down
3 changes: 2 additions & 1 deletion beams/tests/test_serialize.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from apischema import deserialize, serialize

from beams.tree_config.action import IncPVActionItem, SetPVActionItem
from beams.tree_config.base import BehaviorTreeItem, EPICSValue, FixedValue
from beams.tree_config.base import BehaviorTreeItem
from beams.tree_config.composite import SequenceItem
from beams.tree_config.condition import BinaryConditionItem, ConditionOperator
from beams.tree_config.idiom import CheckAndDoItem
from beams.tree_config.value import EPICSValue, FixedValue


def test_serialize_check_and_do():
Expand Down
31 changes: 31 additions & 0 deletions beams/tests/test_utility_trees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import time

import py_trees
from caproto.tests.conftest import run_example_ioc

from beams.tree_config.utility_trees.reset_ioc import ResetIOCItem


def test_sys_reset(request, bt_cleaner):
reset_ioc_tree = ResetIOCItem(
ioc_prefix="SysResetTest").get_tree()

bt_cleaner.register(reset_ioc_tree)

# start mock IOC # NOTE: assumes test is being run from top level of
run_example_ioc(
"beams.tests.mock_iocs.SysResetIOC",
request=request,
pv_to_check="SysResetTest:HEARTBEAT",
)

reset_ioc_tree.setup_with_descendants()
while reset_ioc_tree.status not in (
py_trees.common.Status.SUCCESS,
py_trees.common.Status.FAILURE,
):
for n in reset_ioc_tree.tick():
print(n)
time.sleep(0.01)

assert reset_ioc_tree.status == py_trees.common.Status.SUCCESS
34 changes: 0 additions & 34 deletions beams/tree_config/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import logging
from dataclasses import dataclass
from typing import Any

import py_trees
from epics import caget
from py_trees.behaviour import Behaviour

from beams.serialization import as_tagged_union
Expand Down Expand Up @@ -38,35 +36,3 @@ def get_tree(self) -> Behaviour:
# grab file
# de-serialize tree, return it
raise NotImplementedError


@as_tagged_union
@dataclass
class BaseValue:
def get_value(self) -> Any:
raise NotImplementedError


@dataclass
class FixedValue(BaseValue):
value: Any

def get_value(self) -> Any:
return self.value


@dataclass
class EPICSValue(BaseValue):
pv_name: str
as_string: bool = False

def get_value(self) -> Any:
value = caget(self.pv_name, as_string=self.as_string)
logger.debug(f" <<-- (EPICSValue): caget({self.pv_name}) -> {value}")
return value


@dataclass
class OphydTarget(BaseValue):
device_name: str
component_path: list[str]
10 changes: 8 additions & 2 deletions beams/tree_config/condition.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import logging
import operator
from dataclasses import dataclass, field
from enum import Enum

from beams.behavior_tree.condition_node import ConditionNode
from beams.serialization import as_tagged_union
from beams.tree_config.base import BaseItem, BaseValue, FixedValue
from beams.tree_config.base import BaseItem
from beams.tree_config.value import BaseValue, FixedValue
from beams.typing_helper import Evaluatable

logger = logging.getLogger(__name__)


@as_tagged_union
@dataclass
Expand Down Expand Up @@ -56,7 +60,9 @@ def cond_func():
# TODO: determine if we want to do NULL handling should we get a value but it is None type
rhs = self.right_value.get_value()

return op(lhs, rhs)
eval = op(lhs, rhs)
logger.debug(f"Evalling as lhs {lhs}, rhs {rhs}: {eval}")
return eval

return cond_func

Expand Down
1 change: 0 additions & 1 deletion beams/tree_config/idiom.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,3 @@ class UseCheckConditionItem(BaseConditionItem):

If used in any other context the tree will not be constructable.
"""
...
76 changes: 76 additions & 0 deletions beams/tree_config/utility_trees/reset_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import logging
from dataclasses import dataclass

import py_trees
from epics import caget
from py_trees.composites import Sequence

from beams.behavior_tree.action_node import ActionNode, wrapped_action_work
from beams.tree_config.action import SetPVActionItem
from beams.tree_config.base import BaseItem
from beams.tree_config.condition import BinaryConditionItem, ConditionOperator
from beams.tree_config.value import EPICSValue, ProcessIntValue
from beams.typing_helper import Evaluatable

logger = logging.getLogger(__name__)


@dataclass
class ResetIOCItem(BaseItem):
ioc_prefix: str = ""
# semi static member objects
HEARTBEAT_POSTFIX: str = ":HEARTBEAT"
SYSRESET_POSTFIX: str = ":SysReset"
HEARTBEAT_KEY_NAME: str = "heartbeat"

def __post_init__(self):
# non dataclass PVss
self.hbeat_val = ProcessIntValue(value=-1) # set to unachievable heartbeat val
self.name = f"{self.ioc_prefix}_reset_tree"

def get_tree(self) -> Sequence:
def check_acquired_current_hbeat():
val = self.hbeat_val.get_value() != -1 # set to unachievable heartbeat val
logger.debug(f"Heartbeat cached as {val}, {self.hbeat_val.get_value()}")
return val

# get the current heartbeat of IOC
@wrapped_action_work(loop_period_sec=0.1)
def cache_hbeat_wfunc(comp_condition: Evaluatable) -> py_trees.common.Status:
current_hbeat = caget(self.ioc_prefix+self.HEARTBEAT_POSTFIX)
self.hbeat_val.set_value(current_hbeat)
logger.debug(f"<<-- Aquired ioc hbeat: {self.ioc_prefix} hbeat count: {current_hbeat}")

return py_trees.common.Status.SUCCESS

cache_current_heartbeat = ActionNode(name=f"{self.ioc_prefix}_hbeat_cache",
work_func=cache_hbeat_wfunc,
completion_condition=check_acquired_current_hbeat
)

# send the reset command
reset_success_termination_condiiton = BinaryConditionItem(
left_value=EPICSValue(pv_name=f"{self.ioc_prefix+self.HEARTBEAT_POSTFIX}"),
right_value=self.hbeat_val,
operator=ConditionOperator.less)
send_reset = SetPVActionItem(name=f"reset_{self.ioc_prefix}",
pv=f"{self.ioc_prefix}:SysReset",
value=1,
loop_period_sec=0.1, # this is greater than work_timeout period, should only happen once.
termination_check=reset_success_termination_condiiton)

# get the current heartbeat of IOC
@wrapped_action_work(loop_period_sec=0.1)
def reset_cache_hbeat_wfunc(comp_condition: Evaluatable) -> py_trees.common.Status:
self.hbeat_val.set_value(-1)
logger.debug(f"<<-- Resetting cached ioc hbeat for tree: {self.ioc_prefix}")

return py_trees.common.Status.SUCCESS
reset_heartbeat_cache = ActionNode(name=f"{self.ioc_prefix}_reset_heartbeat_cache",
work_func=reset_cache_hbeat_wfunc,
completion_condition=lambda : self.hbeat_val.get_value() == -1)

root = Sequence(name=self.name,
memory=True,
children=[cache_current_heartbeat, send_reset.get_tree(), reset_heartbeat_cache])
return root
57 changes: 57 additions & 0 deletions beams/tree_config/value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import logging
from ctypes import c_int
from dataclasses import dataclass
from multiprocessing import Value
from typing import Any

from epics import caget

from beams.serialization import as_tagged_union

logger = logging.getLogger(__name__)


@as_tagged_union
@dataclass
class BaseValue:
def get_value(self) -> Any:
raise NotImplementedError


@dataclass
class FixedValue(BaseValue):
value: Any

def get_value(self) -> Any:
return self.value


@dataclass
class EPICSValue(BaseValue):
pv_name: str
as_string: bool = False

def get_value(self) -> Any:
value = caget(self.pv_name, as_string=self.as_string)
logger.debug(f" <<-- (EPICSValue): caget({self.pv_name}) -> {value}")
return value


@dataclass
class ProcessIntValue():
value: int = 0

def __post_init__(self):
self._value = Value(c_int, self.value, lock=True)

def set_value(self, value):
self._value.value = value

def get_value(self) -> Any:
return self._value.value


@dataclass
class OphydTarget(BaseValue):
device_name: str
component_path: list[str]
2 changes: 1 addition & 1 deletion examples/mfx_dg1/mfx_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from beams.tree_config import save_tree_item_to_path
from beams.tree_config.action import SetPVActionItem
from beams.tree_config.base import EPICSValue, FixedValue
from beams.tree_config.composite import (SelectorItem, SequenceConditionItem,
SequenceItem)
from beams.tree_config.condition import (BinaryConditionItem,
BoundedConditionItem,
ConditionOperator)
from beams.tree_config.idiom import CheckAndDoItem
from beams.tree_config.value import EPICSValue, FixedValue

# DG2 Stopper: remove
check_dg2_stp_not_open = BinaryConditionItem(
Expand Down