474 lines
14 KiB
Go
474 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
MyDB = "beieliscaledb"
|
|
username = "beieli"
|
|
password = "beieli4president"
|
|
outputfile = "/home/beieli/lorahandler/lorahandler.log"
|
|
NOT_PLAUSIBLE_16 = 65535
|
|
NOT_PLAUSIBLE_32 = 2147483647
|
|
)
|
|
|
|
type MetadataTTN struct {
|
|
Time string `json:"time"`
|
|
}
|
|
|
|
type MessageTTN struct {
|
|
Hardware_serial string `json:"hardware_serial"`
|
|
Metadata MetadataTTN `json:"metadata"`
|
|
Payload_raw string `json:"payload_raw"`
|
|
}
|
|
|
|
type MessageProperties struct {
|
|
Time string `json:"Time"`
|
|
Payload_hex string `json:"payload_hex"`
|
|
DevEUI string `json:"DevEUI"`
|
|
DevAddr string `json:"DevAddr"`
|
|
LrrLAT float32 `json:"LrrLAT"`
|
|
LrrLON float32 `json:"LrrLON"`
|
|
}
|
|
|
|
type Message struct {
|
|
Prop MessageProperties `json:"DevEUI_uplink"`
|
|
}
|
|
|
|
type payload_128 struct {
|
|
Version uint8
|
|
Fw_version int32
|
|
Vbat uint8
|
|
H uint8
|
|
T int16
|
|
P uint8
|
|
W1 int32
|
|
W2 int32
|
|
W1_0 int32
|
|
W2_0 int32
|
|
W1_C float32
|
|
W2_C float32
|
|
}
|
|
|
|
type payload_129 struct {
|
|
Version uint8
|
|
Fw_version int32
|
|
Vbat uint8
|
|
H uint8
|
|
T int16
|
|
P uint8
|
|
W1 int32
|
|
W2 int32
|
|
W uint16
|
|
}
|
|
|
|
type payload_1 struct {
|
|
Version uint8
|
|
Vbat uint8
|
|
H1 uint8
|
|
H2 uint8
|
|
H3 uint8
|
|
H4 uint8
|
|
H5 uint8
|
|
H6 uint8
|
|
H7 uint8
|
|
H8 uint8
|
|
T int16
|
|
TC1 int8
|
|
TC2 int8
|
|
TC3 int8
|
|
TC4 int8
|
|
TC5 int8
|
|
TC6 int8
|
|
TC7 int8
|
|
P1 uint8
|
|
P2 uint8
|
|
P3 uint8
|
|
P4 uint8
|
|
P5 uint8
|
|
P6 uint8
|
|
P7 uint8
|
|
P8 uint8
|
|
W1 uint16
|
|
W2 uint16
|
|
W3 uint16
|
|
W4 uint16
|
|
W5 uint16
|
|
W6 uint16
|
|
W7 uint16
|
|
W8 uint16
|
|
O uint8
|
|
}
|
|
|
|
// Global variables
|
|
var file *os.File
|
|
|
|
var alertMap map[string]string
|
|
var alertLogMap map[string]string
|
|
|
|
func ProcessInitPacket(deveui string, w1_0 int32, w2_0 int32, w1_c float32, w2_c float32, w1 int32, w2 int32) {
|
|
var downlink_command string
|
|
|
|
fmt.Printf("Processing Init Packet for Deveui %s\n", deveui)
|
|
downlink_command = GetDownlinkCommand(deveui)
|
|
if downlink_command == "do_nothing" {
|
|
// do nothing
|
|
fmt.Printf("Init Packet, downlink_command set to do_nothing (or not set at all), nothing to do... Deveui %s\n", deveui)
|
|
} else if downlink_command == "tare_0" {
|
|
// reset node to 0
|
|
ResetNodeToZero(deveui, w1, w2)
|
|
} else if strings.HasPrefix(downlink_command, "tare_a ") {
|
|
// calibrate Scale A : tare_a <gram_a>
|
|
CalibrateScale(deveui, downlink_command, w1, w2)
|
|
} else if strings.HasPrefix(downlink_command, "tare_b ") {
|
|
// calibrate Scale B : tare_b <gram_b>
|
|
CalibrateScale(deveui, downlink_command, w1, w2)
|
|
} else if strings.HasPrefix(downlink_command, "tare ") {
|
|
// calibrate both Scale A and Scale B in on step : tare <gram_a> <gram_b>
|
|
CalibrateScale(deveui, downlink_command, w1, w2)
|
|
} else {
|
|
fmt.Printf("Error: Unknown downlink_command: %s (DevEUI: %s)\n", downlink_command, deveui)
|
|
}
|
|
// We update the local settings on each init package (if something changed...)
|
|
UpdateCalibrationSettingsFromNode(deveui, w1_0, w2_0, w1_c, w2_c)
|
|
}
|
|
|
|
func DecodePayload(s string, deveui string, devaddr string, lrrlat float32, lrrlon float32, write2file bool) {
|
|
var ba []byte
|
|
var pl_1 payload_1
|
|
var pl_128 payload_128
|
|
var pl_129 payload_129
|
|
ba, _ = hex.DecodeString(s)
|
|
pl_1 = payload_1{}
|
|
pl_128 = payload_128{}
|
|
pl_129 = payload_129{}
|
|
br := bytes.NewReader(ba)
|
|
if s[0:2] == "01" {
|
|
err := binary.Read(br, binary.LittleEndian, &pl_1)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
} else if s[0:2] == "80" {
|
|
err := binary.Read(br, binary.LittleEndian, &pl_128)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
} else if s[0:2] == "81" {
|
|
err := binary.Read(br, binary.LittleEndian, &pl_129)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
} else {
|
|
fmt.Printf("Payload String is unknown: %s\n", s)
|
|
}
|
|
if s[0:2] == "01" {
|
|
fmt.Printf("{\n")
|
|
fmt.Printf(" version: %d,\n", pl_1.Version)
|
|
fmt.Printf(" vbat: %d,\n", pl_1.Vbat)
|
|
fmt.Printf(" offset: %d\n", pl_1.O)
|
|
fmt.Printf(" humidity: [%d,%d,%d,%d,%d,%d,%d,%d],\n", pl_1.H1, pl_1.H2, pl_1.H3, pl_1.H4, pl_1.H5, pl_1.H6, pl_1.H7, pl_1.H8)
|
|
fmt.Printf(" pressure: [%d,%d,%d,%d,%d,%d,%d,%d],\n", pl_1.P1, pl_1.P2, pl_1.P3, pl_1.P4, pl_1.P5, pl_1.P6, pl_1.P7, pl_1.P8)
|
|
fmt.Printf(" weight: [%d,%d,%d,%d,%d,%d,%d,%d],\n", pl_1.W1, pl_1.W2, pl_1.W3, pl_1.W4, pl_1.W5, pl_1.W6, pl_1.W7, pl_1.W8)
|
|
fmt.Printf(" temp: %d,\n", pl_1.T)
|
|
fmt.Printf(" temp_change: [%d,%d,%d,%d,%d,%d,%d],\n", pl_1.TC1, pl_1.TC2, pl_1.TC3, pl_1.TC4, pl_1.TC5, pl_1.TC6, pl_1.TC7)
|
|
fmt.Printf("}\n")
|
|
if write2file {
|
|
// Time of first Packet
|
|
var tfp = (time.Now().Unix() / 60) - int64(pl_1.O)
|
|
humidity_values := []uint8{pl_1.H1, pl_1.H2, pl_1.H3, pl_1.H4, pl_1.H5, pl_1.H6, pl_1.H7, pl_1.H8}
|
|
valid_measurements := 0
|
|
for _, v := range humidity_values {
|
|
if v > 0 {
|
|
valid_measurements = valid_measurements + 1
|
|
}
|
|
}
|
|
fmt.Printf("Valid Measurements: %d,\n", valid_measurements)
|
|
var step int64
|
|
if valid_measurements > 1 {
|
|
step = int64(int(pl_1.O) / (valid_measurements - 1))
|
|
}
|
|
|
|
// the first temperature is usually too high (maybe becaus of lorawan send, so we take
|
|
// the second measurement as first..., the same for humidity, which is usually too low...
|
|
t := pl_1.T
|
|
t = t + int16(pl_1.TC1)
|
|
if valid_measurements > 0 {
|
|
WriteDatapoint(tfp, deveui, devaddr, pl_1.Vbat, pl_1.H1, pl_1.P1, pl_1.W1, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
//t = t + int16(pl_1.TC1)
|
|
if valid_measurements > 1 {
|
|
WriteDatapoint(tfp+(step), deveui, devaddr, 0, pl_1.H2, pl_1.P2, pl_1.W2, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC2)
|
|
if valid_measurements > 2 {
|
|
WriteDatapoint(tfp+(2*step), deveui, devaddr, 0, pl_1.H3, pl_1.P3, pl_1.W3, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC3)
|
|
if valid_measurements > 3 {
|
|
WriteDatapoint(tfp+(3*step), deveui, devaddr, 0, pl_1.H4, pl_1.P4, pl_1.W4, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC4)
|
|
if valid_measurements > 4 {
|
|
WriteDatapoint(tfp+(4*step), deveui, devaddr, 0, pl_1.H5, pl_1.P5, pl_1.W5, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC5)
|
|
if valid_measurements > 5 {
|
|
WriteDatapoint(tfp+(5*step), deveui, devaddr, 0, pl_1.H6, pl_1.P6, pl_1.W6, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC6)
|
|
if valid_measurements > 6 {
|
|
WriteDatapoint(tfp+(6*step), deveui, devaddr, 0, pl_1.H7, pl_1.P7, pl_1.W7, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
t = t + int16(pl_1.TC7)
|
|
if valid_measurements > 7 {
|
|
WriteDatapoint(tfp+(7*step), deveui, devaddr, 0, pl_1.H8, pl_1.P8, pl_1.W8, 0, 0, t, lrrlat, lrrlon, 0, 0, 0, 0, 0)
|
|
}
|
|
}
|
|
} else if s[0:2] == "80" {
|
|
fmt.Printf("{\n")
|
|
fmt.Printf(" version: %d,\n", pl_128.Version)
|
|
fmt.Printf(" fw_version: %d,\n", pl_128.Fw_version)
|
|
fmt.Printf(" vbat: %d,\n", pl_128.Vbat)
|
|
fmt.Printf(" humidity: %d\n", pl_128.H)
|
|
fmt.Printf(" pressure: %d\n", pl_128.P)
|
|
fmt.Printf(" weight1: %d\n", pl_128.W1)
|
|
fmt.Printf(" weight2: %d\n", pl_128.W2)
|
|
fmt.Printf(" cal_1_0: %d\n", pl_128.W1_0)
|
|
fmt.Printf(" cal_2_0: %d\n", pl_128.W2_0)
|
|
fmt.Printf(" cal_1_C: %f\n", pl_128.W1_C)
|
|
fmt.Printf(" cal_2_C: %f\n", pl_128.W2_C)
|
|
fmt.Printf(" temp: %d\n", pl_128.T)
|
|
fmt.Printf("}\n")
|
|
if write2file {
|
|
// Time of Packet received
|
|
var tfp = (time.Now().Unix() / 60)
|
|
|
|
// we calculate the weight...
|
|
var w32 int32
|
|
var w uint16
|
|
w1_0_real := pl_128.W1_0
|
|
w2_0_real := pl_128.W2_0
|
|
w32 = int32(((float64(pl_128.W1-w1_0_real) / float64(pl_128.W1_C)) + (float64(pl_128.W2-w2_0_real) / float64(pl_128.W2_C))) / 5.0)
|
|
if w32 < 0 {
|
|
w = 0
|
|
} else if w32 > 65535 {
|
|
// this is not realistic (>320 kg), we set this to 0 as well...
|
|
w = 0
|
|
} else {
|
|
w = uint16(w32)
|
|
}
|
|
|
|
WriteDatapoint(tfp, deveui, devaddr, pl_128.Vbat, pl_128.H, pl_128.P, w, pl_128.W1, pl_128.W2, pl_128.T, lrrlat, lrrlon, pl_128.Fw_version, pl_128.W1_0, pl_128.W2_0, pl_128.W1_C, pl_128.W2_C)
|
|
}
|
|
ProcessInitPacket(deveui, pl_128.W1_0, pl_128.W2_0, pl_128.W1_C, pl_128.W2_C, pl_128.W1, pl_128.W2)
|
|
} else if s[0:2] == "81" {
|
|
fmt.Printf("{\n")
|
|
fmt.Printf(" version: %d,\n", pl_129.Version)
|
|
fmt.Printf(" fw_version: %d,\n", pl_129.Fw_version)
|
|
fmt.Printf(" vbat: %d,\n", pl_129.Vbat)
|
|
fmt.Printf(" humidity: %d\n", pl_129.H)
|
|
fmt.Printf(" pressure: %d\n", pl_129.P)
|
|
fmt.Printf(" weight1: %d\n", pl_129.W1)
|
|
fmt.Printf(" weight2: %d\n", pl_129.W2)
|
|
fmt.Printf(" weight: %d\n", pl_129.W)
|
|
fmt.Printf(" temp: %d\n", pl_129.T)
|
|
fmt.Printf("}\n")
|
|
if write2file {
|
|
// Time of Packet received
|
|
var tfp = (time.Now().Unix() / 60)
|
|
|
|
WriteDatapoint(tfp, deveui, devaddr, pl_129.Vbat, pl_129.H, pl_129.P, pl_129.W, pl_129.W1, pl_129.W2, pl_129.T, lrrlat, lrrlon, pl_129.Fw_version, 0, 0, 0, 0)
|
|
}
|
|
}
|
|
// Send alert if necessary
|
|
if val, ok := alertMap[deveui]; ok {
|
|
sendAlert(deveui, val)
|
|
delete(alertMap, deveui)
|
|
// alte Werte loeschen
|
|
deleteValues(deveui)
|
|
}
|
|
if val2, ok2 := alertLogMap[deveui]; ok2 {
|
|
WriteStringToFile(val2, deveui, false)
|
|
delete(alertLogMap, deveui)
|
|
}
|
|
}
|
|
|
|
func IsDayTime() bool {
|
|
hours, _, _ := time.Now().Clock()
|
|
return ((hours >= 6) && (hours < 22))
|
|
}
|
|
|
|
func WriteDatapoint(mytime int64, deveui string, devaddr string, v uint8, h uint8, p uint8, w uint16, w1 int32, w2 int32, t int16, lrrlat float32, lrrlon float32, fw_version int32, w1_0 int32, w2_0 int32, w1_c float32, w2_c float32) {
|
|
// wir nehmen humidity als Referenz, wenn diese > 0 ist, dann ist es
|
|
// eine gueltige Messung
|
|
var vp int16 // Voltage in %
|
|
var implausible bool
|
|
//fmt.Printf("WriteDatapoint: h: %d\n", h)
|
|
if h > 0 {
|
|
vp = int16(v) - 70
|
|
if vp < 0 {
|
|
vp = 0
|
|
} else if vp > 100 {
|
|
vp = 100
|
|
}
|
|
|
|
s := ""
|
|
|
|
sv := ""
|
|
if v > 0 {
|
|
sv = fmt.Sprintf("v=%di,vp=%di,", int32(v)*7+2510, vp)
|
|
}
|
|
sfw := ""
|
|
if fw_version > 0 {
|
|
sfw = fmt.Sprintf(",fw_version=%di,w1=%di,w2=%di,w1_0=%di,w2_0=%di,w1_c=%f,w2_c=%f", fw_version, w1, w2, w1_0, w2_0, w1_c, w2_c)
|
|
}
|
|
s = fmt.Sprintf("measurement,deveui=%s devaddr=\"%s\",%sh=%di,p=%di,w=%di,t=%.1f,lrrlat=%f,lrrlon=%f%s %d\n", deveui, devaddr, sv, h, int32(p)+825, uint32(w)*5, float32(t)/10, lrrlat, lrrlon, sfw, mytime*60*1000*1000*1000)
|
|
|
|
implausible = (w1 == NOT_PLAUSIBLE_32) || (w2 == NOT_PLAUSIBLE_32) || (w == NOT_PLAUSIBLE_16)
|
|
|
|
WriteStringToFile(s, deveui, implausible)
|
|
|
|
w_gram := uint32(w) * 5
|
|
if !implausible {
|
|
addValue(deveui, w_gram)
|
|
w_loss := getMaxValue(deveui) - w_gram
|
|
if w_loss > 750 && IsDayTime() {
|
|
// Schwarmalarm!
|
|
alertLogMap[deveui] = fmt.Sprintf("alert,deveui=%s reason=\"swarmalarm\",w=%di,w_loss=%di %d\n", deveui, w_gram, w_loss, mytime*60*1000*1000*1000)
|
|
location, _ := time.LoadLocation("Europe/Zurich")
|
|
alertMap[deveui] = fmt.Sprintf("*** Schwarmalarm ***\n%s\n%s\nGewichtsverlust: %d g", getDevAlias(deveui), time.Unix(mytime*60, 0).In(location).Format("02.01.2006 15:04"), w_loss)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func WriteStringToFile(s string, deveui string, implausible bool) {
|
|
if !implausible {
|
|
n, err := file.WriteString(s)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
if n != len(s) {
|
|
fmt.Println("failed to write data")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Also write to individual files
|
|
datestr := time.Now().Format("2006-01")
|
|
individual_file := "/home/beieli/lorahandler/logs/" + deveui + "-" + datestr + ".log"
|
|
if implausible {
|
|
individual_file = "/home/beieli/lorahandler/logs/" + "implausible" + "-" + datestr + ".log"
|
|
}
|
|
|
|
fi, err := os.OpenFile(individual_file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
defer fi.Close()
|
|
fi.WriteString(s)
|
|
}
|
|
|
|
func Convert2Hex(payload_raw string) (string, error) {
|
|
res := "error"
|
|
p, err := base64.StdEncoding.DecodeString(payload_raw)
|
|
if err == nil {
|
|
res = hex.EncodeToString(p)
|
|
}
|
|
return res, err
|
|
}
|
|
|
|
func main() {
|
|
// Init Redis
|
|
initDB()
|
|
|
|
// Init alertMap
|
|
alertMap = make(map[string]string)
|
|
alertLogMap = make(map[string]string)
|
|
|
|
// Open Output File
|
|
f, err := os.OpenFile(outputfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
file = f
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
defer file.Close()
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
var message_swisscom Message
|
|
var message_ttn MessageTTN
|
|
if r.Body == nil {
|
|
http.Error(w, "Please send a request body", 400)
|
|
return
|
|
}
|
|
|
|
buf, bodyErr := ioutil.ReadAll(r.Body)
|
|
if bodyErr != nil {
|
|
log.Print("bodyErr ", bodyErr.Error())
|
|
http.Error(w, bodyErr.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
|
|
rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf))
|
|
log.Printf("BODY: %s", rdr1)
|
|
r.Body = rdr2
|
|
|
|
// We look for the text string "mini-beieli" in the buffer, then it is a TTN Packet,
|
|
// otherwise it should be a Swisscom Packet...
|
|
|
|
fmt.Printf("Length of JSON Body: %d\n", len(buf))
|
|
|
|
if strings.Contains(string(buf), "mini-beieli") {
|
|
fmt.Printf("Seems to be a TTN Packet...\n")
|
|
// it is probably a TTN package...
|
|
err := json.NewDecoder(r.Body).Decode(&message_ttn)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 400)
|
|
return
|
|
}
|
|
|
|
fmt.Println("Time: " + message_ttn.Metadata.Time)
|
|
fmt.Println("Payload Raw: " + message_ttn.Payload_raw)
|
|
fmt.Println("DevEUI: " + message_ttn.Hardware_serial)
|
|
payload_hex, err := Convert2Hex(message_ttn.Payload_raw)
|
|
if err == nil {
|
|
DecodePayload(payload_hex, message_ttn.Hardware_serial, "N/A", 0, 0, true)
|
|
} else {
|
|
fmt.Println("Error: could not convert raw_package to hex: %s" + message_ttn.Payload_raw)
|
|
}
|
|
} else {
|
|
fmt.Printf("Seems to be a Swisscom Packet...\n")
|
|
// it is probably a Swisscom package...
|
|
err := json.NewDecoder(r.Body).Decode(&message_swisscom)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 400)
|
|
return
|
|
}
|
|
fmt.Println("Time: " + message_swisscom.Prop.Time)
|
|
fmt.Println("Payload Hex: " + message_swisscom.Prop.Payload_hex)
|
|
fmt.Println("DevEUI: " + message_swisscom.Prop.DevEUI)
|
|
fmt.Println("DevAddr: " + message_swisscom.Prop.DevAddr)
|
|
fmt.Printf("LrrLAT: %f\n", message_swisscom.Prop.LrrLAT)
|
|
fmt.Printf("LrrLON: %f\n", message_swisscom.Prop.LrrLON)
|
|
DecodePayload(message_swisscom.Prop.Payload_hex, message_swisscom.Prop.DevEUI, message_swisscom.Prop.DevAddr, message_swisscom.Prop.LrrLAT, message_swisscom.Prop.LrrLON, true)
|
|
}
|
|
})
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
}
|