Compare commits
10 Commits
f00afc9713
...
0fe3232a76
| Author | SHA1 | Date |
|---|---|---|
|
|
0fe3232a76 | |
|
|
65fd72e1ea | |
|
|
3f3e6da696 | |
|
|
2e3c64eeb8 | |
|
|
683a8a5fc1 | |
|
|
6d7b0871ad | |
|
|
ee28065d0f | |
|
|
c9f0610cc0 | |
|
|
2d93042fff | |
|
|
c6c83ce708 |
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM golang:alpine AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
ADD go.mod .
|
||||||
|
COPY . .
|
||||||
|
RUN go build
|
||||||
|
FROM alpine
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
|
ENV TZ=Europe/Zurich
|
||||||
|
WORKDIR /build
|
||||||
|
COPY --from=builder /build/wo-bisch-lorahandler /build/wo-bisch-lorahandler
|
||||||
|
CMD ["./wo-bisch-lorahandler"]
|
||||||
|
EXPOSE 8080
|
||||||
11
alert.go
11
alert.go
|
|
@ -76,7 +76,10 @@ func sendAlert(deveui string, alertMessage string) {
|
||||||
emailalarmactive := getDevEmailAlarmactive(deveui)
|
emailalarmactive := getDevEmailAlarmactive(deveui)
|
||||||
fmt.Printf("sendAlert: deveui=%s, email=%s, emailalarmactive=%s\n", deveui, email, emailalarmactive)
|
fmt.Printf("sendAlert: deveui=%s, email=%s, emailalarmactive=%s\n", deveui, email, emailalarmactive)
|
||||||
if emailalarmactive == "1" {
|
if emailalarmactive == "1" {
|
||||||
sendEmail(email, alertMessage)
|
emails := strings.Split(email, ",")
|
||||||
|
for _, em := range emails {
|
||||||
|
sendEmailMessage(em, alertMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,17 +103,19 @@ func DispatchAlert(deveui string, alertType string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// then we check that an alert was not already sent out recently
|
// then we check that an alert was not already sent out recently
|
||||||
if AlertAlreadySentRecently(deveui) {
|
if AlertAlreadySentRecently(deveui, alertType) {
|
||||||
fmt.Printf("Error: Alert for Deveui %s has already been sent!", deveui)
|
fmt.Printf("Error: Alert for Deveui %s has already been sent!", deveui)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// then we make an entry that an alert was sent, this will expire automatically
|
// then we make an entry that an alert was sent, this will expire automatically
|
||||||
AddAlertAlreadySentRecently(deveui)
|
AddAlertAlreadySentRecently(deveui, alertType)
|
||||||
|
|
||||||
// then we send the alert
|
// then we send the alert
|
||||||
if alertType == "alert_button" {
|
if alertType == "alert_button" {
|
||||||
alertMessage = fmt.Sprintf("Alarm (%s): Knopf gedrueckt, %s", getDevAlias(deveui), time.Now().Format("02.01.2006 15:04:05"))
|
alertMessage = fmt.Sprintf("Alarm (%s): Knopf gedrueckt, %s", getDevAlias(deveui), time.Now().Format("02.01.2006 15:04:05"))
|
||||||
|
} else if alertType == "alert_greenzone" {
|
||||||
|
alertMessage = fmt.Sprintf("Alarm (%s): Gruene Zone wurde verlassen, %s", getDevAlias(deveui), time.Now().Format("02.01.2006 15:04:05"))
|
||||||
}
|
}
|
||||||
sendAlert(deveui, alertMessage)
|
sendAlert(deveui, alertMessage)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module nbit.ch/wo-bisch-lorahandler/v2
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/gomodule/redigo v1.8.8
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E=
|
||||||
|
github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
41
mail.go
41
mail.go
|
|
@ -1,27 +1,39 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"log"
|
"log"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sendEmail(username, message string) {
|
func sendEmail(mail_to, mail_default_authuser, mail_message string) {
|
||||||
c, err := smtp.Dial("127.0.0.1:25")
|
var auth smtp.Auth
|
||||||
|
if getenv("MAILSERVER_USER", "") != "" {
|
||||||
|
// Set up authentication information.
|
||||||
|
auth = smtp.PlainAuth(
|
||||||
|
"",
|
||||||
|
getenv("MAILSERVER_USER", ""),
|
||||||
|
getenv("MAILSERVER_PASSWORD", ""),
|
||||||
|
getenv("MAILSERVER_HOST", "127.0.0.1"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Connect to the server, authenticate, set the sender and recipient,
|
||||||
|
// and send the email all in one step.
|
||||||
|
err := smtp.SendMail(
|
||||||
|
getenv("MAILSERVER_HOST", "127.0.0.1")+":"+getenv("MAILSERVER_PORT", "25"),
|
||||||
|
auth,
|
||||||
|
getenv("MAILSERVER_USER", mail_default_authuser),
|
||||||
|
[]string{mail_to},
|
||||||
|
[]byte(mail_message),
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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()
|
|
||||||
|
func sendEmailMessage(username, message string) {
|
||||||
mail_message := "To: " + username + `
|
mail_message := "To: " + username + `
|
||||||
|
From: info@wo-bisch.ch
|
||||||
Subject: ` + message + `
|
Subject: ` + message + `
|
||||||
|
|
||||||
Lieber Benutzer von wo-bisch.ch
|
Lieber Benutzer von wo-bisch.ch
|
||||||
|
|
@ -30,8 +42,5 @@ Lieber Benutzer von wo-bisch.ch
|
||||||
--
|
--
|
||||||
wo-bisch.ch`
|
wo-bisch.ch`
|
||||||
|
|
||||||
buf := bytes.NewBufferString(mail_message)
|
sendEmail(username, "mail@wo-bisch.ch", mail_message)
|
||||||
if _, err = buf.WriteTo(wc); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/gomodule/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,6 +12,14 @@ var globalPool *redis.Pool
|
||||||
const alertsentPrefix string = "alertsent:"
|
const alertsentPrefix string = "alertsent:"
|
||||||
const devPrefix string = "dev:"
|
const devPrefix string = "dev:"
|
||||||
|
|
||||||
|
func getenv(key, fallback string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
func newPool() *redis.Pool {
|
func newPool() *redis.Pool {
|
||||||
return &redis.Pool{
|
return &redis.Pool{
|
||||||
// Maximum number of idle connections in the pool.
|
// Maximum number of idle connections in the pool.
|
||||||
|
|
@ -20,7 +29,7 @@ func newPool() *redis.Pool {
|
||||||
// Dial is an application supplied function for creating and
|
// Dial is an application supplied function for creating and
|
||||||
// configuring a connection.
|
// configuring a connection.
|
||||||
Dial: func() (redis.Conn, error) {
|
Dial: func() (redis.Conn, error) {
|
||||||
c, err := redis.Dial("tcp", ":6379")
|
c, err := redis.Dial("tcp", getenv("REDIS_CONNECTION_STRING", ":6379"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -192,29 +201,66 @@ func getEmail(deveui string) string {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func AlertAlreadySentRecently(deveui string) bool {
|
func getDevGreenzone(deveui string) string {
|
||||||
|
res := ""
|
||||||
|
|
||||||
|
if deveui == "" {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
conn := globalPool.Get()
|
conn := globalPool.Get()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
exists, _ := redis.Bool(conn.Do("EXISTS", alertsentPrefix+deveui))
|
greenzone, err := redis.String(conn.Do("HGET", devPrefix+deveui, "greenzone"))
|
||||||
|
if err == nil {
|
||||||
|
res = greenzone
|
||||||
|
} else {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlertAlreadySentRecently(deveui string, what string) bool {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
exists, _ := redis.Bool(conn.Do("EXISTS", alertsentPrefix+deveui+":"+what))
|
||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddAlertAlreadySentRecently(deveui string) {
|
func AddAlertAlreadySentRecently(deveui string, what string) {
|
||||||
conn := globalPool.Get()
|
conn := globalPool.Get()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
expire_duration := 120
|
||||||
|
if what == "alert_greenzone" {
|
||||||
|
expire_duration = 7 * 24 * 60 * 60
|
||||||
|
}
|
||||||
|
|
||||||
_, err := conn.Do("SET", alertsentPrefix+deveui, "1")
|
_, err := conn.Do("SET", alertsentPrefix+deveui+":"+what, "1")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// we set an expiration time to prevent duplicate alerts
|
// we set an expiration time to prevent duplicate alerts
|
||||||
_, err1 := conn.Do("EXPIRE", alertsentPrefix+deveui, 120)
|
_, err1 := conn.Do("EXPIRE", alertsentPrefix+deveui+":"+what, expire_duration)
|
||||||
|
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteGreenzoneAlert(deveui string) bool {
|
||||||
|
conn := globalPool.Get()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err := conn.Do("DEL", alertsentPrefix+deveui+":alert_greenzone")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
outputfile = "/home/appuser/wo-bisch-lorahandler/wo-bisch-lorahandler.log"
|
outputfile = "/data/wo-bisch-lorahandler.log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessageProperties struct {
|
type MessageProperties struct {
|
||||||
|
|
@ -45,7 +47,7 @@ func WriteStringToFile(s string, deveui string) {
|
||||||
|
|
||||||
// Also write to individual files
|
// Also write to individual files
|
||||||
datestr := time.Now().Format("2006-01")
|
datestr := time.Now().Format("2006-01")
|
||||||
individual_file := "/home/appuser/wo-bisch-lorahandler/logs/" + deveui + "-" + datestr + ".log"
|
individual_file := "/data/logs/" + deveui + "-" + datestr + ".log"
|
||||||
|
|
||||||
fi, err := os.OpenFile(individual_file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
fi, err := os.OpenFile(individual_file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -65,6 +67,35 @@ func Convert2Hex(payload_raw string) (string, error) {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 0: inside greenzone, 1: outside greenzone, 2: no GPS
|
||||||
|
func IsOutsideGreenzone(deveui string, lon float64, lat float64) int {
|
||||||
|
if lon < 1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if lat < 1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
res := false
|
||||||
|
greenzone := getDevGreenzone(deveui)
|
||||||
|
if greenzone != "" {
|
||||||
|
coords := strings.Split(greenzone, ",")
|
||||||
|
if len(coords) == 4 {
|
||||||
|
lon_min, _ := strconv.ParseFloat(coords[0], 32)
|
||||||
|
lat_min, _ := strconv.ParseFloat(coords[1], 32)
|
||||||
|
lon_max, _ := strconv.ParseFloat(coords[2], 32)
|
||||||
|
lat_max, _ := strconv.ParseFloat(coords[3], 32)
|
||||||
|
res = (lon < lon_min || lon > lon_max || lat < lat_min || lat > lat_max)
|
||||||
|
} else {
|
||||||
|
log.Printf("greenzone does not have right format: %s!", greenzone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func DecodePayload(s string, deveui string) {
|
func DecodePayload(s string, deveui string) {
|
||||||
type payload struct {
|
type payload struct {
|
||||||
Latitude uint32
|
Latitude uint32
|
||||||
|
|
@ -93,10 +124,19 @@ func DecodePayload(s string, deveui string) {
|
||||||
vbat := (pl.AlarmBat & 0x3fff) // Battery Voltage in mV
|
vbat := (pl.AlarmBat & 0x3fff) // Battery Voltage in mV
|
||||||
fw := 160 + (pl.Flag & 0x1f) // Firmware version; 5 bits
|
fw := 160 + (pl.Flag & 0x1f) // Firmware version; 5 bits
|
||||||
alarm := (pl.AlarmBat & 0x4000) == 0x4000 // Alarm Bit
|
alarm := (pl.AlarmBat & 0x4000) == 0x4000 // Alarm Bit
|
||||||
|
outsideGreenzone := IsOutsideGreenzone(deveui, float64(pl.Longitude)/1000000.0, float64(pl.Latitude)/1000000.0)
|
||||||
|
flags := 0
|
||||||
|
if outsideGreenzone == 1 {
|
||||||
|
flags += 2
|
||||||
|
}
|
||||||
|
if alarm {
|
||||||
|
flags += 1
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("AlarmBit: %v", alarm)
|
log.Printf("AlarmBit: %v", alarm)
|
||||||
|
log.Printf("outsideGreenzone: %d", outsideGreenzone)
|
||||||
|
|
||||||
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))
|
mystring := fmt.Sprintf("measurement,deveui=%s lat=%.5f,lon=%.5f,vbat=%d,fw=%d,flags=%d %d\n", deveui, float32(pl.Latitude)/1000000.0, float32(pl.Longitude)/1000000.0, vbat, fw, flags, (time.Now().Unix() * 1000 * 1000 * 1000))
|
||||||
|
|
||||||
WriteStringToFile(mystring, deveui)
|
WriteStringToFile(mystring, deveui)
|
||||||
// we send an alert, when the alert bit is set
|
// we send an alert, when the alert bit is set
|
||||||
|
|
@ -104,6 +144,16 @@ func DecodePayload(s string, deveui string) {
|
||||||
log.Printf("DispatchAlert (button pressed) for %s\n", deveui)
|
log.Printf("DispatchAlert (button pressed) for %s\n", deveui)
|
||||||
DispatchAlert(deveui, "alert_button")
|
DispatchAlert(deveui, "alert_button")
|
||||||
}
|
}
|
||||||
|
if outsideGreenzone == 1 {
|
||||||
|
log.Printf("DispatchAlert (outside greenzone) for %s\n", deveui)
|
||||||
|
DispatchAlert(deveui, "alert_greenzone")
|
||||||
|
} else if outsideGreenzone == 0 {
|
||||||
|
// we reset the alert, when we are again withing the greenzone
|
||||||
|
if AlertAlreadySentRecently(deveui, "alert_greenzone") {
|
||||||
|
fmt.Printf("Info: we are again in Greenzone for Deveui %s, clear alert!", deveui)
|
||||||
|
DeleteGreenzoneAlert(deveui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Payload is not >= 11 bytes (22 chars): %s", s)
|
log.Printf("Payload is not >= 11 bytes (22 chars): %s", s)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue