From f66060bc7ec235a490c5b0ddd43b80be71cdf058 Mon Sep 17 00:00:00 2001 From: Joerg Lehmann Date: Wed, 2 Jun 2021 11:01:37 +0200 Subject: [PATCH] implement alerting, first iteration --- alert.go | 108 +++++++++++++++++++++ mail.go | 37 ++++++++ persistence.go | 202 ++++++++++++++++++++++++++++++++++++++++ wo-bisch-lorahandler.go | 15 ++- 4 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 alert.go create mode 100644 mail.go create mode 100644 persistence.go diff --git a/alert.go b/alert.go new file mode 100644 index 0000000..1a48e6d --- /dev/null +++ b/alert.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + "time" +) + +func stopAlerting(deveui string) { + myurl := fmt.Sprintf("https://proxy1.lpn.swisscom.ch/thingpark/lrc/rest/downlink?DevEUI=%s&FPort=2&Payload=0201", deveui) + + req, err := http.NewRequest("POST", myurl, nil) + if err != nil { + log.Fatal("Error reading request. ", err) + } + + req.Header.Set("Content-type", "application/x-www-form-urlencoded") + + client := &http.Client{Timeout: time.Second * 10} + + resp, err := client.Do(req) + if err != nil { + log.Fatal("Error reading response. ", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal("Error reading body. ", err) + } + + fmt.Printf("%s\n", body) +} + +func sendSMS(phonenumber string, alertMessage string) { + myurl := fmt.Sprintf("https://api.smsapi.com/sms.do?to=%s&message=%s&from=mini-beieli&format=json", phonenumber, url.QueryEscape(alertMessage)) + + req, err := http.NewRequest("GET", myurl, nil) + if err != nil { + log.Fatal("Error reading request. ", err) + } + + req.Header.Set("Authorization", "Bearer IQ4vRG2JvNOmYmrYz6RuSwAanYZgd2hHGwtN62kq") + + client := &http.Client{Timeout: time.Second * 10} + + resp, err := client.Do(req) + if err != nil { + log.Fatal("Error reading response. ", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal("Error reading body. ", err) + } + + fmt.Printf("%s\n", body) +} + +func sendAlert(deveui string, alertMessage string) { + fmt.Printf("sendAlert: deveui=%s, message=%s\n", deveui, alertMessage) + smsnumber := getSmsnumber(deveui) + alarmactive := getDevAlarmactive(deveui) + fmt.Printf("sendAlert: deveui=%s, smsnumber=%s, alarmactive=%s\n", deveui, smsnumber, alarmactive) + if (smsnumber != "") && (alarmactive == "1") { + // we strip of the leading + + smsnumber = strings.Replace(smsnumber, "+", "", -1) + sendSMS(smsnumber, alertMessage) + } else { + email := getEmail(deveui) + fmt.Printf("sendEmail: deveui=%s, email=%s\n", deveui, email) + sendEmail(email, alertMessage) + } +} + +func DispatchAlert(deveui string, alertMessage string) { + // first let's stop the alerting cyle on the Lora Network (every minute for one hour!) + stopAlerting(deveui) + + // we check if deveui exists + if !(checkDevExists(deveui)) { + fmt.Printf("Error: Deveui %s does not exist!", deveui) + return + } + + // then we check if it expired + if AboExpired(deveui) { + fmt.Printf("Error: Abo for Deveui %s is expired!", deveui) + return + } + + // then we check that an alert was not already sent out recently + if AlertAlreadySentRecently(deveui) { + fmt.Printf("Error: Alert for Deveui %s has already been sent!", deveui) + return + } + + // then we make an entry that an alert was sent, this will expire automatically + AddAlertAlreadySentRecently(deveui) + + // then we send the alert + sendAlert(deveui, alertMessage) +} diff --git a/mail.go b/mail.go new file mode 100644 index 0000000..9c33a5f --- /dev/null +++ b/mail.go @@ -0,0 +1,37 @@ +package main + +import ( + "bytes" + "log" + "net/smtp" +) + +func sendEmail(username, message string) { + c, err := smtp.Dial("127.0.0.1:25") + if err != nil { + log.Fatal(err) + } + defer c.Close() + // Set the sender and recipient. + c.Mail("info@wo-bisch.ch") + c.Rcpt(username) + // Send the email body. + wc, err := c.Data() + if err != nil { + log.Fatal(err) + } + defer wc.Close() + mail_message := "To: " + username + ` +Subject: ` + message + ` + +Lieber Benutzer von wo-bisch.ch + +` + message + ` +-- +wo-bisch.ch` + + buf := bytes.NewBufferString(mail_message) + if _, err = buf.WriteTo(wc); err != nil { + log.Fatal(err) + } +} diff --git a/persistence.go b/persistence.go new file mode 100644 index 0000000..c811885 --- /dev/null +++ b/persistence.go @@ -0,0 +1,202 @@ +package main + +import ( + "github.com/gomodule/redigo/redis" + "log" + "time" +) + +var globalPool *redis.Pool + +const alertsentPrefix string = "alertsent:" +const devPrefix string = "dev:" + +func newPool() *redis.Pool { + return &redis.Pool{ + // Maximum number of idle connections in the pool. + MaxIdle: 80, + // max number of connections + MaxActive: 12000, + // Dial is an application supplied function for creating and + // configuring a connection. + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", ":6379") + if err != nil { + panic(err.Error()) + } + return c, err + }, + } +} + +// ping tests connectivity for redis (PONG should be returned) +func ping(c redis.Conn) error { + // Send PING command to Redis + // PING command returns a Redis "Simple String" + // Use redis.String to convert the interface type to string + _, err := redis.String(c.Do("PING")) + if err != nil { + return err + } + + return nil +} + +func initDB() { + + // newPool returns a pointer to a redis.Pool + pool := newPool() + // get a connection from the globalPool (redis.Conn) + conn := pool.Get() + defer conn.Close() + + globalPool = pool + + // wir machen einen Connection Test + ping(conn) +} + +func closeDB() { + globalPool.Close() +} + +func getActiveUntil(deveui string) string { + res := "" + + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + activeuntil, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until")) + if err == nil { + res = activeuntil + } else { + log.Print(err) + } + + return res +} + +func AboExpired(deveui string) bool { + active_until := getActiveUntil(deveui) + + layout := "02.01.2006" + t, _ := time.Parse(layout, active_until) + + return t.Before(time.Now()) +} + +func checkDevExists(deveui string) bool { + conn := globalPool.Get() + defer conn.Close() + + _, err := redis.String(conn.Do("GET", devPrefix+deveui)) + if err == redis.ErrNil { + return false + } else if err != nil { + return true + } + return false +} + +func getDevAlias(deveui string) string { + res := deveui + + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + alias, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alias")) + if err == nil { + res = alias + } else { + res = deveui + } + + return res +} + +func getDevAlarmactive(deveui string) string { + res := "0" + + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + alarmactive, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alarmactive")) + if err == nil { + res = alarmactive + } + + return res +} + +func getSmsnumber(deveui string) string { + res := "" + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + smsnumber, err := redis.String(conn.Do("HGET", devPrefix+deveui, "smsnumber")) + if err == nil { + res = smsnumber + } + + return res +} + +func getEmail(deveui string) string { + res := "" + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + email, err := redis.String(conn.Do("HGET", devPrefix+deveui, "email")) + if err == nil { + res = email + } + + return res +} + +func AlertAlreadySentRecently(deveui string) bool { + conn := globalPool.Get() + defer conn.Close() + + exists, _ := redis.Bool(conn.Do("EXISTS", alertsentPrefix+deveui)) + return exists +} + +func AddAlertAlreadySentRecently(deveui string) { + conn := globalPool.Get() + defer conn.Close() + + _, err := conn.Do("SET", alertsentPrefix+deveui, "1") + + if err != nil { + return + } + + // we set an expiration time of three hours + _, err1 := conn.Do("EXPIRE", alertsentPrefix+deveui, 600) + + if err1 != nil { + return + } + +} diff --git a/wo-bisch-lorahandler.go b/wo-bisch-lorahandler.go index acca198..a7fcdd8 100644 --- a/wo-bisch-lorahandler.go +++ b/wo-bisch-lorahandler.go @@ -90,12 +90,20 @@ func DecodePayload(s string, deveui string) { log.Printf("AlarmBat: %d", pl.AlarmBat) log.Printf("Flag: %d", pl.Flag) - vbat := (pl.AlarmBat & 0x3fff) // Battery Voltage in mV - fw := 160 + (pl.Flag & 0x1f) // Firmware version; 5 bits + vbat := (pl.AlarmBat & 0x3fff) // Battery Voltage in mV + fw := 160 + (pl.Flag & 0x1f) // Firmware version; 5 bits + alarm := (pl.AlarmBat & 0x4000) == 0x4000 // Alarm Bit + + log.Printf("AlarmBit: %v", alarm) mystring := fmt.Sprintf("measurement,deveui=%s lat=%.5f,lon=%.5f,vbat=%d,fw=%d %d\n", deveui, float32(pl.Latitude)/1000000.0, float32(pl.Longitude)/1000000.0, vbat, fw, (time.Now().Unix() * 1000 * 1000 * 1000)) WriteStringToFile(mystring, deveui) + // we send an alert, when the alert bit is set + if alarm { + log.Printf("DispatchAlert (button pressed) for %s\n", deveui) + DispatchAlert(deveui, "Alert button pressed!") + } } else { log.Printf("Payload is not >= 11 bytes (22 chars): %s", s) @@ -103,6 +111,9 @@ func DecodePayload(s string, deveui string) { } func main() { + // Init Redis + initDB() + // Open Output File f, err := os.OpenFile(outputfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) file = f