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 wo-bisch.ch Der Akku von "` + alias + `" (DevEUI: ` + deveui + `) ist noch zu ` + accu_percent + ` Prozent geladen. Bitte rechtzeitig wieder laden! Bei Unterschreitung von 5% Ladung erscheint die letzte Warnung. Mit freundlichen GrĂ¼ssen -- wo-bisch.ch` e := email.NewEmail() e.From = "wo-bisch.ch " e.To = []string{username} e.Bcc = []string{"joerg.lehmann@nbit.ch"} e.Subject = level + " - wo-bisch.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 wo-bisch.ch Das Abo von "` + alias + `" (DevEUI: ` + deveui + `) laeuft in ` + strconv.Itoa(days_left) + ` Tagen ab. Bitte Abo verlaengern auf https://wo-bisch.ch Mit freundlichen GrĂ¼ssen -- wo-bisch.ch` e := email.NewEmail() e.From = "wo-bisch.ch " e.To = []string{username} e.Bcc = []string{"joerg.lehmann@nbit.ch"} e.Subject = level + " - wo-bisch.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 vbat2percent(vbat string) int { i, err := strconv.Atoi(vbat) res := 0 if err == nil { res = int(float64(i-3400) / 6.0) fmt.Printf("vbat2percent Result in Percent: %d\n", res) if res < 0 { res = 0 } else if res > 100 { res = 100 } } return res } func getLastMetrics(deveui string) OneMetric { var res OneMetric url := "http://localhost:8086/api/v2/query?org=wobischorg" data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket") |> range(start:-5d) |> tail(n:10) |> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s") |> filter(fn: (r) => r._field == "vbat") |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")`, 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") 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) >= 9) && !(strings.HasPrefix(s[3], "_")) { t, err := time.Parse(time.RFC3339, s[3]) if err != nil { fmt.Printf("error converting time: %s\n", s[3]) continue } res.Timestamp = t.In(location).Format("02.01.2006 15:04") res.BatteryPercent = strconv.Itoa(vbat2percent(s[8])) } } 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, 20, 10, 5) { 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...") }