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 CalibrateScale(deveui, downlink_command, w1, w2) } else if strings.HasPrefix(downlink_command, "tare_b ") { // calibrate Scale B : tare_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 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)) }