diff --git a/LoRaDTUMock.go b/LoRaDTUMock.go index ea5e21c..e3a5d2b 100644 --- a/LoRaDTUMock.go +++ b/LoRaDTUMock.go @@ -13,9 +13,11 @@ import ( "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "github.com/lxn/win" + "github.com/pkg/errors" "io/ioutil" "math/rand" "os" + "strings" "time" ) @@ -24,11 +26,13 @@ type DTUMainWindow struct { mqttClient paho.Client model *DTUModel tv *walk.TableView + jsonView *walk.TreeView host,username,password *walk.LineEdit - port *walk.NumberEdit + port,sendInterval *walk.NumberEdit connect, disconnect,caConf,send *walk.PushButton - msg *walk.TextEdit - ssl *walk.CheckBox + ascii,noAscii *walk.RadioButton + msg,data *walk.TextEdit + ssl,timeSend *walk.CheckBox connConf ConnectConfig dtuConf DTUConfig connConfFileName string @@ -62,7 +66,7 @@ func main() { CheckBox{Text:"开启SSL/TLS",AssignTo:&mw.ssl,OnClicked:mw.SSL}, PushButton{Text:"证书配置",Enabled:false,AssignTo:&mw.caConf,OnClicked: mw.ConnectConfig}, PushButton{Text:"终端配置",OnClicked: mw.DTUConfig}, - PushButton{Text:"发送",AssignTo:&mw.send,OnClicked: mw.Send}, + PushButton{Text:"清空数据",OnClicked: mw.Clean}, }, }, Composite{ @@ -70,7 +74,7 @@ func main() { Children: []Widget{ TableView{ AssignTo: &mw.tv, - CheckBoxes: true, + MinSize:Size{Width:maxWidth*3/5}, ColumnsOrderable: true, MultiSelection: true, Columns: []TableViewColumn{ @@ -92,6 +96,45 @@ func main() { Model: mw.model, OnItemActivated: mw.tvItemActivated, }, + GroupBox{ + Title:"发送区", + Layout:VBox{SpacingZero:true}, + Children:[]Widget{ + Composite{ + Layout:Grid{Columns:3}, + Children:[]Widget{ + Label{Text:"编码方式:"}, + RadioButton{Text:"ASCII数据",AssignTo:&mw.ascii}, + RadioButton{Text:"Hex数据",AssignTo:&mw.noAscii}, + + CheckBox{Text:"定时发送",AssignTo:&mw.timeSend,OnClicked:mw.TimeSend}, + NumberEdit{AssignTo:&mw.sendInterval}, + Label{Text:"ms/次"}, + }, + }, + Composite{ + Layout:VBox{}, + Children:[]Widget{ + Label{Text:"消息"}, + TextEdit{AssignTo:&mw.msg}, + }, + }, + Composite{ + Layout:HBox{}, + Children:[]Widget{ + HSpacer{}, + PushButton{Text:"发送",AssignTo:&mw.send,OnClicked: mw.SendMsg}, + }, + }, + }, + }, + }, + }, + Composite{ + Layout: HBox{}, + Children: []Widget{ + TextEdit{AssignTo:&mw.data,ReadOnly:true,HScroll:true,VScroll:true}, + TreeView{AssignTo: &mw.jsonView}, }, }, }, @@ -100,9 +143,10 @@ func main() { panic("LoRaDTUMock窗口创建失败") } _ = mw.port.SetValue(1883) + _ = mw.sendInterval.SetValue(1000) + mw.ascii.SetChecked(true) dir,_ := os.Getwd() mw.connConfFileName = dir + "/LoRaDTUMock.json" - mw.dtuConfFileName = dir + "/LoRaDTUConf.json" data, err := ioutil.ReadFile(mw.connConfFileName) if err == nil { err = json.Unmarshal(data, &mw.connConf) @@ -116,6 +160,16 @@ func main() { _ = mw.username.SetText(mw.connConf.Username) _ = mw.password.SetText(mw.connConf.Password) } + mw.dtuConfFileName = dir + "/LoRaDTUConf.json" + data, err = ioutil.ReadFile(mw.dtuConfFileName) + if err == nil { + err = json.Unmarshal(data, &mw.dtuConf) + if err != nil { + msg := "配置文件格式错误:" + err.Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return + } + } mw.Run() } @@ -197,7 +251,6 @@ func (mw *DTUMainWindow) DTUConfig() { var otaa *walk.CheckBox var gatewayId,devEUI,devAddr,appKey,appSKey,nwkSKey *walk.LineEdit var fPort,fCnt,freq *walk.NumberEdit - var msg *walk.TextEdit var acceptPB, cancelPB *walk.PushButton _ = Dialog{ Title: "DTU配置", @@ -233,18 +286,16 @@ func (mw *DTUMainWindow) DTUConfig() { LineEdit{AssignTo:&devAddr}, Label{Text:"应用秘钥:"}, LineEdit{AssignTo:&appKey,Enabled:false}, - Label{Text:"应用会话秘钥:"}, - LineEdit{AssignTo:&appSKey}, Label{Text:"网络会话秘钥:"}, LineEdit{AssignTo:&nwkSKey}, + Label{Text:"应用会话秘钥:"}, + LineEdit{AssignTo:&appSKey}, Label{Text:"端口:"}, NumberEdit{AssignTo:&fPort}, Label{Text:"计数:"}, NumberEdit{AssignTo:&fCnt}, Label{Text:"频率:"}, NumberEdit{AssignTo:&freq,Decimals:2}, - Label{Text:"消息:"}, - TextEdit{AssignTo:&msg}, }, }, Composite{ @@ -265,7 +316,6 @@ func (mw *DTUMainWindow) DTUConfig() { mw.dtuConf.FPort = uint8(fPort.Value()) mw.dtuConf.FCnt = uint32(fCnt.Value()) mw.dtuConf.Freq = freq.Value() - mw.dtuConf.msg = []byte(msg.Text()) var confData bytes.Buffer d,_ := json.Marshal(&mw.dtuConf) @@ -283,15 +333,7 @@ func (mw *DTUMainWindow) DTUConfig() { }, }, }.Create(mw) - data, err := ioutil.ReadFile(mw.dtuConfFileName) - if err == nil { - err = json.Unmarshal(data, &mw.dtuConf) - if err != nil { - msg := "配置文件格式错误:" + err.Error() - walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) - return - } - } + otaa.SetChecked(mw.dtuConf.OTAA) _ = gatewayId.SetText(mw.dtuConf.GatewayId) _ = devEUI.SetText(mw.dtuConf.DevEui) @@ -302,10 +344,14 @@ func (mw *DTUMainWindow) DTUConfig() { _ = fPort.SetValue(float64(mw.dtuConf.FPort)) _ = fCnt.SetValue(float64(mw.dtuConf.FCnt)) _ = freq.SetValue(mw.dtuConf.Freq) - _ = msg.SetText(string(mw.dtuConf.msg)) dlg.Run() } +func (mw *DTUMainWindow) Clean() { + mw.model.Items = []*DTU{} + mw.model.PublishRowsReset() + _ = mw.tv.SetSelectedIndexes([]int{}) +} func (mw *DTUMainWindow) SSL() { if mw.ssl.Checked() { mw.caConf.SetEnabled(true) @@ -399,22 +445,6 @@ func (mw *DTUMainWindow) ConnectConfig() { dlg.Run() } -func (mw *DTUMainWindow) BuildUpData() (*packets.PushDataPacket,*lorawan.PHYPayload,error) { - var fCtrl lorawan.FCtrl - _ = fCtrl.UnmarshalBinary([]byte{128}) - return BuildUpData(mw.dtuConf.GatewayId,mw.dtuConf.DevAddr,mw.dtuConf.NwkSKey, - mw.dtuConf.FCnt,mw.dtuConf.FPort,5,2,mw.dtuConf.Freq,7, - lorawan.UnconfirmedDataUp,fCtrl,-51,mw.dtuConf.msg) -} - -func (mw *DTUMainWindow) BuildJoin() (*packets.PushDataPacket,*lorawan.PHYPayload,error) { - mw.dtuConf.devNonce = lorawan.DevNonce(rand.Uint32()) - appEui := "0807060504030201" - return BuildJoin(mw.dtuConf.GatewayId,appEui,mw.dtuConf.DevEui,mw.dtuConf.AppKey, - 5,2,mw.dtuConf.Freq,7,-51, mw.dtuConf.devNonce) -} - - func (mw *DTUMainWindow) HandleData(client paho.Client, message paho.Message){ var downlinkFrame gw.DownlinkFrame err := proto.Unmarshal(message.Payload(),&downlinkFrame) @@ -492,12 +522,16 @@ func (mw *DTUMainWindow) HandleJoinAccept(phy *lorawan.PHYPayload){ } func (mw *DTUMainWindow) HandleDataDown(phy *lorawan.PHYPayload){ - key := mw.dtuConf.NwkSKey + mpl := phy.MACPayload.(*lorawan.MACPayload) + key := mw.dtuConf.AppSKey + if mpl.FPort != nil && *mpl.FPort == 0 { + key = mw.dtuConf.NwkSKey + } + var aseKey lorawan.AES128Key _ = aseKey.UnmarshalText([]byte(key)) err := phy.DecryptFRMPayload(aseKey) if err == nil { - mpl := phy.MACPayload.(*lorawan.MACPayload) mw.dtuConf.DevAddr = mpl.FHDR.DevAddr.String() } } @@ -515,10 +549,17 @@ func (mw *DTUMainWindow) PushData(gatewayEUI string,event string, msg proto.Mess fmt.Println("mqtt message error") } } - -func (mw *DTUMainWindow) Send(){ +func (mw *DTUMainWindow) sendMsg() error{ + if mw.mqttClient == nil || !mw.mqttClient.IsConnected() { + msg := "请先连接服务器" + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return errors.New("请先连接服务器") + } if mw.dtuConf.OTAA && mw.dtuConf.DevAddr == ""{ - packet,phy,_ := mw.BuildJoin() + mw.dtuConf.devNonce = lorawan.DevNonce(rand.Uint32()) + appEui := "0807060504030201" + packet,phy,_ := BuildJoin(mw.dtuConf.GatewayId,appEui,mw.dtuConf.DevEui,mw.dtuConf.AppKey, + 5,2,mw.dtuConf.Freq,7,-51, mw.dtuConf.devNonce) var origData bytes.Buffer jsonData,_ := phy.MarshalJSON() _ = json.Indent(&origData, jsonData, "", " ") @@ -549,10 +590,31 @@ func (mw *DTUMainWindow) Send(){ fmt.Println("join ok") }else{ fmt.Println("join failed") - return + return errors.New("join failed") } } - packet,phy,_ := mw.BuildUpData() + var bmsg []byte + var err error + if mw.ascii.Checked() { + bmsg = []byte(mw.msg.Text()) + }else{ + bmsg,err = hex.DecodeString(mw.msg.Text()) + if err != nil { + msg := "hex数据格式错误:" + err.Error() + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return err + } + } + var fCtrl lorawan.FCtrl + _ = fCtrl.UnmarshalBinary([]byte{128}) + key := mw.dtuConf.NwkSKey + if mw.dtuConf.FPort == 0 { + key = mw.dtuConf.NwkSKey + } + packet,phy,_ := BuildUpData(mw.dtuConf.GatewayId,mw.dtuConf.DevAddr,key, + mw.dtuConf.FCnt,mw.dtuConf.FPort,5,2,mw.dtuConf.Freq,7, + lorawan.UnconfirmedDataUp,fCtrl,-51,bmsg) + var origData bytes.Buffer jsonData,_ := phy.MarshalJSON() _ = json.Indent(&origData, jsonData, "", " ") @@ -568,8 +630,8 @@ func (mw *DTUMainWindow) Send(){ Frequency:packet.Payload.RXPK[0].Freq, FCnt:mw.dtuConf.FCnt, FPort:mw.dtuConf.FPort, - HexData:hex.EncodeToString(mw.dtuConf.msg), - AsciiData:BytesToString(mw.dtuConf.msg), + HexData:hex.EncodeToString(bmsg), + AsciiData:BytesToString(bmsg), Time:time.Now().Format("2006-01-02 15:04:05"), OrigData:origData.String(), } @@ -585,12 +647,58 @@ func (mw *DTUMainWindow) Send(){ d,_ := json.Marshal(&mw.dtuConf) _ = json.Indent(&confData, d, "", "\t") _ = ioutil.WriteFile(mw.dtuConfFileName,confData.Bytes(),0644) + return nil +} + +func (mw *DTUMainWindow) SendMsg() { + go mw.sendMsg() +} +func (mw *DTUMainWindow) SetSend() { + if mw.timeSend.Checked() { + mw.ascii.SetEnabled(false) + mw.noAscii.SetEnabled(false) + mw.sendInterval.SetEnabled(false) + mw.msg.SetEnabled(false) + mw.send.SetEnabled(false) + }else{ + mw.ascii.SetEnabled(true) + mw.noAscii.SetEnabled(true) + mw.sendInterval.SetEnabled(true) + mw.msg.SetEnabled(true) + mw.send.SetEnabled(true) + } } +func (mw *DTUMainWindow) TimeSend() { + if mw.sendInterval.Value() <= 0 { + msg := "时间间隔需大于0" + walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError) + return + } + mw.SetSend() + go func() { + for{ + if !mw.timeSend.Checked() { + break + } + if err := mw.sendMsg();err != nil { + mw.timeSend.SetChecked(false) + mw.SetSend() + break + } + time.Sleep(time.Duration(mw.sendInterval.Value()) * time.Millisecond) + } + }() +} func (mw *DTUMainWindow) tvItemActivated() { msg := "" for _, i := range mw.tv.SelectedIndexes() { msg += mw.model.Items[i].OrigData + "\n" } - walk.MsgBox(mw, "原始数据", msg, walk.MsgBoxOK) + _ = mw.data.SetText(strings.Replace(msg, "\n", "\r\n", -1)) + + m := make(map[string]interface{}) + if err := json.Unmarshal([]byte(msg), &m); err == nil { + _ = mw.jsonView.SetModel(NewJSONModel(m)) + } } \ No newline at end of file diff --git a/jsonModel.go b/jsonModel.go new file mode 100644 index 0000000..6a221cf --- /dev/null +++ b/jsonModel.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "github.com/lxn/walk" +) + +func FormatBool(b bool) string { + if b { + return "true" + } + return "false" +} + +type Node struct { + name string + value interface{} + parent *Node + children []*Node +} + +func NewNode(name string,value interface{},parent *Node) *Node { + return &Node{name: name,value:value,parent: parent} +} + +func (d *Node) Text() string { + txt := d.name + dim := " : " + switch d.value.(type) { + case int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64: + txt += dim + fmt.Sprintf("%d",d.value) + case float32,float64: + txt += dim + fmt.Sprintf("%f",d.value) + case string: + txt += dim + "\"" + d.value.(string) + "\"" + case bool: + txt += dim + FormatBool(d.value.(bool)) + case map[string]interface{}: + txt += "(Object)" + case []interface{}: + txt += "(Array)" + } + return txt +} + +func (d *Node) Parent() walk.TreeItem { + if d.parent == nil { + return nil + } + return d.parent +} + +func (d *Node) ChildCount() int { + if d.children == nil { + d.ResetChildren(); + } + return len(d.children) +} + +func (d *Node) ChildAt(index int) walk.TreeItem { + return d.children[index] +} + +func (d *Node) Image() interface{} { + icon,err := walk.NewIconFromResourceId(3) + if err != nil { + return "" + } + return icon +} + +func (d *Node) ResetChildren() { + d.children = nil + switch d.value.(type) { + case map[string]interface{}: + m := d.value.(map[string]interface{}) + for k,v := range m { + d.children = append(d.children, NewNode(k, v,d)) + } + case []interface{}: + v := d.value.([]interface{}) + for i,_ := range v { + d.children = append(d.children, NewNode(fmt.Sprintf("%d",i), v[i],d)) + } + } +} + +type JSONModel struct { + walk.TreeModelBase + roots []*Node +} + +func NewJSONModel(m map[string]interface{}) *JSONModel { + model := new(JSONModel) + for k,v := range m { + model.roots = append(model.roots, NewNode(k, v,nil)) + } + return model +} + +func (*JSONModel) LazyPopulation() bool { + return true +} + +func (m *JSONModel) RootCount() int { + return len(m.roots) +} + +func (m *JSONModel) RootAt(index int) walk.TreeItem { + return m.roots[index] +} \ No newline at end of file diff --git a/model.go b/model.go index 6aff720..1991fa0 100644 --- a/model.go +++ b/model.go @@ -27,7 +27,6 @@ type DTUConfig struct { FPort uint8 `json:"fPort"` FCnt uint32 `json:"fCnt"` Freq float64 `json:"freq"` - msg []byte devNonce lorawan.DevNonce } diff --git a/utils.go b/utils.go index c0c2cc3..2694775 100644 --- a/utils.go +++ b/utils.go @@ -53,7 +53,7 @@ func newTLSConfig(cafile, certFile, certKeyFile string) (*tls.Config, error) { return tlsConfig, nil } -func BuildUpData(gatewayId,devAddr,nwkSKey string, +func BuildUpData(gatewayId,devAddr, key string, fCnt uint32,fPort,dr,ch uint8,freq,lsnr float64, mType lorawan.MType,fCtrl lorawan.FCtrl,rssi int16, userData []byte) (*packets.PushDataPacket,*lorawan.PHYPayload,error) { @@ -81,15 +81,15 @@ func BuildUpData(gatewayId,devAddr,nwkSKey string, } mac.FRMPayload = []lorawan.Payload{&dataPayload} phy.MACPayload = &mac - var aseKey lorawan.AES128Key - if err := aseKey.UnmarshalText([]byte(nwkSKey));err != nil { + var aesKey lorawan.AES128Key + if err := aesKey.UnmarshalText([]byte(key));err != nil { return nil,nil,err } - if err := phy.EncryptFRMPayload(aseKey);err != nil { + if err := phy.EncryptFRMPayload(aesKey);err != nil { return nil,nil,err } sf :=[...]string{"SF12","SF11","SF10","SF9","SF8","SF7"} - if err := phy.SetUplinkDataMIC(lorawan.LoRaWAN1_0, 0, dr, ch, aseKey, aseKey);err != nil { + if err := phy.SetUplinkDataMIC(lorawan.LoRaWAN1_0, 0, dr, ch, aesKey, aesKey);err != nil { return nil,nil,err } data,err := phy.MarshalBinary(); @@ -154,12 +154,12 @@ func BuildJoin(gatewayId,appEui,devEui,appKey string,dr,ch uint8, JoinEUI: joinEUI, DevNonce: devNonce, } - var aseKey lorawan.AES128Key - if err := aseKey.UnmarshalText([]byte(appKey));err != nil{ + var aesKey lorawan.AES128Key + if err := aesKey.UnmarshalText([]byte(appKey));err != nil{ return nil,nil,err } sf :=[...]string{"SF12","SF11","SF10","SF9","SF8","SF7"} - if err := phy.SetUplinkJoinMIC(aseKey);err != nil{ + if err := phy.SetUplinkJoinMIC(aesKey);err != nil{ return nil,nil,err } data,err := phy.MarshalBinary()