468 lines
13 KiB
Go
468 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> " + strconv.Itoa(percent) + " %</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)
|
|
|> tail(n:10)
|
|
|> 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")`, 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[3])
|
|
if err != nil {
|
|
fmt.Printf("error converting time: %s\n", s[3])
|
|
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
|
|
}
|