From d15653021f106a7b08264e4f5c13d3292d5959b6 Mon Sep 17 00:00:00 2001 From: Joerg Lehmann Date: Fri, 13 Dec 2019 20:06:12 +0100 Subject: [PATCH] implement remote calibration with downlinks --- calibration.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ lorahandler.go | 39 +++++++++++++-- persistence.go | 110 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 calibration.go diff --git a/calibration.go b/calibration.go new file mode 100644 index 0000000..d56e683 --- /dev/null +++ b/calibration.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "log" + "time" + "math" + "strings" + "strconv" + "io/ioutil" + "net/http" +) + +const api_url="https://proxy1.lpn.swisscom.ch/thingpark/lrc/rest" + +func MakePost(url string) { + req, err := http.NewRequest("POST", url, nil) + if err != nil { + log.Fatal("Error reading request. ", err) + } + + // Set headers + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // 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.Printf("%s\n", body) +} + +func ResetNodeToZero(deveui string, w1_0 int32, w2_0 int32) { + fmt.Printf("Called ResetNodeToZero for deveui %s\n", deveui) + s := fmt.Sprintf("%s/downlink?DevEUI=%s&FPort=1&Payload=00", api_url, deveui) + MakePost(s) + // values should be taken from next packet... + SetDownlinkCommand(deveui,"update_cal_from_node") +} + +func CalibrateScale(deveui string, downlink_command string, w1 int32, w2 int32) { + fmt.Printf("Called CalibrateScale for deveui %s, downlink_command: %s\n", deveui, downlink_command) + var cur_cal_settings CalSettings + cur_cal_settings = GetCurrentCalibrationSettings(deveui) + var calibration_weight_gram int32 + var calibration_weight2_gram int32 + tokens := strings.Split(downlink_command," ") + if (len(tokens) < 2) { + // no value in gram included! + fmt.Printf("Error: invalid downlink_command: %s, examples: \"tare_a 10000\", \"tare_b 10000\", \"tare 10000 10000\" devuid: %s\n", downlink_command, deveui) + return + } + n, err := strconv.ParseInt(tokens[1], 10, 32) + if err == nil { + calibration_weight_gram = int32(n) + } else { + fmt.Printf("Error: cannot get tare weight in gram: %s, deveui: %s\n", downlink_command, deveui) + return + } + + if (len(tokens) > 2) { + n, err := strconv.ParseInt(tokens[2], 10, 32) + if err == nil { + calibration_weight2_gram = int32(n) + } else { + fmt.Printf("Error: cannot get tare weight2 in gram: %s, deveui: %s\n", downlink_command, deveui) + return + } + } + + if (tokens[0] == "tare_a") { + new_w1_c := float32(w1 - cur_cal_settings.w1_0) / float32(calibration_weight_gram) + valstr := fmt.Sprintf("%08X%08X%08X%08X",uint32(cur_cal_settings.w1_0),uint32(cur_cal_settings.w2_0),math.Float32bits(new_w1_c),math.Float32bits(cur_cal_settings.w2_c)) + s := fmt.Sprintf("%s/downlink?DevEUI=%s&FPort=1&Payload=01%s", api_url, deveui, valstr) + MakePost(s) + } else if (tokens[0] == "tare_b") { + new_w2_c := float32(w2 - cur_cal_settings.w2_0) / float32(calibration_weight_gram) + valstr := fmt.Sprintf("%08X%08X%08X%08X",uint32(cur_cal_settings.w1_0),uint32(cur_cal_settings.w2_0),math.Float32bits(cur_cal_settings.w2_c),math.Float32bits(new_w2_c)) + s := fmt.Sprintf("%s/downlink?DevEUI=%s&FPort=1&Payload=01%s", api_url, deveui, valstr) + MakePost(s) + } else if (tokens[0] == "tare") { + new_w1_c := float32(w1 - cur_cal_settings.w1_0) / float32(calibration_weight_gram) + new_w2_c := float32(w2 - cur_cal_settings.w2_0) / float32(calibration_weight2_gram) + valstr := fmt.Sprintf("%08X%08X%08X%08X",uint32(cur_cal_settings.w1_0),uint32(cur_cal_settings.w2_0),math.Float32bits(new_w1_c),math.Float32bits(new_w2_c)) + s := fmt.Sprintf("%s/downlink?DevEUI=%s&FPort=1&Payload=01%s", api_url, deveui, valstr) + MakePost(s) + } else { + fmt.Printf("Error: either use tare_a or tare_b or tare; deveui %s\n", deveui) + } + SetDownlinkCommand(deveui,"update_cal_from_node") +} + +func UpdateNodeCalibrationSettings(deveui string) { + fmt.Printf("Called UpdateNodeCalibrationSettings for deveui %s\n", deveui) + var cur_cal_settings CalSettings + cur_cal_settings = GetCurrentCalibrationSettings(deveui) + + valstr := fmt.Sprintf("%08X%08X%08X%08X",uint32(cur_cal_settings.w1_0),uint32(cur_cal_settings.w2_0),math.Float32bits(cur_cal_settings.w1_c),math.Float32bits(cur_cal_settings.w2_c)) + s := fmt.Sprintf("%s/downlink?DevEUI=%s&FPort=1&Payload=01%s", api_url, deveui, valstr) + MakePost(s) +} + +func UpdateCalibrationSettingsFromNode(deveui string, w1_0 int32, w2_0 int32, w1_c float32, w2_c float32) { + fmt.Printf("Called UpdateCalibrationSettingsFromNode for deveui %s\n", deveui) + var new_cal_settings CalSettings + new_cal_settings.w1_0 = w1_0 + new_cal_settings.w2_0 = w2_0 + new_cal_settings.w1_c = w1_c + new_cal_settings.w2_c = w2_c + SetCurrentCalibrationSettings(deveui,new_cal_settings) + SetDownlinkCommand(deveui,"do_nothing") +} diff --git a/lorahandler.go b/lorahandler.go index 82ff715..09d56eb 100644 --- a/lorahandler.go +++ b/lorahandler.go @@ -9,6 +9,7 @@ import ( "os" "io/ioutil" "log" + "strings" "net/http" "time" ) @@ -105,6 +106,37 @@ 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_0, w2_0) + } 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 if (downlink_command == "update_cal_on_node") { + // update calibration settings on node + UpdateNodeCalibrationSettings(deveui) + } else if (downlink_command == "update_cal_from_node") { + // update calibration settings from node + UpdateCalibrationSettingsFromNode(deveui, w1_0, w2_0, w1_c, w2_c) + } else { + fmt.Printf("Error: Unknown downlink_command: %s (DevEUI: %s)\n", downlink_command, deveui) + } +} + func DecodePayload(s string, deveui string, devaddr string, lrrlat float32, lrrlon float32, write2file bool) { var ba []byte var pl_1 payload_1 @@ -202,6 +234,7 @@ func DecodePayload(s string, deveui string, devaddr string, lrrlat float32, lrrl 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) @@ -250,13 +283,13 @@ func WriteDatapoint(mytime int64, deveui string, devaddr string, v uint8, h uint sv := "" if (v > 0) { - sv = fmt.Sprintf(",v=%di,vp=%di,",int32(v)*7+2510,vp) + 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=%d,w2_0=%d,w1_c=%f,w2_c=%f",fw_version,w1,w2,w1_0,w2_0,w1_c,w2_c) + 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\"%s,h=%di,p=%di,w=%di,t=%.1f,lrrlat=%f,lrrlon=%f%s %d\n",deveui,devaddr,sv,h,int32(p)+825,w*5,float32(t)/10,lrrlat,lrrlon,sfw,mytime*60*1000*1000*1000) + 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,w*5,float32(t)/10,lrrlat,lrrlon,sfw,mytime*60*1000*1000*1000) WriteStringToFile(s) diff --git a/persistence.go b/persistence.go index f07348b..e493f17 100644 --- a/persistence.go +++ b/persistence.go @@ -3,6 +3,7 @@ package main import ( "fmt" "time" + "strconv" "github.com/gomodule/redigo/redis" ) @@ -11,6 +12,13 @@ var globalPool *redis.Pool const lastvaluesPrefix string = "lastvalues:" const devPrefix string = "dev:" +type CalSettings struct { + w1_0 int32 + w2_0 int32 + w1_c float32 + w2_c float32 +} + func newPool() *redis.Pool { return &redis.Pool{ // Maximum number of idle connections in the pool. @@ -187,3 +195,105 @@ func getSmsnumber(deveui string) string { return res } + +func GetDownlinkCommand(deveui string) string { + // 0: do nothing... + res := "do_nothing" + + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + downlink_command, err := redis.String(conn.Do("HGET", devPrefix+deveui, "downlink_command")) + if err == nil { + res = downlink_command + } else { + fmt.Println(err) + res = "do_nothing" + } + + return res +} + +func SetDownlinkCommand(deveui string, new_command string) error { + conn := globalPool.Get() + defer conn.Close() + + // SET object + _, err := conn.Do("HMSET", devPrefix+deveui, "downlink_command", new_command) + if err != nil { + return err + } + + return nil +} + +func GetCurrentCalibrationSettings(deveui string) CalSettings { + var res CalSettings + if deveui == "" { + return res + } + + conn := globalPool.Get() + defer conn.Close() + + s_w1_0, err := redis.String(conn.Do("HGET", devPrefix+deveui, "w1_0")) + if err == nil { + n, err := strconv.ParseInt(s_w1_0, 10, 32) + if err == nil { + res.w1_0 = int32(n) + } + } + + s_w2_0, err := redis.String(conn.Do("HGET", devPrefix+deveui, "w2_0")) + if err == nil { + n, err := strconv.ParseInt(s_w2_0, 10, 32) + if err == nil { + res.w2_0 = int32(n) + } + } + + s_w1_c, err := redis.String(conn.Do("HGET", devPrefix+deveui, "w1_c")) + if err == nil { + f, err := strconv.ParseFloat(s_w1_c, 32) + if err == nil { + res.w1_c = float32(f) + } + } + + s_w2_c, err := redis.String(conn.Do("HGET", devPrefix+deveui, "w2_c")) + if err == nil { + f, err := strconv.ParseFloat(s_w2_c, 32) + if err == nil { + res.w2_c = float32(f) + } + } + + return res +} + +func SetCurrentCalibrationSettings(deveui string, cal_settings CalSettings) error { + if deveui == "" { + return nil + } + + conn := globalPool.Get() + defer conn.Close() + + w1_0 := fmt.Sprintf("%d",cal_settings.w1_0) + w2_0 := fmt.Sprintf("%d",cal_settings.w2_0) + w1_c := fmt.Sprintf("%f",cal_settings.w1_c) + w2_c := fmt.Sprintf("%f",cal_settings.w2_c) + + // SET object + _, err := conn.Do("HMSET", devPrefix+deveui, "w1_0",w1_0,"w2_0",w2_0,"w1_c",w1_c,"w2_c",w2_c) + if err != nil { + return err + } + + return nil +} +