Skip to content

Commit 9dc7c35

Browse files
committed
p4p control, display and alarm fields complete
1 parent 3bbeb92 commit 9dc7c35

File tree

13 files changed

+1099
-74
lines changed

13 files changed

+1099
-74
lines changed

docs/source/config.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ Each interface type has its own ``config`` block. Refer to the
6262
:doc:`interfaces` guide for details on:
6363

6464
- ``p4p`` / ``p4p_server`` — EPICS PVA variables with ``proto``, ``name``,
65-
``type``, ``default`` fields
65+
``type``, ``default`` fields, plus optional alarm metadata
66+
(``compute_alarm``, ``display``, ``control``, ``valueAlarm``)
6667
- ``k2eg`` — Kafka-to-EPICS gateway variables
6768
- ``fastapi_server`` — REST API with ``host``, ``port``, ``start_server``,
6869
``input_queue_max``, ``output_queue_max``, ``cors_origins``, and typed
@@ -80,4 +81,4 @@ Model modules
8081
~~~~~~~~~~~~~
8182
Model config specifies a ``type`` (e.g. ``LocalModelGetter``,
8283
``MLflowModelGetter``) and ``args`` for the model getter class. See the
83-
README for full examples.
84+
README for full examples.

docs/source/interfaces.rst

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,89 @@ The ``p4p`` interface connects to an external EPICS server. The ``p4p_server``
2121
interface hosts its own p4p server for the specified PVs. Both share the same
2222
YAML configuration format. See the README for sample YAML.
2323

