> timeout = new AtomicReference<>();
/** @param timeout_secs Seconds after which we time out */
public ResettableTimeout(final long timeout_secs)
- {
- this.timeout_secs = timeout_secs;
- reset();
- }
+ {
+ this.timeout_secs = timeout_secs;
+ reset();
+ }
+
+ /** Reset the timer. As long as this is called within the timeout, we keep running
+ * @param timeout_secs New timeout in seconds
+ */
+ public void reset(final long timeout_secs)
+ {
+ this.timeout_secs = timeout_secs;
+ reset();
+ }
- /** Reset the timer. As long as this is called within the timeout, we keep running */
+ /** Reset the timer. As long as this is called within the timeout, we keep running */
public void reset()
{
final ScheduledFuture> previous = timeout.getAndSet(timer.schedule(signal_no_more_messages, timeout_secs, TimeUnit.SECONDS));
diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java
index 44ca4c0426..80d60fa470 100644
--- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java
+++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmClient.java
@@ -7,18 +7,6 @@
*******************************************************************************/
package org.phoebus.applications.alarm.client;
-import static org.phoebus.applications.alarm.AlarmSystem.logger;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.Level;
-
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
@@ -33,71 +21,111 @@
import org.phoebus.applications.alarm.model.json.JsonTags;
import org.phoebus.util.time.TimestampFormats;
-/** Alarm client model
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+
+import static org.phoebus.applications.alarm.AlarmSystem.logger;
+
+/**
+ * Alarm client model
*
- * Given an alarm configuration name like "Accelerator",
- * subscribes to the "Accelerator" topic for configuration updates
- * and the "AcceleratorState" topic for alarm state updates.
+ *
Given an alarm configuration name like "Accelerator",
+ * subscribes to the "Accelerator" topic for configuration updates
+ * and the "AcceleratorState" topic for alarm state updates.
*
- *
Updates from either topic are merged into an in-memory model
- * of the complete alarm information,
- * updating listeners with all changes.
+ *
Updates from either topic are merged into an in-memory model
+ * of the complete alarm information,
+ * updating listeners with all changes.
*
- * @author Kay Kasemir
+ * @author Kay Kasemir
*/
@SuppressWarnings("nls")
-public class AlarmClient
-{
- /** Kafka topics for config/status and commands */
+public class AlarmClient {
+ /**
+ * Kafka topics for config/status and commands
+ */
private final String config_topic, command_topic;
- /** Listeners to this client */
+ /**
+ * Listeners to this client
+ */
private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>();
- /** Alarm tree root */
+ /**
+ * Alarm tree root
+ */
private final AlarmClientNode root;
- /** Alarm tree Paths that have been deleted.
+ /**
+ * Timeout in seconds waiting for response from Kafka when sending producer messages.
+ */
+ private static final int KAFKA_CLIENT_TIMEOUT = 10;
+
+ /**
+ * Alarm tree Paths that have been deleted.
*
- * Used to distinguish between paths that are not in the alarm tree
- * because we have never seen a config or status update for them,
- * and entries that have been deleted, so further state updates
- * should be ignored until the item is again added (config message).
+ *
Used to distinguish between paths that are not in the alarm tree
+ * because we have never seen a config or status update for them,
+ * and entries that have been deleted, so further state updates
+ * should be ignored until the item is again added (config message).
*/
private final Set deleted_paths = ConcurrentHashMap.newKeySet();
- /** Flag for message handling thread to run or exit */
+ /**
+ * Flag for message handling thread to run or exit
+ */
private final AtomicBoolean running = new AtomicBoolean(true);
- /** Currently in maintenance mode? */
+ /**
+ * Currently in maintenance mode?
+ */
private final AtomicBoolean maintenance_mode = new AtomicBoolean(false);
- /** Currently in silent mode? */
+ /**
+ * Currently in silent mode?
+ */
private final AtomicBoolean disable_notify = new AtomicBoolean(false);
- /** Kafka consumer */
+ /**
+ * Kafka consumer
+ */
private final Consumer consumer;
- /** Kafka producer */
+ /**
+ * Kafka producer
+ */
private final Producer producer;
- /** Message handling thread */
+ /**
+ * Message handling thread
+ */
private final Thread thread;
- /** Time of last state update (ms),
- * used to determine timeout
+ /**
+ * Time of last state update (ms),
+ * used to determine timeout
*/
private long last_state_update = 0;
- /** Timeout, not seen any messages from server? */
+ /**
+ * Timeout, not seen any messages from server?
+ */
private volatile boolean has_timed_out = false;
- /** @param server Kafka Server host:port
- * @param config_name Name of alarm tree root
- * @param kafka_properties_file File to load additional kafka properties from
+ /**
+ * @param server Kafka Server host:port
+ * @param config_name Name of alarm tree root
+ * @param kafka_properties_file File to load additional kafka properties from
*/
- public AlarmClient(final String server, final String config_name, final String kafka_properties_file)
- {
+ public AlarmClient(final String server, final String config_name, final String kafka_properties_file) {
Objects.requireNonNull(server);
Objects.requireNonNull(config_name);
@@ -113,116 +141,125 @@ public AlarmClient(final String server, final String config_name, final String k
thread.setDaemon(true);
}
- /** @param listener Listener to add */
- public void addListener(final AlarmClientListener listener)
- {
+ /**
+ * @param listener Listener to add
+ */
+ public void addListener(final AlarmClientListener listener) {
listeners.add(listener);
}
- /** @param listener Listener to remove */
- public void removeListener(final AlarmClientListener listener)
- {
- if (! listeners.remove(listener))
+ /**
+ * @param listener Listener to remove
+ */
+ public void removeListener(final AlarmClientListener listener) {
+ if (!listeners.remove(listener))
throw new IllegalStateException("Unknown listener");
}
- /** Start client
- * @see #shutdown()
+ /**
+ * Start client
+ *
+ * @see #shutdown()
*/
- public void start()
- {
+ public void start() {
thread.start();
}
- /** @return true
if start()
had been called */
- public boolean isRunning()
- {
+ /**
+ * @return true
if start()
had been called
+ */
+ public boolean isRunning() {
return thread.isAlive();
}
- /** @return Root of alarm configuration */
- public AlarmClientNode getRoot()
- {
+ /**
+ * @return Root of alarm configuration
+ */
+ public AlarmClientNode getRoot() {
return root;
}
- /** @return Is alarm server in maintenance mode? */
- public boolean isMaintenanceMode()
- {
+ /**
+ * @return Is alarm server in maintenance mode?
+ */
+ public boolean isMaintenanceMode() {
return maintenance_mode.get();
}
- /** @return Is alarm server in disable notify mode? */
- public boolean isDisableNotify()
- {
+ /**
+ * @return Is alarm server in disable notify mode?
+ */
+ public boolean isDisableNotify() {
return disable_notify.get();
}
- /** @param maintenance Select maintenance mode? */
- public void setMode(final boolean maintenance)
- {
+ /**
+ * Client code must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds.
+ *
+ * @param maintenance Select maintenance mode?
+ * @throws Exception if Kafka interaction fails for any reason.
+ */
+ public void setMode(final boolean maintenance) throws Exception {
final String cmd = maintenance ? JsonTags.MAINTENANCE : JsonTags.NORMAL;
- try
- {
- final String json = new String (JsonModelWriter.commandToBytes(cmd));
+ try {
+ final String json = new String(JsonModelWriter.commandToBytes(cmd));
final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + root.getPathName(), json);
- producer.send(record);
- }
- catch (final Exception ex)
- {
+ producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
+ } catch (final Exception ex) {
logger.log(Level.WARNING, "Cannot set mode for " + root + " to " + cmd, ex);
+ throw ex;
}
}
- /** @param disable_notify Select notify disable ? */
- public void setNotify(final boolean disable_notify)
- {
+ /**
+ * Client must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds.
+ *
+ * @param disable_notify Select notify disable ?
+ * @throws Exception if Kafka interaction fails for any reason.
+ */
+ public void setNotify(final boolean disable_notify) throws Exception {
final String cmd = disable_notify ? JsonTags.DISABLE_NOTIFY : JsonTags.ENABLE_NOTIFY;
- try
- {
- final String json = new String (JsonModelWriter.commandToBytes(cmd));
+ try {
+ final String json = new String(JsonModelWriter.commandToBytes(cmd));
final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + root.getPathName(), json);
- producer.send(record);
- }
- catch (final Exception ex)
- {
+ producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
+ } catch (final Exception ex) {
logger.log(Level.WARNING, "Cannot set mode for " + root + " to " + cmd, ex);
+ throw ex;
}
}
- /** Background thread loop that checks for alarm tree updates */
- private void run()
- {
+ /**
+ * Background thread loop that checks for alarm tree updates
+ */
+ private void run() {
// Send an initial "no server" notification,
// to be cleared once we receive data from server.
checkServerState();
- try
- {
- while (running.get())
- {
+ try {
+ while (running.get()) {
checkUpdates();
checkServerState();
}
- }
- catch (final Throwable ex)
- {
+ } catch (final Throwable ex) {
if (running.get())
logger.log(Level.SEVERE, "Alarm client model error", ex);
// else: Intended shutdown
- }
- finally
- {
+ } finally {
consumer.close();
producer.close();
}
}
- /** Time spent in checkUpdates() waiting for, well, updates */
+ /**
+ * Time spent in checkUpdates() waiting for, well, updates
+ */
private static final Duration POLL_PERIOD = Duration.ofMillis(100);
- /** Perform one check for updates */
- private void checkUpdates()
- {
+ /**
+ * Perform one check for updates
+ */
+ private void checkUpdates() {
// Check for messages, with timeout.
// TODO Because of Kafka bug, this will hang if Kafka isn't running.
// Fixed according to https://issues.apache.org/jira/browse/KAFKA-1894 ,
@@ -232,20 +269,20 @@ private void checkUpdates()
handleUpdate(record);
}
- /** Handle one received update
- * @param record Kafka record
+ /**
+ * Handle one received update
+ *
+ * @param record Kafka record
*/
- private void handleUpdate(final ConsumerRecord record)
- {
+ private void handleUpdate(final ConsumerRecord record) {
final int sep = record.key().indexOf(':');
- if (sep < 0)
- {
+ if (sep < 0) {
logger.log(Level.WARNING, "Invalid key, expecting type:path, got " + record.key());
return;
}
- final String type = record.key().substring(0, sep+1);
- final String path = record.key().substring(sep+1);
+ final String type = record.key().substring(0, sep + 1);
+ final String path = record.key().substring(sep + 1);
final long timestamp = record.timestamp();
final String node_config = record.value();
@@ -253,34 +290,27 @@ private void handleUpdate(final ConsumerRecord record)
logger.log(Level.WARNING, "Expect updates with CreateTime, got " + record.timestampType() + ": " + record.timestamp() + " " + path + " = " + node_config);
logger.log(Level.FINE, () ->
- record.topic() + " @ " +
- TimestampFormats.MILLI_FORMAT.format(Instant.ofEpochMilli(timestamp)) + " " +
- type + path + " = " + node_config);
+ record.topic() + " @ " +
+ TimestampFormats.MILLI_FORMAT.format(Instant.ofEpochMilli(timestamp)) + " " +
+ type + path + " = " + node_config);
- try
- {
+ try {
// Only update listeners if the node changed
AlarmTreeItem> changed_node = null;
final Object json = node_config == null ? null : JsonModelReader.parseJsonText(node_config);
- if (type.equals(AlarmSystem.CONFIG_PREFIX))
- {
- if (json == null)
- { // No config -> Delete node
+ if (type.equals(AlarmSystem.CONFIG_PREFIX)) {
+ if (json == null) { // No config -> Delete node
final AlarmTreeItem> node = deleteNode(path);
// If this was a known node, notify listeners
- if (node != null)
- {
+ if (node != null) {
logger.log(Level.FINE, () -> "Delete " + path);
for (final AlarmClientListener listener : listeners)
listener.itemRemoved(node);
}
- }
- else
- { // Configuration update
+ } else { // Configuration update
if (JsonModelReader.isStateUpdate(json))
logger.log(Level.WARNING, "Got config update with state content: " + record.key() + " " + node_config);
- else
- {
+ else {
AlarmTreeItem> node = findNode(path);
// New node? Will need to send update. Otherwise update when there's a change
if (node == null)
@@ -289,27 +319,18 @@ private void handleUpdate(final ConsumerRecord record)
changed_node = node;
}
}
- }
- else if (type.equals(AlarmSystem.STATE_PREFIX))
- { // State update
- if (json == null)
- { // State update for deleted node, ignore
+ } else if (type.equals(AlarmSystem.STATE_PREFIX)) { // State update
+ if (json == null) { // State update for deleted node, ignore
logger.log(Level.FINE, () -> "Got state update for deleted node: " + record.key() + " " + node_config);
return;
- }
- else if (! JsonModelReader.isStateUpdate(json))
- {
+ } else if (!JsonModelReader.isStateUpdate(json)) {
logger.log(Level.WARNING, "Got state update with config content: " + record.key() + " " + node_config);
return;
- }
- else if (deleted_paths.contains(path))
- {
+ } else if (deleted_paths.contains(path)) {
// It it _deleted_??
logger.log(Level.FINE, () -> "Ignoring state for deleted item: " + record.key() + " " + node_config);
return;
- }
- else
- {
+ } else {
AlarmTreeItem> node = findNode(path);
// New node? Create, and remember to notify
if (node == null)
@@ -334,40 +355,36 @@ else if (deleted_paths.contains(path))
// else: Neither config nor state update; ignore.
// If there were changes, notify listeners
- if (changed_node != null)
- {
+ if (changed_node != null) {
logger.log(Level.FINE, "Update " + path + " to " + changed_node.getState());
for (final AlarmClientListener listener : listeners)
listener.itemUpdated(changed_node);
}
- }
- catch (final Exception ex)
- {
+ } catch (final Exception ex) {
logger.log(Level.WARNING,
- "Alarm config update error for path " + path +
- ", config " + node_config, ex);
+ "Alarm config update error for path " + path +
+ ", config " + node_config, ex);
}
}
- /** Find existing node
+ /**
+ * Find existing node
*
- * @param path Path to node
- * @return Node, null
if model does not contain the node
- * @throws Exception on error
+ * @param path Path to node
+ * @return Node, null
if model does not contain the node
+ * @throws Exception on error
*/
- private AlarmTreeItem> findNode(final String path) throws Exception
- {
+ private AlarmTreeItem> findNode(final String path) throws Exception {
final String[] path_elements = AlarmTreePath.splitPath(path);
// Start of path must match the alarm tree root
- if (path_elements.length < 1 ||
- !root.getName().equals(path_elements[0]))
+ if (path_elements.length < 1 ||
+ !root.getName().equals(path_elements[0]))
throw new Exception("Invalid path for alarm configuration " + root.getName() + ": " + path);
// Walk down the path
AlarmTreeItem> node = root;
- for (int i=1; i findNode(final String path) throws Exception
return node;
}
- /** Delete node
+ /**
+ * Delete node
*
- * It's OK to try delete an unknown node:
- * The node might have once existed, but was then deleted.
- * The last entry in the configuration database is then the deletion hint.
- * A new model that reads this node-to-delete information
- * thus never knew the node.
+ *
It's OK to try delete an unknown node:
+ * The node might have once existed, but was then deleted.
+ * The last entry in the configuration database is then the deletion hint.
+ * A new model that reads this node-to-delete information
+ * thus never knew the node.
*
- * @param path Path to node to delete
- * @return Node that was removed, or null
if model never knew that node
- * @throws Exception on error
+ * @param path Path to node to delete
+ * @return Node that was removed, or null
if model never knew that node
+ * @throws Exception on error
*/
- private AlarmTreeItem> deleteNode(final String path) throws Exception
- {
+ private AlarmTreeItem> deleteNode(final String path) throws Exception {
// Mark path as deleted so we ignore state updates
deleted_paths.add(path);
@@ -402,49 +419,44 @@ private AlarmTreeItem> deleteNode(final String path) throws Exception
return node;
}
- /** Find an existing alarm tree item or create a new one
+ /**
+ * Find an existing alarm tree item or create a new one
*
- *
Informs listener about created nodes,
- * if necessary one notification for each created node along the path.
+ *
Informs listener about created nodes,
+ * if necessary one notification for each created node along the path.
*
- * @param path Alarm tree path
- * @param is_leaf Is this the path to a leaf?
- * @return {@link AlarmTreeItem}
- * @throws Exception on error
+ * @param path Alarm tree path
+ * @param is_leaf Is this the path to a leaf?
+ * @return {@link AlarmTreeItem}
+ * @throws Exception on error
*/
- private AlarmTreeItem> findOrCreateNode(final String path, final boolean is_leaf) throws Exception
- {
+ private AlarmTreeItem> findOrCreateNode(final String path, final boolean is_leaf) throws Exception {
// In case it was previously deleted:
deleted_paths.remove(path);
final String[] path_elements = AlarmTreePath.splitPath(path);
// Start of path must match the alarm tree root
- if (path_elements.length < 1 ||
- !root.getName().equals(path_elements[0]))
+ if (path_elements.length < 1 ||
+ !root.getName().equals(path_elements[0]))
throw new Exception("Invalid path for alarm configuration " + root.getName() + ": " + path);
// Walk down the path
AlarmClientNode parent = root;
- for (int i=1; i node = parent.getChild(name);
// Create missing nodes
- if (node == null)
- { // Done when creating leaf
- if (last && is_leaf)
- {
+ if (node == null) { // Done when creating leaf
+ if (last && is_leaf) {
node = new AlarmClientLeaf(parent.getPathName(), name);
node.addToParent(parent);
logger.log(Level.FINE, "Create " + path);
for (final AlarmClientListener listener : listeners)
listener.itemAdded(node);
return node;
- }
- else
- {
+ } else {
node = new AlarmClientNode(parent.getPathName(), name);
node.addToParent(parent);
for (final AlarmClientListener listener : listeners)
@@ -455,10 +467,10 @@ private AlarmTreeItem> findOrCreateNode(final String path, final boolean is_le
if (last)
return node;
// Found or created intermediate node; continue walking down the path
- if (! (node instanceof AlarmClientNode))
+ if (!(node instanceof AlarmClientNode))
throw new Exception("Expected intermediate node, found " +
- node.getClass().getSimpleName() + " " + node.getName() +
- " while traversing " + path);
+ node.getClass().getSimpleName() + " " + node.getName() +
+ " while traversing " + path);
parent = (AlarmClientNode) node;
}
@@ -466,73 +478,69 @@ private AlarmTreeItem> findOrCreateNode(final String path, final boolean is_le
return parent;
}
- /** Add a component to the alarm tree
- * @param path_name to parent Root or parent component under which to add the component
- * @param new_name Name of the new component
+ /**
+ * Add a component to the alarm tree
+ *
+ * @param path_name to parent Root or parent component under which to add the component
+ * @param new_name Name of the new component
*/
- public void addComponent(final String path_name, final String new_name) throws Exception
- {
- try
- {
+ public void addComponent(final String path_name, final String new_name) {
+ try {
sendNewItemInfo(path_name, new_name, new AlarmClientNode(null, new_name));
- }
- catch (final Exception ex)
- {
+ } catch (final Exception ex) {
logger.log(Level.WARNING, "Cannot add component " + new_name + " to " + path_name, ex);
}
}
- /** Add a component to the alarm tree
- * @param path_name to parent Root or parent component under which to add the component
- * @param new_name Name of the new component
+ /**
+ * Add a component to the alarm tree
+ *
+ * @param path_name to parent Root or parent component under which to add the component
+ * @param new_name Name of the new component
*/
- public void addPV(final String path_name, final String new_name)
- {
- try
- {
+ public void addPV(final String path_name, final String new_name) {
+ try {
sendNewItemInfo(path_name, new_name, new AlarmClientLeaf(null, new_name));
- }
- catch (final Exception ex)
- {
+ } catch (final Exception ex) {
logger.log(Level.WARNING, "Cannot add pv " + new_name + " to " + path_name, ex);
}
}
- private void sendNewItemInfo(String path_name, final String new_name, final AlarmTreeItem> content) throws Exception
- {
+ private void sendNewItemInfo(String path_name, final String new_name, final AlarmTreeItem> content) throws Exception {
// Send message about new component.
// All clients, including this one, will receive and then add the new component.
final String new_path = AlarmTreePath.makePath(path_name, new_name);
sendItemConfigurationUpdate(new_path, content);
}
- /** Send item configuration
- *
- * All clients, including this one, will update when they receive the message
+ /**
+ * Client code must not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds.
+ * Send item configuration.
+ *
All clients, including this one, will update when they receive the message
*
- * @param path Path to the item
- * @param config A prototype item (path is ignored) that holds the new configuration
- * @throws Exception on error
+ * @param path Path to the item
+ * @param config A prototype item (path is ignored) that holds the new configuration
+ * @throws Exception on error
*/
- public void sendItemConfigurationUpdate(final String path, final AlarmTreeItem> config) throws Exception
- {
+ public void sendItemConfigurationUpdate(final String path, final AlarmTreeItem> config) throws Exception {
final String json = new String(JsonModelWriter.toJsonBytes(config));
final ProducerRecord record = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + path, json);
- producer.send(record);
+ producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
}
- /** Remove a component (and sub-items) from alarm tree
- * @param item Item to remove
- * @throws Exception on error
+ /**
+ * Client must should not call this on the UI thread as it may block up to 2x{@link #KAFKA_CLIENT_TIMEOUT} seconds.
+ * Remove a component (and sub-items) from alarm tree
+ *
+ * @param item Item to remove
+ * @throws Exception on error
*/
- public void removeComponent(final AlarmTreeItem> item) throws Exception
- {
- try
- {
- // Depth first deletion of all child nodes.
- final List> children = item.getChildren();
- for (final AlarmTreeItem> child : children)
- removeComponent(child);
+ public void removeComponent(final AlarmTreeItem> item) throws Exception {
+ try {
+ // Depth first deletion of all child nodes.
+ final List> children = item.getChildren();
+ for (final AlarmTreeItem> child : children)
+ removeComponent(child);
// Send message about item to remove
// All clients, including this one, will receive and then remove the item.
@@ -542,75 +550,67 @@ public void removeComponent(final AlarmTreeItem> item) throws Exception
// The id message must arrive before the tombstone.
final String json = new String(JsonModelWriter.deleteMessageToBytes());
final ProducerRecord id = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + item.getPathName(), json);
- producer.send(id);
+ producer.send(id).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
final ProducerRecord tombstone = new ProducerRecord<>(config_topic, AlarmSystem.CONFIG_PREFIX + item.getPathName(), null);
- producer.send(tombstone);
- }
- catch (Exception ex)
- {
+ producer.send(tombstone).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
+ } catch (Exception ex) {
throw new Exception("Error deleting " + item.getPathName(), ex);
}
}
- /** @param item Item for which to acknowledge alarm
- * @param acknowledge true
to acknowledge, else un-acknowledge
+ /**
+ * Client must should not call this on the UI thread as it may block up to {@link #KAFKA_CLIENT_TIMEOUT} seconds.
+ *
+ * @param item Item for which to acknowledge alarm
+ * @param acknowledge true
to acknowledge, else un-acknowledge
*/
- public void acknowledge(final AlarmTreeItem> item, final boolean acknowledge) throws Exception
- {
- try
- {
+ public void acknowledge(final AlarmTreeItem> item, final boolean acknowledge) throws Exception {
+ try {
final String cmd = acknowledge ? "acknowledge" : "unacknowledge";
- final String json = new String (JsonModelWriter.commandToBytes(cmd));
+ final String json = new String(JsonModelWriter.commandToBytes(cmd));
final ProducerRecord record = new ProducerRecord<>(command_topic, AlarmSystem.COMMAND_PREFIX + item.getPathName(), json);
- producer.send(record);
- }
- catch (final Exception ex)
- {
+ producer.send(record).get(KAFKA_CLIENT_TIMEOUT, TimeUnit.SECONDS);
+ } catch (final Exception ex) {
logger.log(Level.WARNING, "Cannot acknowledge component " + item, ex);
throw ex;
}
}
- /** @return true
if connected to server, else updates have timed out */
- public boolean isServerAlive()
- {
+ /**
+ * @return true
if connected to server, else updates have timed out
+ */
+ public boolean isServerAlive() {
return !has_timed_out;
}
- /** Check if there have been any messages from server */
- private void checkServerState()
- {
+ /**
+ * Check if there have been any messages from server
+ */
+ private void checkServerState() {
final long now = System.currentTimeMillis();
- if (now - last_state_update > AlarmSystem.idle_timeout_ms*3)
- {
- if (! has_timed_out)
- {
+ if (now - last_state_update > AlarmSystem.idle_timeout_ms * 3) {
+ if (!has_timed_out) {
has_timed_out = true;
for (final AlarmClientListener listener : listeners)
listener.serverStateChanged(false);
}
+ } else if (has_timed_out) {
+ has_timed_out = false;
+ for (final AlarmClientListener listener : listeners)
+ listener.serverStateChanged(true);
}
- else
- if (has_timed_out)
- {
- has_timed_out = false;
- for (final AlarmClientListener listener : listeners)
- listener.serverStateChanged(true);
- }
}
- /** Stop client */
- public void shutdown()
- {
+ /**
+ * Stop client
+ */
+ public void shutdown() {
running.set(false);
consumer.wakeup();
- try
- {
+ try {
thread.join(2000);
- }
- catch (final InterruptedException ex)
- {
+ } catch (final InterruptedException ex) {
logger.log(Level.WARNING, thread.getName() + " thread doesn't shut down", ex);
}
logger.log(Level.INFO, () -> thread.getName() + " shut down");
diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmConfigMonitor.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmConfigMonitor.java
index 0daf5cf327..45bcadc231 100644
--- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmConfigMonitor.java
+++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/AlarmConfigMonitor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018 Oak Ridge National Laboratory.
+ * Copyright (c) 2018-2023 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -24,7 +24,12 @@
* Not receiving any alarm client updates for a while
* likely means that we have a stable configuration.
*
- * This helper awaits such a pause in updates.
+ *
This helper first waits for an initial config message,
+ * allowing for the connection to take some time.
+ * Based on past experience, we then receive a flurry of
+ * config messages.
+ * By then awaiting a pause in configuration updates,
+ * we assume that a complete configuration snapshot has been received.
*
* @author Kay Kasemir
* @author Evan Smith
@@ -34,9 +39,11 @@ public class AlarmConfigMonitor
{
private final AlarmClient client;
private final ResettableTimeout timer;
+ private final long idle_secs;
private final AtomicInteger updates = new AtomicInteger();
- private final AlarmClientListener updateListener = new AlarmClientListener()
+ /** Listener to messages, resetting timer on config messages */
+ private final AlarmClientListener config_listener = new AlarmClientListener()
{
@Override
public void serverStateChanged(final boolean alive)
@@ -50,7 +57,7 @@ public void serverModeChanged(boolean maintenance_mode)
//NOP
}
- @Override
+ @Override
public void serverDisableNotifyChanged(boolean disable_notify)
{
//NOP
@@ -59,16 +66,16 @@ public void serverDisableNotifyChanged(boolean disable_notify)
@Override
public void itemAdded(final AlarmTreeItem> item)
{
- // Reset the timer when receiving update
- timer.reset();
+ // Reset the timer when receiving config update
+ timer.reset(idle_secs);
updates.incrementAndGet();
}
@Override
public void itemRemoved(final AlarmTreeItem> item)
{
- // Reset the timer when receiving update
- timer.reset();
+ // Reset the timer when receiving config update
+ timer.reset(idle_secs);
updates.incrementAndGet();
}
@@ -79,13 +86,15 @@ public void itemUpdated(final AlarmTreeItem> item)
}
};
- /** @param idle_secs Seconds after which we decide that there's a pause in configuration updates
+ /** @param initial_secs Seconds to wait for the initial config message (a 'connection' timeout)
+ * @param idle_secs Seconds after which we decide that there's a pause in configuration updates (assuming we received complete config snapshot)
* @param client AlarmClient to check for a pause in updates
*/
- public AlarmConfigMonitor(final long idle_secs, final AlarmClient client)
+ public AlarmConfigMonitor(final long initial_secs, final long idle_secs, final AlarmClient client)
{
this.client = client;
- timer = new ResettableTimeout(idle_secs);
+ this.idle_secs = idle_secs;
+ timer = new ResettableTimeout(initial_secs);
}
/** Wait for a pause in configuration updates
@@ -94,7 +103,7 @@ public AlarmConfigMonitor(final long idle_secs, final AlarmClient client)
*/
public void waitForPauseInUpdates(final long timeout) throws Exception
{
- client.addListener(updateListener);
+ client.addListener(config_listener);
if (! timer.awaitTimeout(timeout))
throw new Exception(timeout + " seconds have passed, I give up waiting for updates to subside.");
// Reset the counter to count any updates received after we decide to continue.
@@ -110,7 +119,7 @@ public int getCount()
/** Call when no longer interested in checking updates */
public void dispose()
{
- client.removeListener(updateListener);
+ client.removeListener(config_listener);
timer.shutdown();
}
}
diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java
index d3dcf4362c..17fd53d4a3 100644
--- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java
+++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/client/KafkaHelper.java
@@ -31,6 +31,7 @@
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
+import org.phoebus.applications.alarm.AlarmSystem;
/** Alarm client model
*
@@ -117,13 +118,12 @@ public static Producer connectProducer(final String kafka_server
kafka_props.put("bootstrap.servers", kafka_servers);
// Collect messages for 20ms until sending them out as a batch
kafka_props.put("linger.ms", 20);
+ kafka_props.put("max.block.ms", AlarmSystem.max_block_ms == 0 ? 10000 : AlarmSystem.max_block_ms);
// Write String key, value
final Serializer serializer = new StringSerializer();
- final Producer producer = new KafkaProducer<>(kafka_props, serializer, serializer);
-
- return producer;
+ return new KafkaProducer<>(kafka_props, serializer, serializer);
}
/**
@@ -131,7 +131,7 @@ public static Producer connectProducer(final String kafka_server
* @param kafka_servers - Sever to connect to.
* @param topics List of topics to aggregate.
* @param aggregate_topic - Name of topic to aggregate to.
- * @param kafka_props File name to load additional settings for the kafka stream
+ * @param properties_file File name to load additional settings for the kafka stream
* @return aggregate_stream - KafkaStreams
* @author Evan Smith
*/
@@ -162,7 +162,7 @@ static public Properties loadPropsFromFile(String filePath) {
logger.fine("loading file from path: " + filePath);
Properties properties = new Properties();
if(filePath != null && !filePath.isBlank()){
- try(FileInputStream file = new FileInputStream(filePath);){
+ try(FileInputStream file = new FileInputStream(filePath)){
properties.load(file);
} catch(IOException e) {
logger.log(Level.SEVERE, "failed to load kafka properties", e);
diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/messages/AlarmMessageUtil.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/messages/AlarmMessageUtil.java
index 1ad0393a25..3f1dc0b8e6 100644
--- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/messages/AlarmMessageUtil.java
+++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/messages/AlarmMessageUtil.java
@@ -45,6 +45,9 @@ public class AlarmMessageUtil implements Serializable{
// Object mapper for all other alarm messages
@JsonIgnore
static final ObjectMapper objectMapper = new ObjectMapper();
+ static {
+ objectMapper.registerModule(new JavaTimeModule());
+ }
private static class AlarmStateJsonMessage {
@JsonIgnore
diff --git a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/model/EnabledState.java b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/model/EnabledState.java
index e1cc86b99e..ca4dab087b 100644
--- a/app/alarm/model/src/main/java/org/phoebus/applications/alarm/model/EnabledState.java
+++ b/app/alarm/model/src/main/java/org/phoebus/applications/alarm/model/EnabledState.java
@@ -54,7 +54,7 @@ public int hashCode()
{
final int prime = 31;
final int enabled_check = this.enabled ? 1 : 0;
- int result = enabled_date.hashCode();
+ int result = enabled_date == null ? 0 : enabled_date.hashCode();
result = prime * result + enabled_check;
return result;
}
diff --git a/app/alarm/model/src/main/resources/alarm_preferences.properties b/app/alarm/model/src/main/resources/alarm_preferences.properties
index 6d418ef185..d2ba480fed 100644
--- a/app/alarm/model/src/main/resources/alarm_preferences.properties
+++ b/app/alarm/model/src/main/resources/alarm_preferences.properties
@@ -5,13 +5,15 @@
# Kafka Server host:port
server=localhost:9092
-# A file to configure the properites of kafka clients
+# A file to configure the properties of kafka clients
kafka_properties=
-# Name of alarm tree root
+# Name of alarm tree root.
+# Configures the alarm configuration used by the alarm server.
+# For the UI, it sets the default alarm configuration.
config_name=Accelerator
-# Names of selectable alarm configurations
+# Names of selectable alarm configurations for UI.
# The `config_name` will be used as the default for newly opened tools,
# and if `config_names` is empty, it remains the only option.
# When one or more comma-separated configurations are listed,
@@ -22,6 +24,8 @@ config_names=Accelerator, Demo
# Timeout in seconds for initial PV connection
connection_timeout=30
+# Timeout in seconds for "sevrpv:" updates
+severity_pv_timeout=5
## Area Panel
@@ -136,3 +140,6 @@ shelving_options=1 hour, 6 hours, 12 hours, 1 day, 7 days, 30 days
#
# Format: M1=Value1, M2=Value2
macros=TOP=/home/controls/displays,WEBROOT=http://localhost/controls/displays
+
+# Max time in ms a producer call will block.
+max_block_ms=10000
\ No newline at end of file
diff --git a/app/alarm/model/src/test/java/org/phoebus/applications/alarm/AlarmModelSnapshotDemo.java b/app/alarm/model/src/test/java/org/phoebus/applications/alarm/AlarmModelSnapshotDemo.java
index 409144f1ae..b682be0603 100644
--- a/app/alarm/model/src/test/java/org/phoebus/applications/alarm/AlarmModelSnapshotDemo.java
+++ b/app/alarm/model/src/test/java/org/phoebus/applications/alarm/AlarmModelSnapshotDemo.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018 Oak Ridge National Laboratory.
+ * Copyright (c) 2018-2023 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -20,25 +20,25 @@
@SuppressWarnings("nls")
public class AlarmModelSnapshotDemo
{
- @Test
- public void testAlarmModelWriter() throws Exception
- {
- // Get alarm configuration
- final AlarmClient client = new AlarmClient(AlarmDemoSettings.SERVERS, AlarmDemoSettings.ROOT, AlarmDemoSettings.KAFKA_PROPERTIES_FILE);
-
- System.out.println("Wait for stable configuration, i.e. no changes for 4 seconds...");
-
- // Wait until we have a snapshot, i.e. no more changes for 4 seconds
- final AlarmConfigMonitor monitor = new AlarmConfigMonitor(4, client);
- monitor.waitForPauseInUpdates(30);
-
- System.out.println("Alarm configuration:");
-
-
- final ByteArrayOutputStream buf = new ByteArrayOutputStream();
- final XmlModelWriter xmlWriter = new XmlModelWriter(buf);
- xmlWriter.write(client.getRoot());
- xmlWriter.close();
+ @Test
+ public void testAlarmModelWriter() throws Exception
+ {
+ // Get alarm configuration
+ final AlarmClient client = new AlarmClient(AlarmDemoSettings.SERVERS, AlarmDemoSettings.ROOT, AlarmDemoSettings.KAFKA_PROPERTIES_FILE);
+ client.start();
+
+ System.out.println("Wait 10 secs for connection, then for stable configuration, i.e. no changes for 4 seconds...");
+ final long start = System.currentTimeMillis();
+
+ final AlarmConfigMonitor monitor = new AlarmConfigMonitor(10, 4, client);
+ monitor.waitForPauseInUpdates(30);
+ final double secs = (System.currentTimeMillis() - start) / 1000.0;
+ System.out.format("Alarm configuration after %.3f seconds:\n\n", secs);
+
+ final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ final XmlModelWriter xmlWriter = new XmlModelWriter(buf);
+ xmlWriter.write(client.getRoot());
+ xmlWriter.close();
final String xml = buf.toString();
System.out.println(xml);
@@ -47,5 +47,5 @@ public void testAlarmModelWriter() throws Exception
System.out.println("Bummer, there were " + changes + " updates to the configuration, might have to try this again...");
monitor.dispose();
client.shutdown();
- }
+ }
}
diff --git a/app/alarm/model/src/test/java/org/phoebus/applications/alarm/ResettableTimeoutTest.java b/app/alarm/model/src/test/java/org/phoebus/applications/alarm/ResettableTimeoutTest.java
index 24eb6b2ada..0488abd727 100644
--- a/app/alarm/model/src/test/java/org/phoebus/applications/alarm/ResettableTimeoutTest.java
+++ b/app/alarm/model/src/test/java/org/phoebus/applications/alarm/ResettableTimeoutTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012-2018 Oak Ridge National Laboratory.
+ * Copyright (c) 2012-2023 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -61,4 +61,23 @@ public void testReset()
assertThat(timer.awaitTimeout(6), equalTo(true));
timer.shutdown();
}
+
+ @Test
+ public void testChangingTimeout()
+ {
+ ResettableTimeout timer = new ResettableTimeout(4);
+ System.out.println("Should time out in 4 secs");
+ assertThat(timer.awaitTimeout(8), equalTo(true));
+ timer.shutdown();
+
+
+ timer = new ResettableTimeout(4);
+ System.out.println("Now resetting after just 1 second to a 1 second timeout...");
+ assertThat(timer.awaitTimeout(1), equalTo(false));
+ timer.reset(1);
+
+ System.out.println(".. and expecting time out in 1 second...");
+ assertThat(timer.awaitTimeout(4), equalTo(true));
+ timer.shutdown();
+ }
}
diff --git a/app/alarm/pom.xml b/app/alarm/pom.xml
index 321627d9bd..a5b1307493 100644
--- a/app/alarm/pom.xml
+++ b/app/alarm/pom.xml
@@ -5,7 +5,7 @@
org.phoebus
app
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
model
diff --git a/app/alarm/ui/pom.xml b/app/alarm/ui/pom.xml
index 5aad4eaab0..d36aa6b31f 100644
--- a/app/alarm/ui/pom.xml
+++ b/app/alarm/ui/pom.xml
@@ -3,7 +3,7 @@
org.phoebus
app-alarm
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
app-alarm-ui
@@ -22,27 +22,27 @@
org.phoebus
core-framework
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
org.phoebus
core-types
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
org.phoebus
core-util
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
org.phoebus
core-ui
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
org.phoebus
app-alarm-model
- 4.7.2-SNAPSHOT
+ 4.7.4-SNAPSHOT
net.sf.sociaal
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java
index 92760cdb67..e62af387bd 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmUI.java
@@ -50,6 +50,19 @@ public class AlarmUI
createColor(Preferences.undefined_severity_text_color) // UNDEFINED
};
+ private static final Color[] alarm_area_panel_severity_colors = new Color[]
+ {
+ createColor(Preferences.alarm_area_panel_ok_severity_text_color), // OK
+ createColor(Preferences.alarm_area_panel_minor_severity_text_color) .deriveColor(0, 1.0, ADJUST, 1.0), // MINOR_ACK
+ createColor(Preferences.alarm_area_panel_major_severity_text_color) .deriveColor(0, 1.0, ADJUST, 1.0), // MAJOR_ACK
+ createColor(Preferences.alarm_area_panel_invalid_severity_text_color) .deriveColor(0, 1.0, ADJUST, 1.0), // INVALID_ACK
+ createColor(Preferences.alarm_area_panel_undefined_severity_text_color).deriveColor(0, 1.0, ADJUST, 1.0), // UNDEFINED_ACK
+ createColor(Preferences.alarm_area_panel_minor_severity_text_color), // MINOR
+ createColor(Preferences.alarm_area_panel_major_severity_text_color), // MAJOR
+ createColor(Preferences.alarm_area_panel_invalid_severity_text_color), // INVALID
+ createColor(Preferences.alarm_area_panel_undefined_severity_text_color) // UNDEFINED
+ };
+
private static Color createColor(int[] rgb)
{
if (rgb.length == 3)
@@ -75,27 +88,53 @@ else if (rgb.length == 4)
private static final Background[] severity_backgrounds = new Background[]
{
new Background(new BackgroundFill(createColor(Preferences.ok_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // OK
- new Background(new BackgroundFill(createColor(Preferences.minor_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR_ACK
- new Background(new BackgroundFill(createColor(Preferences.major_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR_ACK
- new Background(new BackgroundFill(createColor(Preferences.invalid_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID_ACK
+ new Background(new BackgroundFill(createColor(Preferences.minor_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR_ACK
+ new Background(new BackgroundFill(createColor(Preferences.major_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR_ACK
+ new Background(new BackgroundFill(createColor(Preferences.invalid_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID_ACK
new Background(new BackgroundFill(createColor(Preferences.undefined_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // UNDEFINED_ACK
- new Background(new BackgroundFill(createColor(Preferences.minor_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR
- new Background(new BackgroundFill(createColor(Preferences.major_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR
- new Background(new BackgroundFill(createColor(Preferences.invalid_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID
- new Background(new BackgroundFill(createColor(Preferences.undefined_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // UNDEFINED
+ new Background(new BackgroundFill(createColor(Preferences.minor_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR
+ new Background(new BackgroundFill(createColor(Preferences.major_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR
+ new Background(new BackgroundFill(createColor(Preferences.invalid_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID
+ new Background(new BackgroundFill(createColor(Preferences.undefined_severity_background_color), CornerRadii.EMPTY, Insets.EMPTY)), // UNDEFINED
};
- private static final Background[] legacy_table_severity_backgrounds = new Background[]
- {
- new Background(new BackgroundFill(createColor(Preferences.ok_severity_text_color), CornerRadii.EMPTY, Insets.EMPTY)), // OK
- new Background(new BackgroundFill(createColor(Preferences.minor_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR_ACK
- new Background(new BackgroundFill(createColor(Preferences.major_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR_ACK
- new Background(new BackgroundFill(createColor(Preferences.invalid_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID_ACK
- new Background(new BackgroundFill(createColor(Preferences.undefined_severity_text_color).deriveColor(0, ADJUST, 1.0, 1.0), CornerRadii.EMPTY, Insets.EMPTY)), // UNDEFINED_ACK
- new Background(new BackgroundFill(createColor(Preferences.minor_severity_text_color), CornerRadii.EMPTY, Insets.EMPTY)), // MINOR
- new Background(new BackgroundFill(createColor(Preferences.major_severity_text_color), CornerRadii.EMPTY, Insets.EMPTY)), // MAJOR
- new Background(new BackgroundFill(createColor(Preferences.invalid_severity_text_color), CornerRadii.EMPTY, Insets.EMPTY)), // INVALID
- new Background(new BackgroundFill(createColor(Preferences.undefined_severity_text_color), CornerRadii.EMPTY, Insets.EMPTY)), // UNDEFINED
+ private static final Color[] severity_background_colors = new Color[]
+ {
+ createColor(Preferences.ok_severity_background_color), // OK
+ createColor(Preferences.minor_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MINOR_ACK
+ createColor(Preferences.major_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MAJOR_ACK
+ createColor(Preferences.invalid_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // INVALID_ACK
+ createColor(Preferences.undefined_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), // UNDEFINED_ACK
+ createColor(Preferences.minor_severity_background_color) , // MINOR
+ createColor(Preferences.major_severity_background_color) , // MAJOR
+ createColor(Preferences.invalid_severity_background_color) , // INVALID
+ createColor(Preferences.undefined_severity_background_color) , // UNDEFINED
+ };
+
+ private static final Color[] alarm_area_panel_severity_backgrounds = new Color[]
+ {
+ createColor(Preferences.alarm_area_panel_ok_severity_background_color), // OK
+ createColor(Preferences.alarm_area_panel_minor_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MINOR_ACK
+ createColor(Preferences.alarm_area_panel_major_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MAJOR_ACK
+ createColor(Preferences.alarm_area_panel_invalid_severity_background_color) .deriveColor(0, ADJUST, 1.0, 1.0), // INVALID_ACK
+ createColor(Preferences.alarm_area_panel_undefined_severity_background_color).deriveColor(0, ADJUST, 1.0, 1.0), // UNDEFINED_ACK
+ createColor(Preferences.alarm_area_panel_minor_severity_background_color), // MINOR
+ createColor(Preferences.alarm_area_panel_major_severity_background_color), // MAJOR
+ createColor(Preferences.alarm_area_panel_invalid_severity_background_color), // INVALID
+ createColor(Preferences.alarm_area_panel_undefined_severity_background_color), // UNDEFINED
+ };
+
+ private static final Color[] legacy_table_severity_backgrounds = new Color[]
+ {
+ createColor(Preferences.ok_severity_text_color), // OK
+ createColor(Preferences.minor_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MINOR_ACK
+ createColor(Preferences.major_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), // MAJOR_ACK
+ createColor(Preferences.invalid_severity_text_color) .deriveColor(0, ADJUST, 1.0, 1.0), // INVALID_ACK
+ createColor(Preferences.undefined_severity_text_color).deriveColor(0, ADJUST, 1.0, 1.0), // UNDEFINED_ACK
+ createColor(Preferences.minor_severity_text_color), // MINOR
+ createColor(Preferences.major_severity_text_color), // MAJOR
+ createColor(Preferences.invalid_severity_text_color), // INVALID
+ createColor(Preferences.undefined_severity_text_color), // UNDEFINED
};
@@ -108,7 +147,14 @@ else if (rgb.length == 4)
public static Color getColor(final SeverityLevel severity)
{
return severity_colors[severity.ordinal()];
+ }
+ /** @param severity {@link SeverityLevel}
+ * @return Color
+ */
+ public static Color getAlarmAreaPanelColor(final SeverityLevel severity)
+ {
+ return alarm_area_panel_severity_colors[severity.ordinal()];
}
/** @param severity {@link SeverityLevel}
@@ -119,6 +165,14 @@ public static Image getIcon(final SeverityLevel severity)
return severity_icons[severity.ordinal()];
}
+ /** @param severity {@link SeverityLevel}
+ * @return Color, may be null
+ */
+ public static Color getBackgroundColor(final SeverityLevel severity)
+ {
+ return severity_background_colors[severity.ordinal()];
+ }
+
/** @param severity {@link SeverityLevel}
* @return Background, may be null
*/
@@ -127,17 +181,26 @@ public static Background getBackground(final SeverityLevel severity)
return severity_backgrounds[severity.ordinal()];
}
+
/** @param severity {@link SeverityLevel}
* @return Background, may be null
*/
- public static Background getLegacyTableBackground(final SeverityLevel severity)
+ public static Color getLegacyTableBackground(final SeverityLevel severity)
{
return legacy_table_severity_backgrounds[severity.ordinal()];
}
+ /** @param severity {@link SeverityLevel}
+ * @return Background, may be null
+ */
+ public static Color getAlarmAreaPanelBackgroundColor(final SeverityLevel severity)
+ {
+ return alarm_area_panel_severity_backgrounds[severity.ordinal()];
+ }
+
/** Verify authorization, qualified by model's current config
* @param model Alarm client model
- * @param auto Authorization name
+ * @param authorization Authorization name
* @return true
if the user has authorization
*/
private static boolean haveQualifiedAuthorization(final AlarmClient model, final String authorization)
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java
index 389fb7e00a..f36654a84d 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/Messages.java
@@ -15,6 +15,8 @@ public class Messages
public static String acknowledgeFailed;
public static String addComponentFailed;
+ public static String disableAlarmFailed;
+ public static String enableAlarmFailed;
public static String moveItemFailed;
public static String removeComponentFailed;
public static String renameItemFailed;
diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
index f757130562..6bc83d1a10 100644
--- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
+++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java
@@ -17,12 +17,21 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.BorderStroke;
+import javafx.scene.layout.BorderStrokeStyle;
+import javafx.scene.layout.BorderWidths;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
import org.phoebus.applications.alarm.AlarmSystem;
import org.phoebus.applications.alarm.client.AlarmClient;
import org.phoebus.applications.alarm.client.AlarmClientListener;
import org.phoebus.applications.alarm.model.AlarmTreeItem;
import org.phoebus.applications.alarm.model.SeverityLevel;
import org.phoebus.applications.alarm.ui.AlarmUI;
+import org.phoebus.ui.javafx.JFXUtil;
import org.phoebus.ui.javafx.UpdateThrottle;
import javafx.application.Platform;
@@ -32,16 +41,6 @@
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
-import javafx.scene.layout.Background;
-import javafx.scene.layout.BackgroundFill;
-import javafx.scene.layout.Border;
-import javafx.scene.layout.BorderStroke;
-import javafx.scene.layout.BorderStrokeStyle;
-import javafx.scene.layout.BorderWidths;
-import javafx.scene.layout.CornerRadii;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
@@ -87,12 +86,16 @@ public class AlarmAreaView extends StackPane implements AlarmClientListener
private final Font font = new Font(AlarmSystem.alarm_area_font_size);
private final Border border = new Border(new BorderStroke(Color.BLACK, style, radii, new BorderWidths(2)));
+ private final String alarmConfigName;
+
/** @param model Model */
public AlarmAreaView(final AlarmClient model)
{
if (model.isRunning())
throw new IllegalStateException();
+ alarmConfigName = model.getRoot().getName();
+
grid.setHgap(AlarmSystem.alarm_area_gap);
grid.setVgap(AlarmSystem.alarm_area_gap);
grid.setPadding(new Insets(AlarmSystem.alarm_area_gap));
@@ -234,20 +237,15 @@ private void updateItem(final String item_name)
logger.log(Level.WARNING, "Cannot update unknown alarm area item " + item_name);
return;
}
- final SeverityLevel severity = areaFilter.getSeverity(item_name);
- final Color color = AlarmUI.getColor(severity);
- view_item.setBackground(new Background(new BackgroundFill(color, radii, Insets.EMPTY)));
- if (color.getBrightness() >= 0.5)
- view_item.setTextFill(Color.BLACK);
- else
- view_item.setTextFill(Color.WHITE);
+ SeverityLevel severityLevel = areaFilter.getSeverity(item_name);
+ view_item.setStyle("-fx-alignment: center; -fx-border-color: black; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-insets: 1; -fx-background-radius: 10; -fx-text-fill: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelColor(severityLevel)) + "; -fx-background-color: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelBackgroundColor(severityLevel)));
}
private void createContextMenu()
{
final ObservableList