Skip to content

Commit 045aa0f

Browse files
author
kasemir
committed
Alarms: Support infopv:NameOfPV action
Similar to `mailto` action, but sends info to PV instead of email.
1 parent 20dd0cc commit 045aa0f

File tree

7 files changed

+236
-18
lines changed

7 files changed

+236
-18
lines changed

app/alarm/examples/infopv.db

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Example for "Info PV"
2+
# used with automated action set to "infopv:NameOfPV"
3+
#
4+
# softIoc -s -m N=NameOfPV -d infopv.db
5+
#
6+
# With Channel Access, use $(N).VAL$ to access the full text.
7+
8+
record(lsi, "$(N)")
9+
{
10+
field(SIZV, 1000)
11+
field(INP, {const:""})
12+
field(PINI, "YES")
13+
}

app/alarm/ui/doc/index.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,26 @@ Sends email with alarm detail to list of recipients.
307307
The email server is configured in the alarm preferences.
308308

309309

310+
``infopv:SomePV``:
311+
Writes the alarm detail to a PV.
312+
313+
The PV needs to hold a string, for example::
314+
315+
# Example for "Info PV"
316+
# used with automated action set to "infopv:NameOfPV"
317+
#
318+
# softIoc -s -m N=NameOfPV -d infopv.db
319+
320+
record(lsi, "$(N)")
321+
{
322+
field(SIZV, 1000)
323+
field(INP, {const:""})
324+
field(PINI, "YES")
325+
}
326+
327+
With Channel Access, since the text usually exceeds 40 characters, use ``infopv:SomePV.VAL$``.
328+
329+
310330
``cmd:some_command arg1 arg2``:
311331
Invokes command with list of space-separated arguments.
312332
The special argument "*" will be replaced with a list of alarm PVs and their alarm severity.

app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/TitleDetailDelayTable.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018-2023 Oak Ridge National Laboratory.
2+
* Copyright (c) 2018-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -49,8 +49,16 @@
4949
@SuppressWarnings("nls")
5050
public class TitleDetailDelayTable extends BorderPane
5151
{
52-
private enum Option_d {
53-
mailto, cmd, sevrpv
52+
private enum Option_d
53+
{
54+
// Send email with alarm info
55+
mailto,
56+
// Execute external command
57+
cmd,
58+
// Update PV with severity
59+
sevrpv,
60+
// Update PV with alarm info text
61+
infopv
5462
};
5563

5664
private final ObservableList<TitleDetailDelay> items = FXCollections.observableArrayList();

services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/ServerModel.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018-2022 Oak Ridge National Laboratory.
2+
* Copyright (c) 2018-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -15,7 +15,6 @@
1515
import java.util.List;
1616
import java.util.Objects;
1717
import java.util.concurrent.ConcurrentHashMap;
18-
import java.util.concurrent.TimeUnit;
1918
import java.util.logging.Level;
2019

2120
import org.apache.kafka.clients.consumer.Consumer;
@@ -34,6 +33,7 @@
3433
import org.phoebus.applications.alarm.model.SeverityLevel;
3534
import org.phoebus.applications.alarm.model.json.JsonModelReader;
3635
import org.phoebus.applications.alarm.model.json.JsonModelWriter;
36+
import org.phoebus.applications.alarm.server.actions.InfoPVActionExecutor;
3737

3838
/** Server's model of the alarm configuration
3939
*
@@ -120,6 +120,7 @@ public void start()
120120
{
121121
thread.start();
122122
SeverityPVHandler.initialize();
123+
InfoPVActionExecutor.initialize();
123124

124125
// Alarm server startup message
125126
sendAnnunciatorMessage(root.getPathName(), SeverityLevel.OK, "* Alarm server started. Everything is going to be all right.");
@@ -603,6 +604,7 @@ private void clearActionsAndStopPVs(final AlarmTreeItem<?> node)
603604
public void shutdown()
604605
{
605606
SeverityPVHandler.stop();
607+
InfoPVActionExecutor.stop();
606608
running = false;
607609
consumer.wakeup();
608610
try

services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/actions/AutomatedActionExecutor.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018-2019 Oak Ridge National Laboratory.
2+
* Copyright (c) 2018-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -27,6 +27,9 @@
2727
2828
* Sends email with alarm detail to list of recipients.
2929
*
30+
* <p>"infopv:ca://demo"<br>
31+
* Writes alarm detail to string PV.
32+
*
3033
* <p>"cmd:some_command arg1 arg2"<br>
3134
* Invokes command with list of space-separated arguments.
3235
* The special argument "*" will be replaced with a list of alarm PVs and their alarm severity.
@@ -44,18 +47,21 @@ public void accept(final AlarmTreeItem<?> item, final TitleDetailDelay action)
4447
// Perform the automated action in background thread
4548
JobManager.schedule("Automated Action", monitor ->
4649
{
47-
if (action.detail.startsWith("mailto:"))
48-
{
49-
if (AlarmLogic.getDisableNotify() == true)
50-
{
51-
return;
52-
}
53-
EmailActionExecutor.sendEmail(item, action.detail.substring(7).split(" *, *"));
54-
}
50+
final boolean mailto = action.detail.startsWith("mailto:");
51+
final boolean infopv = action.detail.startsWith("infopv:");
52+
if (mailto || infopv)
53+
{ // Are notifications disabled?
54+
if (AlarmLogic.getDisableNotify())
55+
return;
56+
if (mailto)
57+
EmailActionExecutor.sendEmail(item, action.detail.substring(7).split(" *, *"));
58+
else
59+
InfoPVActionExecutor.writeInfo(item, action.detail.substring(7).trim());
60+
}
5561
else if (action.detail.startsWith("cmd:"))
5662
CommandActionExecutor.run(item, action.detail.substring(4));
5763
else
58-
logger.log(Level.WARNING, "Automated action " + action + " lacks 'mailto:' or 'cmd:' in detail");
64+
logger.log(Level.WARNING, "Automated action " + action + " lacks 'mailto:', 'infopv:' or 'cmd:' in detail");
5965
});
6066
}
6167

services/alarm-server/src/main/java/org/phoebus/applications/alarm/server/actions/EmailActionExecutor.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2018-2020 Oak Ridge National Laboratory.
2+
* Copyright (c) 2018-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -54,7 +54,11 @@ static void sendEmail(final AlarmTreeItem<?> item, final String[] addresses)
5454
}
5555
}
5656

57-
private static String createTitle(final AlarmTreeItem<?> item)
57+
/** Create title for email, also used by Info PV
58+
* @param item Item for which to create title
59+
* @return Title
60+
*/
61+
static String createTitle(final AlarmTreeItem<?> item)
5862
{
5963
final StringBuilder buf = new StringBuilder();
6064

@@ -77,7 +81,11 @@ private static String createTitle(final AlarmTreeItem<?> item)
7781
return buf.toString();
7882
}
7983

80-
private static String createBody(final AlarmTreeItem<?> item)
84+
/** Create info body for email, also used by Info PV
85+
* @param item Item for which to create info
86+
* @return Info text
87+
*/
88+
static String createBody(final AlarmTreeItem<?> item)
8189
{
8290
final StringBuilder buf = new StringBuilder();
8391

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Oak Ridge National Laboratory.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*******************************************************************************/
8+
package org.phoebus.applications.alarm.server.actions;
9+
10+
import static org.phoebus.applications.alarm.AlarmSystem.logger;
11+
12+
import java.time.Duration;
13+
import java.time.Instant;
14+
import java.util.concurrent.ConcurrentHashMap;
15+
import java.util.concurrent.CountDownLatch;
16+
import java.util.concurrent.TimeUnit;
17+
import java.util.logging.Level;
18+
19+
import org.phoebus.applications.alarm.model.AlarmTreeItem;
20+
import org.phoebus.pv.PV;
21+
import org.phoebus.pv.PVPool;
22+
23+
/** Executor for 'infopv:' actions
24+
*
25+
* <p>Handles automated actions with the following detail:
26+
*
27+
* <p>"infopv:SomePVName"<br>
28+
* Writes alarm detail as string to PV.
29+
*
30+
* @author Kay Kasemir
31+
*/
32+
public class InfoPVActionExecutor
33+
{
34+
/** Map of PV name to PV.
35+
* PVs are created/added on first use and then kept in here until the server shuts down.
36+
*/
37+
private static final ConcurrentHashMap<String, PV> pvs = new ConcurrentHashMap<>();
38+
39+
/** Info to write to a PV and time it was requested */
40+
private static record TimedInfo(Instant time, String info) {};
41+
42+
/** Map of PV name to most recent info.
43+
* Catches updates to a PV.
44+
* If several updates arrive for the same PV, they are not queued
45+
* because older messages tend to be obsolete.
46+
* They are written with a slight delay to reduce traffic, then removed.
47+
*/
48+
private static final ConcurrentHashMap<String, TimedInfo> updates = new ConcurrentHashMap<>();
49+
50+
/** Flag for thread to exit */
51+
private static CountDownLatch done = new CountDownLatch(1);
52+
53+
/** Thread that performs updates */
54+
private static final Thread thread = new Thread(InfoPVActionExecutor::run, "InfoPVActionExecutor");
55+
56+
57+
/** Initialize, start InfoPVActionExecutor thread */
58+
public static void initialize()
59+
{
60+
thread.setDaemon(true);
61+
thread.start();
62+
}
63+
64+
65+
/** Request writing alarm info text to PV
66+
* @param item Alarm item from which to get alarm info
67+
* @param pv_name Name of PV to update
68+
*/
69+
public static void writeInfo(final AlarmTreeItem<?> item, final String pv_name)
70+
{
71+
final String info = EmailActionExecutor.createTitle(item) +
72+
EmailActionExecutor.createBody(item);
73+
74+
// Register PV or find existing one
75+
PV pv = pvs.computeIfAbsent(pv_name, name ->
76+
{
77+
try
78+
{
79+
return PVPool.getPV(pv_name);
80+
}
81+
catch (Exception ex)
82+
{
83+
logger.log(Level.WARNING, "Cannot create PV '" + pv_name + "'", ex);
84+
}
85+
return null;
86+
});
87+
// On success, register update
88+
if (pv != null)
89+
updates.put(pv_name, new TimedInfo(Instant.now(), info));
90+
}
91+
92+
93+
/** Executed by InfoPVActionExecutor:
94+
* Waits for requested updates and performs them.
95+
*/
96+
private static void run()
97+
{
98+
try
99+
{
100+
// Delay to throttle the rate of writes and re-tries
101+
while (! done.await(1, TimeUnit.SECONDS))
102+
{
103+
// Keep trying to write an update until it's old
104+
final Instant old = Instant.now().minus(Duration.ofSeconds(30));
105+
for (String pv_name : updates.keySet())
106+
{
107+
final TimedInfo update = updates.get(pv_name);
108+
boolean success = false;
109+
try
110+
{
111+
final PV pv = pvs.get(pv_name);
112+
pv.write(update.info);
113+
success = true;
114+
}
115+
catch (Exception ex)
116+
{
117+
logger.log(Level.WARNING, "Failed to write alarm info to " + pv_name, ex);
118+
}
119+
// If update was handled, or failed several times and is now old, remove it
120+
if (success || update.time.isBefore(old))
121+
{ // Only remove the one we're dealing with!
122+
if (updates.remove(pv_name, update))
123+
{
124+
if (success)
125+
logger.log(Level.INFO, "Wrote alarm info to " + pv_name);
126+
else
127+
logger.log(Level.WARNING, "Give up writing alarm info to " + pv_name);
128+
}
129+
// else: There's already a new update, keep that
130+
}
131+
// else: Update failed, not old, try again
132+
}
133+
134+
}
135+
}
136+
catch (Exception ex)
137+
{
138+
logger.log(Level.WARNING, Thread.currentThread().getName() + " error", ex);
139+
}
140+
}
141+
142+
143+
/** Release all PVs */
144+
public static void stop()
145+
{
146+
// Stop thread
147+
done.countDown();
148+
try
149+
{
150+
thread.join(Duration.ofSeconds(5));
151+
}
152+
catch (InterruptedException ex)
153+
{
154+
// Ignore, closing down anyway
155+
}
156+
// Release all PVs
157+
for (PV pv : pvs.values())
158+
PVPool.releasePV(pv);
159+
pvs.clear();
160+
}
161+
}

0 commit comments

Comments
 (0)