package org.yamcs.alarms; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.yamcs.ConfigurationException; import org.yamcs.InvalidIdentification; import org.yamcs.Processor; import org.yamcs.api.EventProducer; import org.yamcs.api.EventProducerFactory; import org.yamcs.parameter.ParameterConsumer; import org.yamcs.parameter.ParameterRequestManagerImpl; import org.yamcs.parameter.ParameterValue; import org.yamcs.protobuf.Pvalue.MonitoringResult; import org.yamcs.protobuf.Pvalue.RangeCondition; import org.yamcs.xtce.AlarmReportType; import org.yamcs.xtce.AlarmType; import org.yamcs.xtce.Parameter; import org.yamcs.xtce.ParameterType; import org.yamcs.xtce.XtceDb; import org.yamcs.xtceproc.XtceDbFactory; import com.google.common.util.concurrent.AbstractService; /** * Generates realtime alarm events automatically, by subscribing to all relevant * parameters. */ public class AlarmReporter extends AbstractService implements ParameterConsumer { private EventProducer eventProducer; private Map<Parameter, ActiveAlarm> activeAlarms=new HashMap<Parameter, ActiveAlarm>(); // Last value of each param (for detecting changes in value) private Map<Parameter, ParameterValue> lastValuePerParameter=new HashMap<Parameter, ParameterValue>(); final String yamcsInstance; final String yprocName; public AlarmReporter(String yamcsInstance) { this(yamcsInstance, "realtime"); } public AlarmReporter(String yamcsInstance, String yprocName) { this.yamcsInstance = yamcsInstance; this.yprocName = yprocName; eventProducer=EventProducerFactory.getEventProducer(yamcsInstance); eventProducer.setSource("AlarmChecker"); } @Override public void doStart() { Processor yproc = Processor.getInstance(yamcsInstance, yprocName); if(yproc==null) { ConfigurationException e = new ConfigurationException("Cannot find a yproc '"+yprocName+"' in instance '"+yamcsInstance+"'"); notifyFailed(e); return; } ParameterRequestManagerImpl prm = yproc.getParameterRequestManager(); prm.getAlarmChecker().enableReporting(this); // Auto-subscribe to parameters with alarms Set<Parameter> requiredParameters=new HashSet<Parameter>(); try { XtceDb xtcedb=XtceDbFactory.getInstance(yamcsInstance); for (Parameter parameter:xtcedb.getParameters()) { ParameterType ptype=parameter.getParameterType(); if(ptype!=null && ptype.hasAlarm()) { requiredParameters.add(parameter); Set<Parameter> dependentParameters = ptype.getDependentParameters(); if(dependentParameters!=null) { requiredParameters.addAll(dependentParameters); } } } } catch(ConfigurationException e) { notifyFailed(e); return; } if(!requiredParameters.isEmpty()) { List<Parameter> params=new ArrayList<Parameter>(requiredParameters); // Now that we have uniques.. try { prm.addRequest(params, this); } catch(InvalidIdentification e) { throw new RuntimeException("Could not register dependencies for alarms", e); } } notifyStarted(); } @Override public void doStop() { notifyStopped(); } @Override public void updateItems(int subscriptionId, List<ParameterValue> items) { // Nothing. The real business of sending events, happens while checking the alarms // because that's where we have easy access to the XTCE definition of the active // alarm. The PRM is only used to signal the parameter subscriptions. } /** * Sends an event if an alarm condition for the active context has been * triggered <tt>minViolations</tt> times. This configuration does not * affect events for parameters that go back to normal, or that change * severity levels while the alarm is already active. */ public void reportNumericParameterEvent(ParameterValue pv, AlarmType alarmType, int minViolations) { boolean sendUpdateEvent=false; if(alarmType == null) { // TODO: do something with more interesting return; } if(alarmType.getAlarmReportType()==AlarmReportType.ON_VALUE_CHANGE) { ParameterValue oldPv=lastValuePerParameter.get(pv.getParameter()); if(oldPv!=null && hasChanged(oldPv, pv)) { sendUpdateEvent=true; } lastValuePerParameter.put(pv.getParameter(), pv); } if(pv.getMonitoringResult()==MonitoringResult.IN_LIMITS) { if(activeAlarms.containsKey(pv.getParameter())) { eventProducer.sendInfo("NORMAL", "Parameter "+pv.getParameter().getQualifiedName()+" is back to normal"); activeAlarms.remove(pv.getParameter()); } } else { // out of limits MonitoringResult previousMonitoringResult=null; ActiveAlarm activeAlarm=activeAlarms.get(pv.getParameter()); if(activeAlarm==null || activeAlarm.alarmType!=alarmType) { activeAlarm=new ActiveAlarm(alarmType, pv.getMonitoringResult()); } else { previousMonitoringResult=activeAlarm.monitoringResult; activeAlarm.monitoringResult=pv.getMonitoringResult(); activeAlarm.violations++; } if(activeAlarm.violations==minViolations || (activeAlarm.violations>minViolations && previousMonitoringResult!=activeAlarm.monitoringResult)) { sendUpdateEvent=true; } activeAlarms.put(pv.getParameter(), activeAlarm); } if(sendUpdateEvent) { sendValueChangeEvent(pv); } } public void reportEnumeratedParameterEvent(ParameterValue pv, AlarmType alarmType, int minViolations) { boolean sendUpdateEvent=false; if(alarmType == null) { // TODO: something more interesting return; } if(alarmType.getAlarmReportType()==AlarmReportType.ON_VALUE_CHANGE) { ParameterValue oldPv=lastValuePerParameter.get(pv.getParameter()); if(oldPv!=null && hasChanged(oldPv, pv)) { sendUpdateEvent=true; } lastValuePerParameter.put(pv.getParameter(), pv); } if(pv.getMonitoringResult()==MonitoringResult.IN_LIMITS) { if(activeAlarms.containsKey(pv.getParameter())) { eventProducer.sendInfo("NORMAL", "Parameter "+pv.getParameter().getQualifiedName()+" is back to a normal state ("+pv.getEngValue().getStringValue()+")"); activeAlarms.remove(pv.getParameter()); } } else { // out of limits MonitoringResult previousMonitoringResult=null; ActiveAlarm activeAlarm=activeAlarms.get(pv.getParameter()); if(activeAlarm==null || activeAlarm.alarmType!=alarmType) { activeAlarm=new ActiveAlarm(alarmType, pv.getMonitoringResult()); } else { previousMonitoringResult=activeAlarm.monitoringResult; activeAlarm.monitoringResult=pv.getMonitoringResult(); activeAlarm.violations++; } if(activeAlarm.violations==minViolations || (activeAlarm.violations>minViolations&& previousMonitoringResult!=activeAlarm.monitoringResult)) { sendUpdateEvent=true; } activeAlarms.put(pv.getParameter(), activeAlarm); } if(sendUpdateEvent) { sendStateChangeEvent(pv); } } private void sendValueChangeEvent(ParameterValue pv) { if(pv.getMonitoringResult() == null) { eventProducer.sendInfo("no monitoring", "Parameter "+pv.getParameter().getQualifiedName()+" has changed to value "+pv.getEngValue()); return; } if (pv.getMonitoringResult() == MonitoringResult.IN_LIMITS) { eventProducer.sendInfo(pv.getMonitoringResult().toString(), "Parameter "+pv.getParameter().getQualifiedName()+" has changed to value "+pv.getEngValue()); } else { String message; if (pv.getRangeCondition() == RangeCondition.LOW) { message = "Parameter "+pv.getParameter().getQualifiedName()+" is too low"; } else if (pv.getRangeCondition() == RangeCondition.HIGH) { message = "Parameter "+pv.getParameter().getQualifiedName()+" is too high"; } else { throw new IllegalStateException("Unexpected range condition: "+pv.getRangeCondition()); } switch (pv.getMonitoringResult()) { case WATCH: case WARNING: eventProducer.sendWarning(pv.getMonitoringResult().toString(), message); break; case DISTRESS: case CRITICAL: case SEVERE: eventProducer.sendError(pv.getMonitoringResult().toString(), message); break; default: throw new IllegalStateException("Unexpected monitoring result: "+pv.getMonitoringResult()); } } } private void sendStateChangeEvent(ParameterValue pv) { switch(pv.getMonitoringResult()) { case WATCH: case WARNING: eventProducer.sendWarning(pv.getMonitoringResult().toString(), "Parameter "+pv.getParameter().getQualifiedName()+" transitioned to state "+pv.getEngValue().getStringValue()); break; case DISTRESS: case CRITICAL: case SEVERE: eventProducer.sendError(pv.getMonitoringResult().toString(), "Parameter "+pv.getParameter().getQualifiedName()+" transitioned to state "+pv.getEngValue().getStringValue()); break; case IN_LIMITS: eventProducer.sendInfo(pv.getMonitoringResult().toString(), "Parameter "+pv.getParameter().getQualifiedName()+" transitioned to state "+pv.getEngValue().getStringValue()); break; default: throw new IllegalStateException("Unexpected monitoring result: "+pv.getMonitoringResult()); } } private boolean hasChanged(ParameterValue pvOld, ParameterValue pvNew) { // Crude string value comparison. return !pvOld.getEngValue().equals(pvNew.getEngValue()); } private static class ActiveAlarm { MonitoringResult monitoringResult; AlarmType alarmType; int violations=1; ParameterValue lastValue; ActiveAlarm(AlarmType alarmType, MonitoringResult monitoringResult) { this.alarmType=alarmType; this.monitoringResult=monitoringResult; } } }