mini-beieli-web/check_mini_beieli_nodes/main.go

400 lines
9.9 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/jordan-wright/email"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func sendEmailAccu(username string, alias string, deveui string, accu_percent string, threshold int, level string) {
fmt.Printf("SEND EMAIL ACCU (%s) - %s:%s\n", level, username, deveui)
mail_message := `Lieber Benutzer von mini-beieli.ch
Der Akku von "` + alias + `" (DevEUI: ` + deveui + `) ist noch zu ` + accu_percent + ` Prozent geladen.
Bitte rechtzeitig wieder laden! Bei 10%-er Ladung erscheint die letzte Warnung.
Mit freundlichen Grüssen
--
mini-beieli.ch`
e := email.NewEmail()
e.From = "mini-beieli.ch <info@mini-beieli.ch>"
e.To = []string{username}
e.Bcc = []string{"joerg.lehmann@nbit.ch"}
e.Subject = level + " - mini-beieli.ch: Akku Ladezustand (" + alias + ")"
e.Text = []byte(mail_message)
e.Send("127.0.0.1:25", nil)
}
func sendEmailAbo(username string, alias string, deveui string, days_left int, level string) {
fmt.Printf("SEND EMAIL ABO (%s) - %s:%s\n", level, username, deveui)
mail_message := `Lieber Benutzer von mini-beieli.ch
Das Abo von "` + alias + `" (DevEUI: ` + deveui + `) laeuft in ` + strconv.Itoa(days_left) + ` Tagen ab.
Bitte Abo verlaengern auf https://mini-beieli.ch
Mit freundlichen Grüssen
--
mini-beieli.ch`
e := email.NewEmail()
e.From = "mini-beieli.ch <info@mini-beieli.ch>"
e.To = []string{username}
e.Bcc = []string{"joerg.lehmann@nbit.ch"}
e.Subject = level + " - mini-beieli.ch: Abo laeuft ab (" + alias + ")"
e.Text = []byte(mail_message)
e.Send("127.0.0.1:25", nil)
}
var globalPool *redis.Pool
const userPrefix string = "user:"
const devPrefix string = "dev:"
const confirmPrefix string = "confirm:"
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
s, err := redis.String(c.Do("PING"))
if err != nil {
return err
}
logit("PING Response = " + s)
// Output: PONG
return nil
}
type Dev struct {
Deveui string
Alias string
Alarmactive string
Smsnumber string
ActiveUntil string // Abo bezahlt bis TT.MM.YYYY
}
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 getUsers() []string {
res := []string{}
conn := globalPool.Get()
defer conn.Close()
//logit("getUsers")
users, err := redis.Strings(conn.Do("KEYS", userPrefix+"*"))
if err == nil {
//logit("getUsers successful!")
res = users
} else {
log.Print(err)
}
return res
}
func getMyDevs(username string) []string {
res := []string{}
if username == "" {
return res
}
conn := globalPool.Get()
defer conn.Close()
//logit("getMyDevs: User: " + username)
mydevs, err := redis.String(conn.Do("HGET", userPrefix+username, "my_devs"))
if err == nil {
//logit("getMyDevs: mydevs: " + mydevs)
res = strings.Split(mydevs, ",")
} else {
log.Print(err)
}
return res
}
func getDevAlias(deveui string) string {
res := deveui
if deveui == "" {
return res
}
conn := globalPool.Get()
defer conn.Close()
//logit("getDevAlias: Deveui: " + deveui)
alias, err := redis.String(conn.Do("HGET", devPrefix+deveui, "alias"))
if err == nil {
//logit("getDevAlias: alias: " + alias)
res = alias
} else {
log.Print(err)
}
return res
}
func getActiveUntil(deveui string) string {
res := ""
if deveui == "" {
return res
}
conn := globalPool.Get()
defer conn.Close()
//logit("getActiveUntil: Deveui: " + deveui)
activeuntil, err := redis.String(conn.Do("HGET", devPrefix+deveui, "active_until"))
if err == nil {
//logit("getActiveUntil: activeuntil: " + activeuntil)
res = activeuntil
} else {
log.Print(err)
}
return res
}
func InsertAlert(prefix string, deveui string, email string, threshold int) {
conn := globalPool.Get()
defer conn.Close()
_, err := conn.Do("SET", prefix+deveui+":"+email, threshold)
if err != nil {
logit("InsertAlert: Error inserting: " + prefix + deveui + ":" + email)
}
}
func DeleteAlert(prefix string, deveui string, email string) {
conn := globalPool.Get()
defer conn.Close()
exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))
if exists {
_, err := conn.Do("DEL", prefix+deveui+":"+email)
if err != nil {
logit("DeleteAlert: Error deleting: " + prefix + deveui + ":" + email)
}
}
}
func AlarmNotAlreadySent(prefix string, deveui string, email string, threshold int) bool {
conn := globalPool.Get()
defer conn.Close()
exists, _ := redis.Bool(conn.Do("EXISTS", prefix+deveui+":"+email))
if !exists {
return true
}
alarm_threshold, _ := redis.Int(conn.Do("GET", prefix+deveui+":"+email))
return threshold != alarm_threshold
}
type OneMetric struct {
Deveui string
Alias string
Timestamp string
BatteryPercent string
ActiveUntil string
DaysUntilDeactivated int // berechneter Wert
}
func CalcDaysUntil(mydate string) int {
var days int
layout := "02.01.2006"
t, err := time.Parse(layout, mydate)
if err != nil {
days = 0
}
days = int(t.Sub(time.Now()).Hours() / 24)
return days
}
func getLastMetrics(deveui string) OneMetric {
var res OneMetric
url := "http://localhost:8086/api/v2/query?org=beieliorg"
data := []byte(fmt.Sprintf(`from(bucket:"beielibucket")
|> range(start:-5d)
|> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
|> filter(fn: (r) => r._field == "vp")
|> last(column: "_time") |> yield(name: "last")`, deveui))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil {
log.Fatal("Error reading request. ", err)
}
// Set headers
var INFLUX_RO_TOKEN = os.Getenv("INFLUX_RO_TOKEN")
// Set headers
req.Header.Set("Authorization", "Token "+INFLUX_RO_TOKEN)
req.Header.Set("accept", "application/csv")
req.Header.Set("content-type", "application/vnd.flux")
// Set client timeout
client := &http.Client{Timeout: time.Second * 10}
// Send request
resp, err := client.Do(req)
if err != nil {
log.Fatal("Error reading response. ", err)
}
defer resp.Body.Close()
//fmt.Println("response Status:", resp.Status)
//fmt.Println("response Headers:", resp.Header)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("Error reading body. ", err)
}
//fmt.Println("response Body:", string(body))
scanner := bufio.NewScanner(strings.NewReader(string(body)))
location, err := time.LoadLocation("Europe/Zurich")
for scanner.Scan() {
s := strings.Split(scanner.Text(), ",")
if (len(s) >= 7) && !(strings.HasPrefix(s[5], "_")) {
mytime, err := time.Parse(time.RFC3339, s[5])
if err != nil {
continue
}
res.Timestamp = mytime.In(location).Format("02.01.2006 15:04")
value := s[6]
field := s[7]
if field == "vp" {
res.BatteryPercent = value
}
}
}
res.Deveui = deveui
res.Alias = getDevAlias(deveui)
res.ActiveUntil = getActiveUntil(deveui)
res.DaysUntilDeactivated = CalcDaysUntil(res.ActiveUntil)
return res
}
func CheckThreshold(d string, vp int, u2 string, last_metric OneMetric, info_threshold int, warning_threshold int, alert_threshold int) bool {
var alias string
if vp <= info_threshold {
alias = getDevAlias(d)
}
if vp <= alert_threshold {
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, alert_threshold) {
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "ALARM")
InsertAlert("alarm_sent_accu:", d, u2, alert_threshold)
}
return false
}
if vp <= warning_threshold {
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, warning_threshold) {
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, warning_threshold, "WARNING")
InsertAlert("alarm_sent_accu:", d, u2, warning_threshold)
}
return false
}
if vp <= info_threshold {
if AlarmNotAlreadySent("alarm_sent_accu:", d, u2, info_threshold) {
sendEmailAccu(u2, alias, d, last_metric.BatteryPercent, alert_threshold, "INFO")
InsertAlert("alarm_sent_accu:", d, u2, info_threshold)
}
return false
}
return true
}
func logit(log_message string) {
log.Println(log_message)
}
func main() {
logit("Starting check_battery...")
initDB()
defer closeDB()
users := getUsers()
for _, u := range users {
//fmt.Println(u)
u2 := u[5:]
my_devs := getMyDevs(u2)
for _, d := range my_devs {
//fmt.Printf("%s:%s\n", u2, d)
if !strings.HasPrefix(d, "@") {
last_metric := getLastMetrics(d)
// Zuerst der Batteriealarm
if last_metric.BatteryPercent != "" {
fmt.Printf("%s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
vp, _ := strconv.Atoi(last_metric.BatteryPercent)
if CheckThreshold(d, vp, u2, last_metric, 50, 20, 10) {
DeleteAlert("alarm_sent_accu:", d, u2)
}
}
// Jetzt der Alarm wegen der Abodauer
if last_metric.DaysUntilDeactivated < 30 {
fmt.Printf("SEND EMAIL %s:%s:%s Percent:%s:%d\n", u2, d, last_metric.BatteryPercent, last_metric.ActiveUntil, last_metric.DaysUntilDeactivated)
alias := getDevAlias(d)
sendEmailAbo("joerg.lehmann@nbit.ch", alias, d, last_metric.DaysUntilDeactivated, "INFO")
}
}
}
}
logit("Done with check_battery...")
}