wo-bisch-web/metrics.go

469 lines
13 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type OneMetric struct {
Deveui string
DeveuiJS template.JS
Alias string
Readonly bool
Timestamp string
PosTimestamp string
Lat string
Lon string
LatJS template.JS
LonJS template.JS
Vbat string
Fw string
BatteryPercent string
BatteryPercentHTML template.HTML
ActiveUntil string
DaysUntilDeactivated int // berechneter Wert
}
// global variables
var INFLUX_RO_TOKEN = os.Getenv("INFLUX_RO_TOKEN")
// metrics handler
func string2float64(s string) float64 {
f, err := strconv.ParseFloat(s, 64)
if err == nil {
return f
}
return 0
}
func metricsHandler(response http.ResponseWriter, request *http.Request) {
name := getUserName(request)
if name != "" {
deveui, ok := request.URL.Query()["deveui"]
if !ok || len(deveui[0]) < 1 {
log.Println("Url Param 'deveui' is missing")
fmt.Fprintf(response, "{ \"msg\": \"error: deveui must be specified in URL\" }")
return
}
// Query()["deveui"] will return an array of items,
// we only want the single item.
mydeveui := deveui[0]
if !(Contains(getMyDevs(name), mydeveui)) {
log.Println("specified 'deveui' does not belong to this user")
fmt.Fprintf(response, "{ \"msg\": \"error: specified deveui does not belong to this user\" }")
return
}
if AboExpired(mydeveui) {
log.Println("specified 'deveui' has an expired abo")
fmt.Fprintf(response, "{ \"msg\": \"specified deveui has an expired abo\" }")
return
}
log.Println("Url Param 'deveui' is: " + string(mydeveui))
// Format of start and stop: YYYY-MM-DDTHH:MI:SSZ
stop, ok := request.URL.Query()["stop"]
var mystop string
if !ok || len(stop[0]) < 1 {
log.Println("Url Param 'stop' is missing, set it to now")
mystop = time.Now().Format("2006-01-02T15:04:05Z")
}
if ok {
mystop = stop[0]
}
layout := "2006-01-02T15:04:05Z"
stopDate, err := time.Parse(layout, mystop)
if err != nil {
fmt.Println(err)
}
start, ok := request.URL.Query()["start"]
var mystart string
if !ok || len(start[0]) < 1 {
log.Println("Url Param 'start' is missing, set it to stop minus one day")
t := stopDate.AddDate(0, 0, -1)
mystart = t.Format("2006-01-02T15:04:05Z")
}
if ok {
mystart = start[0]
}
url := "http://localhost:8086/api/v2/query?org=wobischorg"
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r._measurement == "measurement")
|> filter(fn: (r) => r._field == "lon" or r._field == "lat" or r._field == "vbat")
|> filter(fn: (r) => r.deveui == "%s")
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")`, mystart, mystop, mydeveui))
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 "+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)))
first := true
var lat float64
var lon float64
var min_lat float64 = 9999999999.9
var min_lon float64 = 9999999999.9
var max_lat float64 = 0
var max_lon float64 = 0
fmt.Fprintf(response, "{\n")
fmt.Fprintf(response, " \"data\": [\n")
for scanner.Scan() {
s := strings.Split(scanner.Text(), ",")
fmt.Printf("Scanned Line: %v\n", s)
if (len(s) >= 11) && !(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
}
a := t.Unix()
lat = string2float64(s[8])
lon = string2float64(s[9])
if (lat == 0) || (lon == 0) {
fmt.Println("skip 0 value for lon/lat")
} else {
if lat < min_lat {
min_lat = lat
}
if lon < min_lon {
min_lon = lon
}
if lat > max_lat {
max_lat = lat
}
if lon > max_lon {
max_lon = lon
}
if !(first) {
fmt.Fprintf(response, " ,")
} else {
first = false
fmt.Fprintf(response, " ")
}
fmt.Fprintf(response, "[%d, %s, %s, %s, %d]\n", a, s[8], s[9], s[10], vbat2percent(s[10]))
}
}
}
fmt.Fprintf(response, " ],\n")
fmt.Fprintf(response, " \"min_lat\": %f,\n", min_lat)
fmt.Fprintf(response, " \"min_lon\": %f,\n", min_lon)
fmt.Fprintf(response, " \"max_lat\": %f,\n", max_lat)
fmt.Fprintf(response, " \"max_lon\": %f\n", max_lon)
fmt.Fprintf(response, "}\n")
} else {
fmt.Fprintf(response, "{ \"msg\": \"Only available for logged in users\" }")
}
}
func downloadmetricsHandler(response http.ResponseWriter, request *http.Request) {
name := getUserName(request)
if name != "" {
deveui, ok := request.URL.Query()["deveui"]
if !ok || len(deveui[0]) < 1 {
log.Println("Url Param 'deveui' is missing")
fmt.Fprintf(response, "Error: deveui is missing")
return
}
// Query()["deveui"] will return an array of items,
// we only want the single item.
mydeveui := deveui[0]
if !(Contains(getMyDevs(name), mydeveui)) {
log.Println("specified 'deveui' does not belong to this user")
fmt.Fprintf(response, "Error: specified deveui does not belong to this user")
return
}
if AboExpired(mydeveui) {
log.Println("specified 'deveui' has an expired abo")
fmt.Fprintf(response, "Error: specified deveui has an expired abo")
return
}
log.Println("Url Param 'deveui' is: " + string(mydeveui))
// Format of start and stop: YYYY-MM-DDTHH:MI:SSZ
stop, ok := request.URL.Query()["stop"]
var mystop string
if !ok || len(stop[0]) < 1 {
log.Println("Url Param 'stop' is missing, set it to now")
mystop = time.Now().Format("2006-01-02T15:04:05Z")
}
if ok {
mystop = stop[0]
}
layout := "2006-01-02T15:04:05Z"
stopDate, err := time.Parse(layout, mystop)
if err != nil {
fmt.Println(err)
}
start, ok := request.URL.Query()["start"]
var mystart string
if !ok || len(start[0]) < 1 {
log.Println("Url Param 'start' is missing, set it to stop minus one day")
t := stopDate.AddDate(0, 0, -1)
mystart = t.Format("2006-01-02T15:04:05Z")
}
if ok {
mystart = start[0]
}
url := "http://localhost:8086/api/v2/query?org=wobischorg"
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r._measurement == "measurement")
|> filter(fn: (r) => r._field == "lon" or r._field == "lat" or r._field == "vbat")
|> filter(fn: (r) => r.deveui == "%s")
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")`, mystart, mystop, mydeveui))
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 "+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))
location, err := time.LoadLocation("Europe/Zurich")
scanner := bufio.NewScanner(strings.NewReader(string(body)))
for scanner.Scan() {
s := strings.Split(scanner.Text(), ",")
fmt.Printf("Scanned Line: %v\n", s)
if (len(s) >= 11) && !(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
}
mytime := t.In(location).Format("02.01.2006 15:04")
fmt.Fprintf(response, "\"%s\",%s,%s\n", mytime, s[8], s[9])
}
}
} else {
fmt.Fprintf(response, "Only available for logged in Users")
}
}
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 vbat2html(vbat string) string {
percent := vbat2percent(vbat)
fa_battery_string := ""
icon_color := ""
if percent <= 20 {
fa_battery_string = "fa-battery-empty"
icon_color = "has-background-danger-dark has-text-white pl-2 pr-2"
} else if percent <= 40 {
fa_battery_string = "fa-battery-quarter"
icon_color = "has-background-danger-light has-text-danger pl-2 pr-2"
} else if percent <= 60 {
fa_battery_string = "fa-battery-half"
icon_color = "has-text-success pl-2 pr-2"
} else if percent <= 80 {
fa_battery_string = "fa-battery-three-quarters"
icon_color = "has-text-success pl-2 pr-2"
} else {
fa_battery_string = "fa-battery-full"
icon_color = "has-text-success pl-2 pr-2"
}
return "<span class=\"" + icon_color + "\"><i class=\"fas " + fa_battery_string + "\"></i>&nbsp;" + strconv.Itoa(percent) + " &percnt;</span>"
}
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=wobischorg"
data := []byte(fmt.Sprintf(`from(bucket:"wobischbucket")
|> range(start:-365d)
|> filter(fn: (r) => r._measurement == "measurement" and r.deveui == "%s")
|> filter(fn: (r) => r._field == "lon" or r._field == "lat" or r._field == "vbat" or r._field == "fw")
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|> filter(fn: (r) => r.lon != 0)
|> last(column: "_time")`, 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 "+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 2:", string(body))
scanner := bufio.NewScanner(strings.NewReader(string(body)))
location, err := time.LoadLocation("Europe/Zurich")
for scanner.Scan() {
s := strings.Split(scanner.Text(), ",")
fmt.Printf("BlaBla: %v\n", s)
if (len(s) >= 12) && !(strings.HasPrefix(s[3], "_")) {
t, err := time.Parse(time.RFC3339, s[5])
if err != nil {
fmt.Printf("error converting time: %s\n", s[5])
continue
}
res.Fw = s[8]
res.Timestamp = t.In(location).Format("02.01.2006 15:04")
if s[9] != "0" {
res.Lat = s[9]
res.PosTimestamp = t.In(location).Format("02.01.2006 15:04")
}
if s[10] != "0" {
res.Lon = s[10]
res.PosTimestamp = t.In(location).Format("02.01.2006 15:04")
}
res.BatteryPercent = strconv.Itoa(vbat2percent(s[11]))
res.BatteryPercentHTML = template.HTML(vbat2html(s[11]))
}
}
res.Deveui = deveui
res.DeveuiJS = template.JS(deveui)
res.LatJS = template.JS(res.Lat)
res.LonJS = template.JS(res.Lon)
res.Alias = getDevAlias(deveui)
res.Readonly = false
res.ActiveUntil = getActiveUntil(deveui)
res.DaysUntilDeactivated = CalcDaysUntil(res.ActiveUntil)
fmt.Printf("Last Metrics: %v\n", res)
return res
}