303 lines
6.7 KiB
Go
303 lines
6.7 KiB
Go
package services
|
|
|
|
import (
|
|
"basic-sensor-receiver/integrations"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type AlertsService struct {
|
|
ctx *Context
|
|
}
|
|
|
|
type AlertItem struct {
|
|
Id int64 `json:"id"`
|
|
ContactPointId int64 `json:"contactPointId"`
|
|
Name string `json:"name"`
|
|
Condition string `json:"condition"`
|
|
|
|
/* how long does the condition have to be true for the alert to go off */
|
|
TriggerInterval int64 `json:"triggerInterval"`
|
|
|
|
/* current alert status, possible values: good, pending, alerting */
|
|
LastStatus string `json:"lastStatus"`
|
|
/* time at which was status last changed */
|
|
LastStatusAt int64 `json:"lastStatusAt"`
|
|
}
|
|
|
|
type AlertConditionSensorValue struct {
|
|
SensorId int64 `json:"sensorId"`
|
|
Condition string `json:"condition"`
|
|
Value float64 `json:"value"`
|
|
}
|
|
|
|
type AlertConditionSensorLastContact struct {
|
|
SensorId int64 `json:"sensorId"`
|
|
Value int64 `json:"value"`
|
|
ValueUnit string `json:"valueUnit"`
|
|
}
|
|
|
|
/*
|
|
Conditions examples:
|
|
sensor value (temperature) is less/more/equal to <<number>>
|
|
last contact by sensor is more than 5 minutes ago
|
|
|
|
|
|
// When value of sensor 1 is less than 20
|
|
{
|
|
type: 'sensor_value',
|
|
sensorId: 1,
|
|
condition: 'less',
|
|
value: 20
|
|
}
|
|
|
|
// When last contact by sensor was more than 10 minutes ago
|
|
{
|
|
type: 'sensor_last_contact',
|
|
sensorId: 1,
|
|
value: 10
|
|
valueUnit: 'minutes'
|
|
}
|
|
|
|
*/
|
|
|
|
func evaluateSensorValueCondition(condition *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 (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
|
|
condition := map[string]interface{}{}
|
|
err := json.Unmarshal([]byte(alert.Condition), &condition)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newStatus := alert.LastStatus
|
|
lastValue := ""
|
|
conditionMet := false
|
|
sensorId := int64(-1)
|
|
|
|
switch condition["type"].(string) {
|
|
case "sensor_value":
|
|
{
|
|
conditionData := AlertConditionSensorValue{
|
|
SensorId: condition["sensorId"].(int64),
|
|
Condition: condition["condition"].(string),
|
|
Value: condition["value"].(float64),
|
|
}
|
|
|
|
value, err := s.ctx.Services.SensorValues.GetLatest(conditionData.SensorId, time.Now().Unix())
|
|
lastValue = fmt.Sprintf("%f", value.Value)
|
|
sensorId = int64(conditionData.SensorId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conditionMet = evaluateSensorValueCondition(&conditionData, value)
|
|
|
|
break
|
|
}
|
|
/*
|
|
TODO:
|
|
case "sensor_last_contact":
|
|
{
|
|
conditionData := AlertConditionSensorLastContact{
|
|
SensorId: condition["sensorId"].(int64),
|
|
Value: condition["value"].(int64),
|
|
ValueUnit: condition["valueUnit"].(string),
|
|
}
|
|
|
|
|
|
value, err := s.ctx.Services.Sensors.GetLastContact(conditionData.SensorId)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conditionMet := time.Now().Unix()-value > conditionData.Value
|
|
|
|
break
|
|
}
|
|
*/
|
|
}
|
|
|
|
if conditionMet {
|
|
if newStatus == "good" {
|
|
newStatus = "alerting"
|
|
} else if newStatus == "pending" {
|
|
if time.Now().Unix()-alert.LastStatusAt > alert.TriggerInterval {
|
|
newStatus = "alerting"
|
|
}
|
|
}
|
|
}
|
|
|
|
if newStatus != alert.LastStatus {
|
|
s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id)
|
|
|
|
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: "alert",
|
|
AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{
|
|
AlertId: alert.Id,
|
|
AlertName: alert.Name,
|
|
AlertValue: lastValue,
|
|
SensorName: sensor.Name,
|
|
CustomMessage: "",
|
|
},
|
|
}, contactPoint.TypeConfig)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *AlertsService) GetList() ([]*AlertItem, error) {
|
|
rows, err := s.ctx.DB.Query("SELECT 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 := []*AlertItem{}
|
|
|
|
for rows.Next() {
|
|
alert := AlertItem{}
|
|
|
|
err := rows.Scan(&alert.Id, &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) (*AlertItem, error) {
|
|
alert := AlertItem{}
|
|
|
|
row := s.ctx.DB.QueryRow("SELECT id, name, condition, trigger_interval, last_status, last_status_at FROM alerts WHERE id = ?", id)
|
|
err := row.Scan(&alert.Id, &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) (*AlertItem, error) {
|
|
alert := AlertItem{
|
|
ContactPointId: contactPointId,
|
|
Name: name,
|
|
Condition: condition,
|
|
TriggerInterval: triggerInterval,
|
|
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) VALUES (?, ?, ?, ?, ?)",
|
|
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.LastStatus, alert.LastStatusAt,
|
|
)
|
|
|
|
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) (*AlertItem, error) {
|
|
alert := AlertItem{
|
|
Id: id,
|
|
ContactPointId: contactPointId,
|
|
Name: name,
|
|
Condition: condition,
|
|
TriggerInterval: triggerInterval,
|
|
}
|
|
|
|
_, err := s.ctx.DB.Exec(
|
|
"UPDATE alerts SET contact_point_id = ?, name = ?, condition = ?, trigger_interval = ? WHERE id = ?",
|
|
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.Id,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &alert, nil
|
|
}
|