package org.yamcs.alarms;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.ConfigurationException;
import org.yamcs.parameter.ParameterValue;
import org.yamcs.api.EventProducer;
import org.yamcs.api.EventProducerFactory;
import org.yamcs.protobuf.Pvalue.MonitoringResult;
import org.yamcs.xtce.Parameter;
import org.yamcs.yarch.DataType;
import org.yamcs.yarch.Stream;
import org.yamcs.yarch.TupleDefinition;
import org.yamcs.yarch.YarchDatabase;
import com.google.common.util.concurrent.AbstractService;
/**
* Maintains a list of active alarms.
*/
public class AlarmServer extends AbstractService {
private EventProducer eventProducer;
private Map<Parameter, ActiveAlarm> activeAlarms=new ConcurrentHashMap<>();
// Last value of each param (for detecting changes in value)
final String yamcsInstance;
private final Logger log = LoggerFactory.getLogger(AlarmServer.class);
private Set<AlarmListener> alarmListeners = new HashSet<>();
private String streamName;
static public final TupleDefinition ALARM_TUPLE_DEFINITION=new TupleDefinition();
//user time, parameter name sequence number and event
static {
ALARM_TUPLE_DEFINITION.addColumn("triggerTime", DataType.TIMESTAMP);
ALARM_TUPLE_DEFINITION.addColumn("parameter", DataType.STRING);
ALARM_TUPLE_DEFINITION.addColumn("seqNum", DataType.INT);
ALARM_TUPLE_DEFINITION.addColumn("event", DataType.STRING);
}
/**
* alarm server without a listener (used for unit tests )
* @param yamcsInstance
*/
AlarmServer(String yamcsInstance) {
this.yamcsInstance = yamcsInstance;
eventProducer=EventProducerFactory.getEventProducer(yamcsInstance);
eventProducer.setSource("AlarmServer");
}
/**
* Creates an alarm server that pushes all alarms to a stream
* @param yamcsInstance
* @param streamName
*/
public AlarmServer(String yamcsInstance, String streamName) {
this.yamcsInstance = yamcsInstance;
eventProducer=EventProducerFactory.getEventProducer(yamcsInstance);
eventProducer.setSource("AlarmServer");
this.streamName = streamName;
}
/**
* Register for alarm notices
* @return the current set of active alarms
*/
public Map<Parameter, ActiveAlarm> subscribe(AlarmListener listener) {
alarmListeners.add(listener);
return activeAlarms;
}
public void unsubscribe(AlarmListener listener) {
alarmListeners.remove(listener);
}
/**
* Returns the current set of active alarms
*/
public Map<Parameter, ActiveAlarm> getActiveAlarms() {
return activeAlarms;
}
@Override
public void doStart() {
if(streamName!=null) {
YarchDatabase ydb = YarchDatabase.getInstance(yamcsInstance);
Stream s = ydb.getStream(streamName);
if(s==null) {
notifyFailed(new ConfigurationException("Cannot find a stream named '"+streamName+"'"));
return;
}
subscribe(new AlarmStreamer(s));
}
notifyStarted();
}
@Override
public void doStop() {
notifyStopped();
}
public void update(ParameterValue pv, int minViolations) {
update(pv, minViolations, false);
}
public void update(ParameterValue pv, int minViolations, boolean autoAck) {
Parameter param = pv.getParameter();
ActiveAlarm activeAlarm=activeAlarms.get(pv.getParameter());
if((activeAlarm==null) && (pv.getMonitoringResult()==MonitoringResult.IN_LIMITS
|| pv.getMonitoringResult() == null
|| pv.getMonitoringResult() == MonitoringResult.DISABLED)) {
return;
}
if(pv.getMonitoringResult()==MonitoringResult.IN_LIMITS
|| pv.getMonitoringResult() == MonitoringResult.DISABLED
|| pv.getMonitoringResult() == null) {
if (activeAlarm.violations < minViolations) {
log.debug("Clearing glitch for {}", param.getQualifiedName());
activeAlarms.remove(param);
return;
}
activeAlarm.currentValue = pv;
if ((activeAlarm.acknowledged) || (activeAlarm.autoAcknowledge)) {
for (AlarmListener l : alarmListeners) {
l.notifyCleared(activeAlarm);
}
activeAlarms.remove(pv.getParameter());
} else {
for (AlarmListener l : alarmListeners) {
l.notifyParameterValueUpdate(activeAlarm);
}
}
} else { // out of limits
if(activeAlarm==null) {
activeAlarm=new ActiveAlarm(pv, autoAck);
activeAlarms.put(pv.getParameter(), activeAlarm);
} else {
activeAlarm.currentValue = pv;
activeAlarm.violations++;
}
if(activeAlarm.violations < minViolations) {
return;
}
if(activeAlarm.violations == minViolations) {
for (AlarmListener l : alarmListeners) {
l.notifyTriggered(activeAlarm);
}
} else {
if(moreSevere(pv.getMonitoringResult(), activeAlarm.mostSevereValue.getMonitoringResult())){
activeAlarm.mostSevereValue = pv;
for (AlarmListener l : alarmListeners) {
l.notifySeverityIncrease(activeAlarm);
}
}
for (AlarmListener l : alarmListeners) {
l.notifyParameterValueUpdate(activeAlarm);
}
}
activeAlarms.put(pv.getParameter(), activeAlarm);
}
}
public ActiveAlarm acknowledge(Parameter p, int id, String username, long ackTime, String message) throws CouldNotAcknowledgeAlarmException {
ActiveAlarm aa = activeAlarms.get(p);
if(aa==null) {
throw new CouldNotAcknowledgeAlarmException("Parameter " + p.getQualifiedName() + " is not in state of alarm");
}
if(aa.id!=id) {
log.warn("Got acknowledge for parameter {} but the id does not match", p);
throw new CouldNotAcknowledgeAlarmException("Alarm Id " + id + " does not match parameter " + p.getQualifiedName());
}
aa.acknowledged = true;
aa.usernameThatAcknowledged = username;
aa.acknowledgeTime = ackTime;
aa.message = message;
alarmListeners.forEach(l -> l.notifyAcknowledged(aa));
if(aa.currentValue.getMonitoringResult()==MonitoringResult.IN_LIMITS
|| aa.currentValue.getMonitoringResult()==MonitoringResult.DISABLED
|| aa.currentValue.getMonitoringResult()==null) {
activeAlarms.remove(p);
alarmListeners.forEach(l -> l.notifyCleared(aa));
}
return aa;
}
protected static boolean moreSevere(MonitoringResult mr1, MonitoringResult mr2) {
if (mr1 == mr2) return false;
switch (mr2) {
case WATCH:
if (mr1 == MonitoringResult.WARNING) {
return true;
}
// fall
case WARNING:
if (mr1 == MonitoringResult.DISTRESS) {
return true;
}
// fall
case DISTRESS:
if (mr1 == MonitoringResult.CRITICAL) {
return true;
}
// fall
case CRITICAL:
if (mr1 == MonitoringResult.SEVERE) {
return true;
}
default:
return false;
}
}
}