Continued work on alerts
This commit is contained in:
parent
bdeb82441d
commit
3b0c3fff64
|
|
@ -1,5 +1,7 @@
|
|||
package integrations
|
||||
|
||||
import "basic-sensor-receiver/models"
|
||||
|
||||
type ContactPointMessageType string
|
||||
|
||||
const (
|
||||
|
|
@ -14,11 +16,11 @@ type ContactPointEvent struct {
|
|||
}
|
||||
|
||||
type ContactPointAlertTriggeredEvent struct {
|
||||
AlertId int64
|
||||
AlertName string
|
||||
AlertValue string
|
||||
SensorName string
|
||||
CustomMessage string
|
||||
Alert *models.AlertItem
|
||||
Sensor *models.SensorItem
|
||||
SensorValueCondition *models.AlertConditionSensorValue
|
||||
SensorLastContactCondition *models.AlertConditionSensorLastContact
|
||||
LastValue float64
|
||||
}
|
||||
|
||||
type ContactPointIntegration interface {
|
||||
|
|
|
|||
|
|
@ -34,12 +34,13 @@ func (s TelegramIntegration) ProcessEvent(evt *ContactPointEvent, rawConfig stri
|
|||
case ContactPointEventAlertTriggered:
|
||||
data := evt.AlertTriggeredEvent
|
||||
|
||||
text := fmt.Sprintf("🚨 %s is at {value}", data.SensorName)
|
||||
if data.CustomMessage != "" {
|
||||
text = data.CustomMessage
|
||||
text := fmt.Sprintf("🚨 %s is at {value}", data.Sensor.Name)
|
||||
|
||||
if data.Alert.CustomMessage != "" {
|
||||
text = data.Alert.CustomMessage
|
||||
}
|
||||
|
||||
text = strings.Replace(text, "{value}", data.AlertValue, -1)
|
||||
text = strings.Replace(text, "{value}", fmt.Sprintf("%f", data.LastValue), -1)
|
||||
|
||||
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
||||
msg.ParseMode = "Markdown"
|
||||
|
|
@ -47,108 +48,6 @@ func (s TelegramIntegration) ProcessEvent(evt *ContactPointEvent, rawConfig stri
|
|||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
cameraUrl := ""
|
||||
|
||||
if event != nil {
|
||||
cameraUrl = fmt.Sprintf("%s/#/cameras/%d", evt.AppConfig.PublicUrl, event.CameraId)
|
||||
}
|
||||
|
||||
switch evt.Type {
|
||||
case ChannelEventTest:
|
||||
msg := tgbotapi.NewMessage(config.TargetChannel, "👋 Test message")
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
case ChannelEventMotionStart:
|
||||
msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("👁 **Motion detected** by %s. [Go to stream](%s)", camera.Name, cameraUrl))
|
||||
msg.ParseMode = "Markdown"
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
case ChannelEventCameraLost:
|
||||
msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("🔴 Connection to camera %s **lost**. [Go to camera](%s)", camera.Name, cameraUrl))
|
||||
msg.ParseMode = "Markdown"
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
case ChannelEventCameraFound:
|
||||
msg := tgbotapi.NewMessage(config.TargetChannel, fmt.Sprintf("🟢 Connection to camera %s **re-established**. [Go to camera](%s)", camera.Name, cameraUrl))
|
||||
msg.ParseMode = "Markdown"
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
case ChannelEventPreview:
|
||||
if !event.Preview.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
reader, err := os.Open(event.Preview.String)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open preview file - %w", err)
|
||||
}
|
||||
|
||||
file := tgbotapi.FileReader{
|
||||
Name: event.Preview.String,
|
||||
Reader: reader,
|
||||
}
|
||||
|
||||
previewMsg := tgbotapi.NewVideo(config.TargetChannel, file)
|
||||
// previewMsg.Caption = fmt.Sprintf("Captured by %s", camera.Name)
|
||||
_, err = bot.Send(previewMsg)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case ChannelEventMovie:
|
||||
if !event.Movie.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
reader, err := os.Open(event.Movie.String)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
videoFile := tgbotapi.FileReader{
|
||||
Name: event.Movie.String,
|
||||
Reader: reader,
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewVideo(config.TargetChannel, videoFile)
|
||||
// msg.Caption = fmt.Sprintf("Captured by %s", camera.Name)
|
||||
_, err = bot.Send(msg)
|
||||
|
||||
return err
|
||||
|
||||
case ChannelEventPicture:
|
||||
if !event.Picture.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
reader, err := os.Open(event.Picture.String)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
videoFile := tgbotapi.FileReader{
|
||||
Name: event.Movie.String,
|
||||
Reader: reader,
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewPhoto(config.TargetChannel, videoFile)
|
||||
msg.Caption = fmt.Sprintf(`Captured by %s`, camera.Name)
|
||||
|
||||
_, err = bot.Send(msg)
|
||||
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package models
|
||||
|
||||
type AlertItem struct {
|
||||
Id int64 `json:"id"`
|
||||
ContactPointId int64 `json:"contactPointId"`
|
||||
Name string `json:"name"`
|
||||
Condition string `json:"condition"`
|
||||
CustomMessage string `json:"customMessage"`
|
||||
|
||||
/* 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"`
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type SensorItem struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AuthKey string `json:"authKey"`
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ package services
|
|||
|
||||
import (
|
||||
"basic-sensor-receiver/integrations"
|
||||
"basic-sensor-receiver/models"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -11,58 +11,7 @@ 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 {
|
||||
func evaluateSensorValueCondition(condition *models.AlertConditionSensorValue, value *sensorValue) bool {
|
||||
switch condition.Condition {
|
||||
case "less":
|
||||
return value.Value < condition.Value
|
||||
|
|
@ -94,7 +43,22 @@ func (s *AlertsService) EvaluateAlerts() error {
|
|||
|
||||
}
|
||||
|
||||
func (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
|
||||
func unitToSeconds(unit string) int64 {
|
||||
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 {
|
||||
|
|
@ -102,53 +66,56 @@ func (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
|
|||
}
|
||||
|
||||
newStatus := alert.LastStatus
|
||||
lastValue := ""
|
||||
lastValue := float64(0)
|
||||
conditionMet := false
|
||||
sensorId := int64(-1)
|
||||
sensorValueCondition := models.AlertConditionSensorValue{}
|
||||
sensorLastContactCondition := models.AlertConditionSensorLastContact{}
|
||||
|
||||
switch condition["type"].(string) {
|
||||
case "sensor_value":
|
||||
{
|
||||
conditionData := AlertConditionSensorValue{
|
||||
sensorValueCondition = models.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)
|
||||
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(&conditionData, value)
|
||||
conditionMet = evaluateSensorValueCondition(&sensorValueCondition, value)
|
||||
|
||||
break
|
||||
}
|
||||
/*
|
||||
TODO:
|
||||
case "sensor_last_contact":
|
||||
{
|
||||
conditionData := AlertConditionSensorLastContact{
|
||||
SensorId: condition["sensorId"].(int64),
|
||||
Value: condition["value"].(int64),
|
||||
ValueUnit: condition["valueUnit"].(string),
|
||||
}
|
||||
|
||||
case "sensor_last_contact":
|
||||
{
|
||||
sensorLastContactCondition := models.AlertConditionSensorLastContact{
|
||||
SensorId: condition["sensorId"].(int64),
|
||||
Value: condition["value"].(int64),
|
||||
ValueUnit: condition["valueUnit"].(string),
|
||||
}
|
||||
|
||||
value, err := s.ctx.Services.Sensors.GetLastContact(conditionData.SensorId)
|
||||
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
|
||||
lastValue = float64(value.Timestamp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conditionMet := time.Now().Unix()-value > conditionData.Value
|
||||
conditionInSec := sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit)
|
||||
|
||||
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if conditionMet {
|
||||
|
|
@ -182,11 +149,11 @@ func (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
|
|||
err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{
|
||||
Type: "alert",
|
||||
AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{
|
||||
AlertId: alert.Id,
|
||||
AlertName: alert.Name,
|
||||
AlertValue: lastValue,
|
||||
SensorName: sensor.Name,
|
||||
CustomMessage: "",
|
||||
Alert: alert,
|
||||
Sensor: sensor,
|
||||
SensorValueCondition: &sensorValueCondition,
|
||||
SensorLastContactCondition: &sensorLastContactCondition,
|
||||
LastValue: lastValue,
|
||||
},
|
||||
}, contactPoint.TypeConfig)
|
||||
|
||||
|
|
@ -198,7 +165,7 @@ func (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *AlertsService) GetList() ([]*AlertItem, error) {
|
||||
func (s *AlertsService) GetList() ([]*models.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 {
|
||||
|
|
@ -207,10 +174,10 @@ func (s *AlertsService) GetList() ([]*AlertItem, error) {
|
|||
|
||||
defer rows.Close()
|
||||
|
||||
alerts := []*AlertItem{}
|
||||
alerts := []*models.AlertItem{}
|
||||
|
||||
for rows.Next() {
|
||||
alert := AlertItem{}
|
||||
alert := models.AlertItem{}
|
||||
|
||||
err := rows.Scan(&alert.Id, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt)
|
||||
|
||||
|
|
@ -229,8 +196,8 @@ func (s *AlertsService) GetList() ([]*AlertItem, error) {
|
|||
return alerts, nil
|
||||
}
|
||||
|
||||
func (s *AlertsService) GetById(id int64) (*AlertItem, error) {
|
||||
alert := AlertItem{}
|
||||
func (s *AlertsService) GetById(id int64) (*models.AlertItem, error) {
|
||||
alert := models.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)
|
||||
|
|
@ -242,8 +209,8 @@ func (s *AlertsService) GetById(id int64) (*AlertItem, error) {
|
|||
return &alert, nil
|
||||
}
|
||||
|
||||
func (s *AlertsService) Create(contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) {
|
||||
alert := AlertItem{
|
||||
func (s *AlertsService) Create(contactPointId int64, name string, condition string, triggerInterval int64) (*models.AlertItem, error) {
|
||||
alert := models.AlertItem{
|
||||
ContactPointId: contactPointId,
|
||||
Name: name,
|
||||
Condition: condition,
|
||||
|
|
@ -280,8 +247,8 @@ func (s *AlertsService) DeleteById(id int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) {
|
||||
alert := AlertItem{
|
||||
func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64) (*models.AlertItem, error) {
|
||||
alert := models.AlertItem{
|
||||
Id: id,
|
||||
ContactPointId: contactPointId,
|
||||
Name: name,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/models"
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"math/big"
|
||||
|
|
@ -10,14 +11,8 @@ type SensorsService struct {
|
|||
ctx *Context
|
||||
}
|
||||
|
||||
type SensorItem struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AuthKey string `json:"authKey"`
|
||||
}
|
||||
|
||||
func (s *SensorsService) GetList() ([]SensorItem, error) {
|
||||
sensors := make([]SensorItem, 0)
|
||||
func (s *SensorsService) GetList() ([]models.SensorItem, error) {
|
||||
sensors := make([]models.SensorItem, 0)
|
||||
|
||||
rows, err := s.ctx.DB.Query("SELECT id, name, auth_key FROM sensors")
|
||||
|
||||
|
|
@ -32,7 +27,7 @@ func (s *SensorsService) GetList() ([]SensorItem, error) {
|
|||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
item := SensorItem{}
|
||||
item := models.SensorItem{}
|
||||
|
||||
err := rows.Scan(&item.Id, &item.Name, &item.AuthKey)
|
||||
if err != nil {
|
||||
|
|
@ -50,14 +45,14 @@ func (s *SensorsService) GetList() ([]SensorItem, error) {
|
|||
return sensors, nil
|
||||
}
|
||||
|
||||
func (s *SensorsService) Create(name string) (*SensorItem, error) {
|
||||
func (s *SensorsService) Create(name string) (*models.SensorItem, error) {
|
||||
authKey, err := generateRandomString(32)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item := SensorItem{
|
||||
item := models.SensorItem{
|
||||
Name: name,
|
||||
AuthKey: authKey,
|
||||
}
|
||||
|
|
@ -77,8 +72,8 @@ func (s *SensorsService) Create(name string) (*SensorItem, error) {
|
|||
return &item, nil
|
||||
}
|
||||
|
||||
func (s *SensorsService) GetById(id int64) (*SensorItem, error) {
|
||||
item := SensorItem{}
|
||||
func (s *SensorsService) GetById(id int64) (*models.SensorItem, error) {
|
||||
item := models.SensorItem{}
|
||||
|
||||
row := s.ctx.DB.QueryRow("SELECT id, name, auth_key FROM sensors WHERE id = ?", id)
|
||||
|
||||
|
|
@ -90,7 +85,7 @@ func (s *SensorsService) GetById(id int64) (*SensorItem, error) {
|
|||
return &item, nil
|
||||
}
|
||||
|
||||
func (s *SensorsService) Update(id int64, name string) (*SensorItem, error) {
|
||||
func (s *SensorsService) Update(id int64, name string) (*models.SensorItem, error) {
|
||||
item, err := s.GetById(id)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue