diff --git a/.gitignore b/.gitignore index c6d17b3..f117536 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ mini-beieli-web nohup.out database/ node_modules/ +check_mini_beieli_nodes/check_mini_beieli_nodes diff --git a/check_mini_beieli_nodes/main.go b/check_mini_beieli_nodes/main.go new file mode 100644 index 0000000..e80c035 --- /dev/null +++ b/check_mini_beieli_nodes/main.go @@ -0,0 +1,358 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "github.com/gomodule/redigo/redis" + "io/ioutil" + "log" + "net/http" + "net/smtp" + "strconv" + "strings" + "time" +) + +func sendEmailAccu(username string, alias string, deveui string, accu_percent 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@mini-beieli.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: mini-beieli.ch: Bitte Akku laden (` + alias + `) + +Lieber Benutzer von mini-beieli.ch + +Der Akku von "` + alias + `" (DevEUI: ` + deveui + `) ist nur noch zu ` + accu_percent + ` Prozent geladen. + +Bitte bei nächster Gelegenheit laden. + +Mit freundlichen Grüssen +-- +mini-beieli.ch` + + buf := bytes.NewBufferString(mail_message) + if _, err = buf.WriteTo(wc); err != nil { + log.Fatal(err) + } +} + +func sendEmailAbo(username string, alias string, deveui string, days_left int) { + 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@mini-beieli.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: mini-beieli.ch: Abo laeuft ab (` + alias + `) + +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` + + buf := bytes.NewBufferString(mail_message) + if _, err = buf.WriteTo(wc); err != nil { + log.Fatal(err) + } +} + +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 +} + +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:9999/api/v2/query?org=beieliorg" + data := []byte(fmt.Sprintf(`from(bucket:"beielibucket") + |> range(start:-5d) + |> filter(fn: (r) => r.deveui == "%s") + |> filter(fn: (r) => r._field == "vp") + |> last() |> yield(name: "last")`, deveui)) + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + log.Fatal("Error reading request. ", err) + } + + // Set headers + req.Header.Set("Authorization", "Token OzSltiHLmm3WCaFPI0DrK0VFAVCiqcORNrGsgHO43jb0qodyVGAumJQ3HRtkaze53BVR3AmHqfkmrwrjq-xTyA==") + 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 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 vp < 90 { + 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) + sendEmailAccu("joerg.lehmann@nbit.ch", alias, d, last_metric.BatteryPercent) + } + } + // 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) + } + } + } + } + logit("Done with check_battery...") +} diff --git a/persistence.go b/persistence.go index 889e68b..33c802b 100644 --- a/persistence.go +++ b/persistence.go @@ -82,6 +82,24 @@ 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 updateScaleSettings(scaleSettings Dev) error { conn := globalPool.Get() defer conn.Close() @@ -100,7 +118,7 @@ func checkUserAvailable(username string) bool { conn := globalPool.Get() defer conn.Close() - _, err := redis.String(conn.Do("GET", userPrefix+username)) + _, err := conn.Do("HGETALL", userPrefix+username) if err == redis.ErrNil { logit("User does not exist and is therefore available:" + username) return true