package services import ( "basic-sensor-receiver/integrations" "basic-sensor-receiver/models" "encoding/json" "time" ) type AlertsService struct { ctx *Context } func evaluateSensorValueCondition(condition *models.AlertConditionSensorValue, value *sensorValue) bool { switch condition.Condition { case "less": return value.Value < condition.Value case "more": return value.Value > condition.Value case "equal": return value.Value == condition.Value } return false } func (s *AlertsService) EvaluateAlerts() error { alerts, err := s.GetList() if err != nil { return err } for _, alert := range alerts { err := s.EvaluateAlert(alert) if err != nil { return err } } return nil } func unitToSeconds(unit string) float64 { switch unit { case "s": return 1 case "m": return 60 case "h": return 3600 case "d": return 86400 } return 0 } func (s *AlertsService) EvaluateAlert(alert *models.AlertItem) error { condition := map[string]interface{}{} err := json.Unmarshal([]byte(alert.Condition), &condition) if err != nil { return err } newStatus := alert.LastStatus lastValue := float64(0) conditionMet := false sensorId := int64(-1) sensorValueCondition := models.AlertConditionSensorValue{} sensorLastContactCondition := models.AlertConditionSensorLastContact{} switch condition["type"].(string) { case "sensor_value": { sensorValueCondition = models.AlertConditionSensorValue{ SensorId: int64(condition["sensorId"].(float64)), Condition: condition["condition"].(string), Value: condition["value"].(float64), } value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix()) lastValue = value.Value sensorId = int64(sensorValueCondition.SensorId) if err != nil { return err } conditionMet = evaluateSensorValueCondition(&sensorValueCondition, value) break } case "sensor_last_contact": { sensorLastContactCondition := models.AlertConditionSensorLastContact{ SensorId: int64(condition["sensorId"].(float64)), Value: condition["value"].(float64), ValueUnit: condition["valueUnit"].(string), } value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix()) lastValue = float64(value.Timestamp) if err != nil { return err } conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit)) conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec break } } if conditionMet { if alert.LastStatus == "good" { newStatus = "pending" } else if alert.LastStatus == "pending" { if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval { newStatus = "alerting" } } } else { if alert.LastStatus == "alerting" { newStatus = "pending" } else if alert.LastStatus == "pending" { if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval { newStatus = "good" } } } if newStatus != alert.LastStatus { s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id) if newStatus == "alerting" { sensor, err := s.ctx.Services.Sensors.GetById(sensorId) if err != nil { return err } contactPoint, err := s.ctx.Services.ContactPoints.GetById(alert.ContactPointId) if err != nil { return err } dispatchService, err := contactPoint.getService(s.ctx) if err != nil { return err } err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{ Type: integrations.ContactPointEventAlertTriggered, AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{ Alert: alert, Sensor: sensor, SensorValueCondition: &sensorValueCondition, SensorLastContactCondition: &sensorLastContactCondition, LastValue: lastValue, }, }, contactPoint.TypeConfig) if err != nil { return err } } } return nil } func (s *AlertsService) GetList() ([]*models.AlertItem, error) { rows, err := s.ctx.DB.Query("SELECT id, contact_point_id, name, condition, trigger_interval, last_status, last_status_at FROM alerts ORDER BY name ASC") if err != nil { return nil, err } defer rows.Close() alerts := []*models.AlertItem{} for rows.Next() { alert := models.AlertItem{} err := rows.Scan(&alert.Id, &alert.ContactPointId, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt) if err != nil { return nil, err } alerts = append(alerts, &alert) } err = rows.Err() if err != nil { return nil, err } return alerts, nil } func (s *AlertsService) GetById(id int64) (*models.AlertItem, error) { alert := models.AlertItem{} row := s.ctx.DB.QueryRow("SELECT id, contact_point_id, name, condition, trigger_interval, last_status, last_status_at FROM alerts WHERE id = ?", id) err := row.Scan(&alert.Id, &alert.ContactPointId, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt) if err != nil { return nil, err } return &alert, nil } func (s *AlertsService) Create(contactPointId int64, name string, condition string, triggerInterval int64, customMessage string) (*models.AlertItem, error) { alert := models.AlertItem{ ContactPointId: contactPointId, Name: name, Condition: condition, TriggerInterval: triggerInterval, CustomMessage: customMessage, LastStatus: "good", LastStatusAt: 0, } res, err := s.ctx.DB.Exec( "INSERT INTO alerts (contact_point_id, name, condition, trigger_interval, last_status, last_status_at, custom_message) VALUES (?, ?, ?, ?, ?, ?, ?)", alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.LastStatus, alert.LastStatusAt, alert.CustomMessage, ) if err != nil { return nil, err } alert.Id, err = res.LastInsertId() if err != nil { return nil, err } return &alert, nil } func (s *AlertsService) DeleteById(id int64) error { _, err := s.ctx.DB.Exec("DELETE FROM alerts WHERE id = ?", id) if err != nil { return err } return nil } func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64, customMessage string) (*models.AlertItem, error) { alert := models.AlertItem{ Id: id, ContactPointId: contactPointId, Name: name, Condition: condition, TriggerInterval: triggerInterval, CustomMessage: customMessage, } _, err := s.ctx.DB.Exec( "UPDATE alerts SET contact_point_id = ?, name = ?, condition = ?, trigger_interval = ?, custom_message = ? WHERE id = ?", alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.CustomMessage, alert.Id, ) if err != nil { return nil, err } return &alert, nil }