24+
EPICS Variable Fields
25+
~~~~~~~~~~~~~~~~~~~~~
26+
27+
.. list-table::
28+
:header-rows: 1
29+
:widths: 18 10 15 57
30+
31+
* - Field
32+
- Type
33+
- Default
34+
- Description
35+
* - ``proto``
36+
- string
37+
- **required**
38+
- Protocol (currently ``pva``)
39+
* - ``name``
40+
- string
41+
- **required**
42+
- PV name
43+
* - ``mode``
44+
- string
45+
- ``"inout"``
46+
- ``in``, ``out``, or ``inout``
47+
* - ``type``
48+
- string
49+
- ``"scalar"``
50+
- ``scalar``, ``waveform``, ``array``, ``image``
51+
* - ``default``
52+
- any
53+
- ``0.0`` / zeros
54+
- Initial value (not supported for ``image`` type)
55+
* - ``length``
56+
- int
57+
- ``10``
58+
- Array/waveform length when no default is provided
59+
* - ``image_size``
60+
- dict
61+
- —
62+
- Required for ``image`` type: ``{"x": int, "y": int}``
63+
* - ``compute_alarm``
64+
- bool
65+
- ``false``
66+
- Enable scalar alarm computation from ``valueAlarm`` limits
67+
* - ``display``
68+
- dict
69+
- —
70+
- Optional NTScalar display metadata
71+
* - ``control``
72+
- dict
73+
- —
74+
- Optional NTScalar control metadata
75+
* - ``valueAlarm``
76+
- dict
77+
- —
78+
- Optional NTScalar alarm limit metadata
79+
80+
Alarm Behavior
81+
~~~~~~~~~~~~~~
82+
83+
- Computation is scalar-only and active when ``compute_alarm: true``.
84+
- ``compute_alarm: true`` requires:
85+
``valueAlarm.active: true`` and limits
86+
``lowAlarmLimit``, ``lowWarningLimit``, ``highWarningLimit``, ``highAlarmLimit``.
87+
- Missing severities use defaults:
88+
``lowAlarmSeverity=2``, ``lowWarningSeverity=1``,
89+
``highWarningSeverity=1``, ``highAlarmSeverity=2``.
90+
- Status mapping follows EPICS ``menuAlarmStat``:
91+
``NO_ALARM=0``, ``HIHI=3``, ``HIGH=4``, ``LOLO=5``, ``LOW=6``.
92+
- Explicit ``alarm`` payload overrides computed alarm.
93+
- Non-scalars do not compute alarms, but explicit ``alarm`` payloads are accepted.
94+
95+
Model Alarm Override
96+
~~~~~~~~~~~~~~~~~~~~
97+
98+
Models can publish structured output with explicit alarm fields (for example
99+
``{"PV": {"value": 1.0, "alarm": {...}}}``), and ``ModelObserver`` preserves
100+
that structure when publishing downstream.
101+
102+
See example:
103+
104+
- ``examples/base/local/deployment_config_p4p_alarm.yaml``
105+
- ``examples/base/local/model_definition_alarm_override.py``
106+
24107
k2eg Interface
25108
--------------
26109
Built on SLAC's `k2eg <https://github.com/slaclab/k2eg>`_, this interface gets
@@ -246,4 +329,4 @@ Completed jobs can be retrieved via ``GET /jobs/next`` (FIFO dequeue) or
246329
Proper job tracking will be integrated via trace propagation across the
247330
message broker. Each job's ``job_id`` will be carried through the full
248331
pipeline in struct metadata, enabling accurate matching of results to jobs
249-
even under concurrent load.
332+
even under concurrent load.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
deployment:
2+
type: "continuous"
3+
rate: .1 #seconds
4+
5+
modules:
6+
p4p_server:
7+
name: "p4p_server"
8+
type: "interface.p4p_server"
9+
pub:
10+
- "in_interface"
11+
sub:
12+
- "get_all"
13+
- "model_out"
14+
module_args: None
15+
config:
16+
EPICS_PVA_NAME_SERVERS: "localhost:5075"
17+
variables:
18+
# scalar inputs
19+
ML:LOCAL:TEST_B:
20+
proto: pva
21+
name: ML:LOCAL:TEST_B
22+
type: scalar
23+
default: 0.0
24+
compute_alarm: true
25+
display:
26+
description: "Model input B"
27+
units: "arb"
28+
format: "%.3f"
29+
limitLow: -20.0
30+
limitHigh: 20.0
31+
control:
32+
limitLow: -10.0
33+
limitHigh: 10.0
34+
minStep: 0.1
35+
valueAlarm:
36+
active: true
37+
lowAlarmLimit: -8.0
38+
lowWarningLimit: -5.0
39+
highWarningLimit: 5.0
40+
highAlarmLimit: 8.0
41+
42+
ML:LOCAL:TEST_A:
43+
proto: pva
44+
name: ML:LOCAL:TEST_A
45+
type: scalar
46+
default: 0.0
47+
compute_alarm: true
48+
display:
49+
description: "Model input A"
50+
units: "arb"
51+
format: "%.3f"
52+
limitLow: -20.0
53+
limitHigh: 20.0
54+
control:
55+
limitLow: -10.0
56+
limitHigh: 10.0
57+
minStep: 0.1
58+
valueAlarm:
59+
active: true
60+
lowAlarmLimit: -8.0
61+
lowWarningLimit: -5.0
62+
highWarningLimit: 5.0
63+
highAlarmLimit: 8.0
64+
65+
# scalar output with computed alarm
66+
ML:LOCAL:TEST_S:
67+
proto: pva
68+
name: ML:LOCAL:TEST_S
69+
type: scalar
70+
default: 0.0
71+
compute_alarm: true
72+
display:
73+
description: "Model output S"
74+
units: "arb"
75+
format: "%.3f"
76+
limitLow: -200.0
77+
limitHigh: 200.0
78+
control:
79+
limitLow: -120.0
80+
limitHigh: 120.0
81+
minStep: 0.1
82+
valueAlarm:
83+
active: true
84+
lowAlarmLimit: -100.0
85+
lowWarningLimit: -60.0
86+
highWarningLimit: 60.0
87+
highAlarmLimit: 100.0
88+
89+
# non-scalar PV: no computed alarms, but explicit alarm payloads are forwarded
90+
ML:LOCAL:TEST_WF:
91+
proto: pva
92+
name: ML:LOCAL:TEST_WF
93+
type: waveform
94+
length: 8
95+
default: [0, 1, 2, 3, 4, 5, 6, 7]
96+
97+
input_transformer:
98+
name: "input_transformer"
99+
type: "transformer.SimpleTransformer"
100+
pub: "in_transformer"
101+
sub: "in_interface"
102+
module_args: None
103+
config:
104+
symbols:
105+
- "ML:LOCAL:TEST_B"
106+
- "ML:LOCAL:TEST_A"
107+
variables:
108+
x:
109+
formula: "ML:LOCAL:TEST_A"
110+
y:
111+
formula: "ML:LOCAL:TEST_B"
112+
113+
model:
114+
name: "model"
115+
type: "model.SimpleModel"
116+
pub: "model_out"
117+
sub: "in_transformer"
118+
module_args: None
119+
config:
120+
type: "LocalModelGetter"
121+
args:
122+
# This model emits an explicit alarm payload for ML:LOCAL:TEST_S.
123+
model_path: "examples/base/local/model_definition_alarm_override.py"
124+
model_factory_class: "ModelFactory"
125+
variables:
126+
max:
127+
type: "scalar"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import torch
2+
import os
3+
4+
5+
OUTPUT_PV = 'ML:LOCAL:TEST_S'
6+
7+
8+
class ModelFactory:
9+
def __init__(self):
10+
os.environ['PYTHONPATH'] = os.path.abspath(
11+
os.path.join(os.path.dirname(__file__), '..', '..', '..')
12+
)
13+
self.model = SimpleModel()
14+
model_path = 'examples/base/local/model.pth'
15+
if os.path.exists(model_path):
16+
self.model.load_state_dict(torch.load(model_path))
17+
18+
def get_model(self):
19+
return self.model
20+
21+
22+
class SimpleModel(torch.nn.Module):
23+
def __init__(self):
24+
super(SimpleModel, self).__init__()
25+
self.linear1 = torch.nn.Linear(2, 10)
26+
self.linear2 = torch.nn.Linear(10, 1)
27+
28+
def forward(self, x):
29+
x = torch.relu(self.linear1(x))
30+
x = self.linear2(x)
31+
return x
32+
33+
@staticmethod
34+
def _model_alarm_for_output(value: float) -> dict:
35+
# Example override policy from model side.
36+
if value >= 50.0:
37+
return {'severity': 2, 'status': 3, 'message': 'HIHI (model override)'}
38+
if value >= 20.0:
39+
return {'severity': 1, 'status': 4, 'message': 'HIGH (model override)'}
40+
if value <= -50.0:
41+
return {'severity': 2, 'status': 5, 'message': 'LOLO (model override)'}
42+
if value <= -20.0:
43+
return {'severity': 1, 'status': 6, 'message': 'LOW (model override)'}
44+
return {'severity': 0, 'status': 0, 'message': ''}
45+
46+
def evaluate(self, x: dict) -> dict:
47+
input_tensor = torch.tensor([x['x'], x['y']], dtype=torch.float32)
48+
output_tensor = self.forward(input_tensor)
49+
output_value = float(output_tensor.item())
50+
51+
return {
52+
OUTPUT_PV: {
53+
'value': output_value,
54+
# Explicit alarm from model should override interface-computed alarm.
55+
'alarm': self._model_alarm_for_output(output_value),
56+
}
57+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
SUPPORTED_PV_TYPES = {'scalar', 'waveform', 'array', 'image'}
2+
DISPLAY_FIELDS = {'limitLow', 'limitHigh', 'description', 'format', 'units'}
3+
CONTROL_FIELDS = {'limitLow', 'limitHigh', 'minStep'}
4+
VALUE_ALARM_FIELDS = {
5+
'active',
6+
'lowAlarmLimit',
7+
'lowWarningLimit',
8+
'highWarningLimit',
9+
'highAlarmLimit',
10+
'lowAlarmSeverity',
11+
'lowWarningSeverity',
12+
'highWarningSeverity',
13+
'highAlarmSeverity',
14+
'hysteresis',
15+
}
16+
17+
ALARM_STATUSES = {
18+
'normal': 0, # NO_ALARM
19+
'highAlarm': 3, # HIHI
20+
'highWarning': 4, # HIGH
21+
'lowAlarm': 5, # LOLO
22+
'lowWarning': 6, # LOW
23+
}
24+
25+
ALARM_SEVERITY_FIELDS = (
26+
'lowAlarmSeverity',
27+
'lowWarningSeverity',
28+
'highWarningSeverity',
29+
'highAlarmSeverity',
30+
)
31+
32+
DEFAULT_ALARM_SEVERITIES = {
33+
'lowAlarmSeverity': 2,
34+
'lowWarningSeverity': 1,
35+
'highWarningSeverity': 1,
36+
'highAlarmSeverity': 2,
37+
}
38+
39+
ALARM_LIMIT_FIELDS = (
40+
'lowAlarmLimit',
41+
'lowWarningLimit',
42+
'highWarningLimit',
43+
'highAlarmLimit',
44+
)

0 commit comments

Comments
 (0)