You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
16 KiB
594 lines
16 KiB
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
|
"github.com/lxn/walk"
|
|
. "github.com/lxn/walk/declarative"
|
|
"github.com/lxn/win"
|
|
"go.bug.st/serial.v1"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type DeviceInfo struct {
|
|
ID int64 `json:"id"`
|
|
DevEui string `json:"devEUI"`
|
|
DevAddr string `json:"devAddr"`
|
|
Username string `json:"username"`
|
|
CreateAt string `json:"createAt"`
|
|
}
|
|
|
|
type Config struct {
|
|
MQTT struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
SSL bool `json:"ssl"`
|
|
CACert string `json:"ca_cert"`
|
|
TLSCert string `json:"tls_cert"`
|
|
TLSKey string `json:"tls_key"`
|
|
}
|
|
}
|
|
|
|
type CSModel struct {
|
|
walk.TableModelBase
|
|
walk.SorterBase
|
|
sortColumn int
|
|
sortOrder walk.SortOrder
|
|
items []*DeviceInfo
|
|
}
|
|
|
|
func (m *CSModel) RowCount() int {
|
|
return len(m.items)
|
|
}
|
|
|
|
func (m *CSModel) Value(row, col int) interface{} {
|
|
item := m.items[row]
|
|
|
|
switch col {
|
|
case 0:
|
|
return item.ID
|
|
case 1:
|
|
return item.DevEui
|
|
case 2:
|
|
return item.DevAddr
|
|
case 3:
|
|
return item.Username
|
|
case 4:
|
|
return item.CreateAt
|
|
}
|
|
panic("unexpected col")
|
|
}
|
|
|
|
func (m *CSModel) Sort(col int, order walk.SortOrder) error {
|
|
m.sortColumn, m.sortOrder = col, order
|
|
sort.Stable(m)
|
|
return m.SorterBase.Sort(col, order)
|
|
}
|
|
|
|
func (m *CSModel) Len() int {
|
|
return len(m.items)
|
|
}
|
|
|
|
func (m *CSModel) Less(i, j int) bool {
|
|
a, b := m.items[i], m.items[j]
|
|
c := func(ls bool) bool {
|
|
if m.sortOrder == walk.SortAscending {
|
|
return ls
|
|
}
|
|
return !ls
|
|
}
|
|
|
|
switch m.sortColumn {
|
|
case 0:
|
|
return c(a.ID < b.ID)
|
|
case 1:
|
|
return c(a.DevEui < b.DevEui)
|
|
case 2:
|
|
return c(a.DevAddr < b.DevAddr)
|
|
case 3:
|
|
return c(a.Username < b.Username)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (m *CSModel) Swap(i, j int) {
|
|
m.items[i], m.items[j] = m.items[j], m.items[i]
|
|
}
|
|
|
|
func NewCSModel() *CSModel {
|
|
return new(CSModel)
|
|
}
|
|
|
|
type CSMainWindow struct {
|
|
*walk.MainWindow
|
|
mqttClient mqtt.Client
|
|
serialPort serial.Port
|
|
model *CSModel
|
|
tv *walk.TableView
|
|
host,username,password*walk.LineEdit
|
|
port *walk.NumberEdit
|
|
connect, disconnect,caConf,send,open,close *walk.PushButton
|
|
ssl *walk.CheckBox
|
|
msg,recvData,sendData *walk.TextEdit
|
|
commPort,baudRate,dataBits,stopBits,parity *walk.ComboBox
|
|
confFileName string
|
|
conf Config
|
|
icon *walk.Icon
|
|
recvDatas,sendDatas string
|
|
ch chan string
|
|
serialOpen bool
|
|
}
|
|
|
|
func main() {
|
|
mw := &CSMainWindow{model: NewCSModel(),ch:make(chan string,100),serialOpen:false}
|
|
mw.icon,_ = walk.NewIconFromResourceId(3)
|
|
maxWidth := int(win.GetSystemMetrics(win.SM_CXSCREEN)) - 200
|
|
maxHeight := int(win.GetSystemMetrics(win.SM_CYSCREEN)) - 100
|
|
err := MainWindow{
|
|
AssignTo: &mw.MainWindow,
|
|
Title: "DeviceIDCS",
|
|
Icon: mw.icon,
|
|
Size: Size{maxWidth,maxHeight },
|
|
Layout: VBox{},
|
|
Children: []Widget{
|
|
Composite{
|
|
Layout: HBox{MarginsZero: true},
|
|
Children: []Widget{
|
|
Label{Text:"主机/IP:"},
|
|
LineEdit{AssignTo:&mw.host},
|
|
Label{Text:"端口:"},
|
|
NumberEdit{AssignTo:&mw.port,MinSize:Size{Width:50}},
|
|
Label{Text:"用户名:"},
|
|
LineEdit{AssignTo:&mw.username},
|
|
Label{Text:"密码:"},
|
|
LineEdit{AssignTo:&mw.password,PasswordMode:true},
|
|
PushButton{Text:"连接", AssignTo: &mw.connect,OnClicked:mw.Connect},
|
|
PushButton{Text:"断开连接", AssignTo:&mw.disconnect, Enabled:false, OnClicked: mw.Disconnect},
|
|
CheckBox{Text:"开启SSL/TLS",AssignTo:&mw.ssl,OnClicked:mw.SSL},
|
|
PushButton{Text:"证书配置",Enabled:false,AssignTo:&mw.caConf,OnClicked: mw.Config},
|
|
PushButton{Text:"清空数据", OnClicked: mw.Clean},
|
|
},
|
|
},
|
|
Composite{
|
|
Layout: HBox{},
|
|
Children: []Widget{
|
|
TableView{
|
|
AssignTo: &mw.tv,
|
|
MinSize:Size{Width:maxWidth*3/5},
|
|
CheckBoxes: true,
|
|
ColumnsOrderable: true,
|
|
MultiSelection: true,
|
|
Columns: []TableViewColumn{
|
|
{Title: "序号"},
|
|
{Title: "终端EUI"},
|
|
{Title: "终端Addr"},
|
|
{Title: "用户名"},
|
|
{Title: "创建时间"},
|
|
},
|
|
Model: mw.model,
|
|
},
|
|
Composite{
|
|
Layout:VBox{},
|
|
Children:[]Widget{
|
|
GroupBox{
|
|
Title:"串口设置",
|
|
Layout:VBox{SpacingZero:true},
|
|
Children:[]Widget{
|
|
Composite{
|
|
Layout: Grid{Columns:4},
|
|
Children: []Widget{
|
|
Label{Text:"端口号:"},
|
|
ComboBox{AssignTo:&mw.commPort},
|
|
Label{Text:"波特率:"},
|
|
ComboBox{AssignTo:&mw.baudRate,Model:[]string{"115200","57600","56000","38400","19200","14400","9600"}},
|
|
Label{Text:"数据位:"},
|
|
ComboBox{AssignTo:&mw.dataBits,Model:[]string{"8","7","6","5"}},
|
|
Label{Text:"停止位:"},
|
|
ComboBox{AssignTo:&mw.stopBits,Model:[]string{"1","1.5","2"}},
|
|
Label{Text:"奇偶校验:"},
|
|
ComboBox{AssignTo:&mw.parity,Model:[]string{"None","Odd","Even","Mark","Space"}},
|
|
HSpacer{},
|
|
HSpacer{},
|
|
HSpacer{},
|
|
HSpacer{},
|
|
PushButton{Text:"打开串口", AssignTo: &mw.open,OnClicked:mw.Open},
|
|
PushButton{Text:"关闭串口", AssignTo: &mw.close,Enabled:false,OnClicked:mw.Close1},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
GroupBox{
|
|
Title:"串口发送",
|
|
Layout:VBox{SpacingZero:true},
|
|
Children:[]Widget{
|
|
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.recvData,ReadOnly:true,HScroll:true,VScroll:true},
|
|
TextEdit{AssignTo:&mw.sendData,ReadOnly:true,HScroll:true,VScroll:true},
|
|
},
|
|
},
|
|
},
|
|
}.Create()
|
|
if err != nil {
|
|
panic("DeviceIDCS窗口创建失败")
|
|
}
|
|
_ = mw.port.SetValue(1883)
|
|
_ = mw.baudRate.SetCurrentIndex(0)
|
|
_ = mw.dataBits.SetCurrentIndex(0)
|
|
_ = mw.stopBits.SetCurrentIndex(0)
|
|
_ = mw.parity.SetCurrentIndex(0)
|
|
|
|
go func(mw *CSMainWindow) {
|
|
for{
|
|
ports, _ := serial.GetPortsList()
|
|
if (len(ports) > 0 && mw.commPort.Model() == nil) ||
|
|
(mw.commPort.Model() != nil && len(ports) != len(mw.commPort.Model().([]string))) {
|
|
_ = mw.commPort.SetModel(ports)
|
|
}
|
|
time.Sleep(time.Second)
|
|
}
|
|
}(mw)
|
|
|
|
dir,_ := os.Getwd()
|
|
mw.confFileName = dir + "/config.json"
|
|
data, err := ioutil.ReadFile(mw.confFileName)
|
|
if err == nil {
|
|
err = json.Unmarshal(data, &mw.conf)
|
|
if err != nil {
|
|
msg := "配置文件格式错误:" + err.Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
} else {
|
|
_ = mw.host.SetText(mw.conf.MQTT.Host)
|
|
_ = mw.port.SetValue(float64(mw.conf.MQTT.Port))
|
|
_ = mw.username.SetText(mw.conf.MQTT.Username)
|
|
_ = mw.password.SetText(mw.conf.MQTT.Password)
|
|
mw.ssl.SetChecked(mw.conf.MQTT.SSL)
|
|
}
|
|
}
|
|
mw.Run()
|
|
}
|
|
|
|
func newTLSConfig(cafile, certFile, certKeyFile string) (*tls.Config, error) {
|
|
if cafile == "" && certFile == "" && certKeyFile == "" {
|
|
return nil, nil
|
|
}
|
|
tlsConfig := &tls.Config{}
|
|
// Import trusted certificates from CAfile.pem.
|
|
if cafile != "" {
|
|
cacert, err := ioutil.ReadFile(cafile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
certpool := x509.NewCertPool()
|
|
certpool.AppendCertsFromPEM(cacert)
|
|
|
|
tlsConfig.RootCAs = certpool // RootCAs = certs used to verify server cert.
|
|
}
|
|
|
|
// Import certificate and the key
|
|
if certFile != "" && certKeyFile != "" {
|
|
kp, err := tls.LoadX509KeyPair(certFile, certKeyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{kp}
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
func (mw *CSMainWindow) Connect() {
|
|
if !mw.serialOpen {
|
|
msg := "串口未打开,请先打开串口"
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
return
|
|
}
|
|
go func() {
|
|
if mw.host.Text() != "" && mw.port.Value() > 0 {
|
|
opts := mqtt.NewClientOptions()
|
|
serverAddr := fmt.Sprintf("%s:%d",mw.host.Text(),int(mw.port.Value()))
|
|
if mw.ssl.Checked() {
|
|
if mw.conf.MQTT.CACert != "" {
|
|
serverAddr = fmt.Sprintf("ssl://%s:%d",mw.host.Text(),int(mw.port.Value()))
|
|
tlsconfig, err := newTLSConfig(mw.conf.MQTT.CACert, mw.conf.MQTT.TLSCert, mw.conf.MQTT.TLSKey)
|
|
if err != nil {
|
|
msg := "证书加载错误:" + err.Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
}
|
|
if tlsconfig != nil {
|
|
opts.SetTLSConfig(tlsconfig)
|
|
}
|
|
}else{
|
|
msg := "证书未配置,服务端单向认证只需配置CA证书,双向认证还需配置客户端证书及秘钥"
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
return
|
|
}
|
|
}
|
|
opts.AddBroker(serverAddr)
|
|
opts.SetUsername(mw.username.Text())
|
|
opts.SetPassword(mw.password.Text())
|
|
opts.SetConnectTimeout(5 * time.Second)
|
|
mw.mqttClient = mqtt.NewClient(opts)
|
|
token := mw.mqttClient.Connect()
|
|
if token.Wait() && token.Error() == nil {
|
|
mw.conf.MQTT.Host = mw.host.Text()
|
|
mw.conf.MQTT.Port = int(mw.port.Value())
|
|
mw.conf.MQTT.Username = mw.username.Text()
|
|
mw.conf.MQTT.Password = mw.password.Text()
|
|
var confData bytes.Buffer
|
|
d,_ := json.Marshal(&mw.conf)
|
|
_ = json.Indent(&confData, d, "", "\t")
|
|
_ = ioutil.WriteFile(mw.confFileName,confData.Bytes(),0644)
|
|
mw.host.SetEnabled(false)
|
|
mw.port.SetEnabled(false)
|
|
mw.username.SetEnabled(false)
|
|
mw.password.SetEnabled(false)
|
|
mw.disconnect.SetEnabled(true)
|
|
mw.connect.SetEnabled(false)
|
|
mw.ssl.SetEnabled(false)
|
|
mw.caConf.SetEnabled(false)
|
|
mw.send.SetEnabled(false)
|
|
mw.msg.SetEnabled(false)
|
|
topic := "device/id"
|
|
token := mw.mqttClient.Subscribe(topic,0, func(client mqtt.Client, message mqtt.Message) {
|
|
var dev DeviceInfo
|
|
_ = json.Unmarshal(message.Payload(), &dev)
|
|
const TIME_LAYOUT = "2006-01-02 15:04:05"
|
|
if lt, err := time.ParseInLocation(time.RFC3339Nano, dev.CreateAt, time.UTC);err == nil {
|
|
dev.CreateAt = lt.Local().Format(TIME_LAYOUT)
|
|
}
|
|
cmds := []string{"AT+DEUI=" + dev.DevEui + "\r\n","AT+DADDR=" + dev.DevAddr + "\r\n"}
|
|
for _,cmd := range cmds{
|
|
_, err := mw.serialPort.Write([]byte(cmd))
|
|
if err != nil {
|
|
mw.sendDatas += err.Error() + "\r\n"
|
|
_ = mw.sendData.SetText(mw.sendDatas)
|
|
continue
|
|
}
|
|
mw.sendDatas += cmd
|
|
_ = mw.sendData.SetText(mw.sendDatas)
|
|
mw.ch <- cmd
|
|
}
|
|
|
|
mw.model.items = append(mw.model.items, &dev)
|
|
mw.model.PublishRowsReset()
|
|
_ = mw.tv.SetSelectedIndexes([]int{})
|
|
})
|
|
if token.Wait() && token.Error() != nil {
|
|
msg := "订阅失败:" + token.Error().Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
}
|
|
}else{
|
|
msg := "连接失败:" + token.Error().Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
}
|
|
}else{
|
|
msg := "主机/IP不能为空,端口不能为0"
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (mw *CSMainWindow) Disconnect() {
|
|
mw.connect.SetEnabled(true)
|
|
mw.host.SetEnabled(true)
|
|
mw.port.SetEnabled(true)
|
|
mw.username.SetEnabled(true)
|
|
mw.password.SetEnabled(true)
|
|
mw.disconnect.SetEnabled(false)
|
|
mw.ssl.SetEnabled(true)
|
|
mw.caConf.SetEnabled(true)
|
|
mw.mqttClient.Disconnect(0)
|
|
mw.send.SetEnabled(true)
|
|
mw.msg.SetEnabled(true)
|
|
}
|
|
|
|
|
|
func (mw *CSMainWindow) Clean() {
|
|
mw.model.items = []*DeviceInfo{}
|
|
mw.model.PublishRowsReset()
|
|
_ = mw.tv.SetSelectedIndexes([]int{})
|
|
}
|
|
func (mw *CSMainWindow) SSL() {
|
|
mw.conf.MQTT.SSL = mw.ssl.Checked()
|
|
if mw.ssl.Checked() {
|
|
mw.caConf.SetEnabled(true)
|
|
}else{
|
|
mw.caConf.SetEnabled(false)
|
|
}
|
|
}
|
|
|
|
func (mw *CSMainWindow) Config() {
|
|
var dlg *walk.Dialog
|
|
var caCert,tlsCert,tlsKey *walk.LineEdit
|
|
var acceptPB, cancelPB *walk.PushButton
|
|
_ = Dialog{
|
|
Title: "配置",
|
|
Layout: VBox{},
|
|
Icon: mw.icon,
|
|
AssignTo: &dlg,
|
|
DefaultButton: &acceptPB,
|
|
CancelButton: &cancelPB,
|
|
MinSize: Size{400, 200},
|
|
Children: []Widget{
|
|
Composite{
|
|
Layout:Grid{Columns: 3},
|
|
Children:[]Widget{
|
|
Label{Text:"CA证书:"},
|
|
LineEdit{Text:Bind("CACert"),AssignTo:&caCert},
|
|
PushButton{Text:"打开",OnClicked: func() {
|
|
dlg := new(walk.FileDialog)
|
|
dlg.Title = "请选择CA证书"
|
|
dlg.Filter = "CA证书 (*.crt)|*.crt|所有文件 (*.*)|*.*"
|
|
if ok, err := dlg.ShowOpen(mw); err != nil {
|
|
return
|
|
} else if !ok {
|
|
return
|
|
}
|
|
_ = caCert.SetText(dlg.FilePath)
|
|
}},
|
|
Label{Text:"客户端证书:"},
|
|
LineEdit{Text:Bind("TLSCert"),AssignTo:&tlsCert},
|
|
PushButton{Text:"打开",OnClicked: func() {
|
|
dlg := new(walk.FileDialog)
|
|
dlg.Title = "请选择客户端证书"
|
|
dlg.Filter = "客户端证书 (*.crt)|*.crt|所有文件 (*.*)|*.*"
|
|
if ok, err := dlg.ShowOpen(mw); err != nil {
|
|
return
|
|
} else if !ok {
|
|
return
|
|
}
|
|
_ = tlsCert.SetText(dlg.FilePath)
|
|
}},
|
|
Label{Text:"客户端证书秘钥:"},
|
|
LineEdit{Text:Bind("TLSKey"),AssignTo:&tlsKey},
|
|
PushButton{Text:"打开",OnClicked: func() {
|
|
dlg := new(walk.FileDialog)
|
|
dlg.Title = "请选择客户端证书秘钥"
|
|
dlg.Filter = "客户端证书秘钥 (*.key)|*.key|所有文件 (*.*)|*.*"
|
|
if ok, err := dlg.ShowOpen(mw); err != nil {
|
|
return
|
|
} else if !ok {
|
|
return
|
|
}
|
|
_ = tlsKey.SetText(dlg.FilePath)
|
|
}},
|
|
},
|
|
},
|
|
Composite{
|
|
Layout: HBox{},
|
|
Children: []Widget{
|
|
HSpacer{},
|
|
PushButton{
|
|
AssignTo: &acceptPB,
|
|
Text: "确定",
|
|
OnClicked: func() {
|
|
mw.conf.MQTT.CACert = caCert.Text()
|
|
mw.conf.MQTT.TLSCert = tlsCert.Text()
|
|
mw.conf.MQTT.TLSKey = tlsKey.Text()
|
|
dlg.Accept()
|
|
},
|
|
},
|
|
PushButton{
|
|
AssignTo: &cancelPB,
|
|
Text: "取消",
|
|
OnClicked: func() { dlg.Cancel() },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}.Create(mw)
|
|
_ = caCert.SetText(mw.conf.MQTT.CACert)
|
|
_ = tlsCert.SetText(mw.conf.MQTT.TLSCert)
|
|
_ = tlsKey.SetText(mw.conf.MQTT.TLSKey)
|
|
dlg.Run()
|
|
}
|
|
func (mw *CSMainWindow) Open() {
|
|
baudRate,err := strconv.Atoi(mw.baudRate.Text())
|
|
if err != nil {
|
|
return
|
|
}
|
|
dataBits,err := strconv.Atoi(mw.dataBits.Text())
|
|
if err != nil {
|
|
return
|
|
}
|
|
mode := &serial.Mode{
|
|
BaudRate: baudRate,
|
|
DataBits: dataBits,
|
|
StopBits: serial.StopBits(mw.stopBits.CurrentIndex()),
|
|
Parity: serial.Parity(mw.parity.CurrentIndex()),
|
|
}
|
|
mw.serialPort,err = serial.Open(mw.commPort.Text(),mode)
|
|
if err != nil {
|
|
msg := "打开串口失败:" + err.Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
return
|
|
}
|
|
mw.open.SetEnabled(false)
|
|
mw.close.SetEnabled(true)
|
|
mw.serialOpen = true
|
|
go RecvMsg(mw)
|
|
}
|
|
func (mw *CSMainWindow) Close1() {
|
|
mw.open.SetEnabled(true)
|
|
mw.close.SetEnabled(false)
|
|
_ = mw.serialPort.Close()
|
|
}
|
|
func (mw *CSMainWindow) SendMsg() {
|
|
if mw.serialPort == nil {
|
|
msg := "请先打开串口"
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
return
|
|
}
|
|
msg := mw.msg.Text() + "\r\n"
|
|
_, err := mw.serialPort.Write([]byte(msg))
|
|
if err != nil {
|
|
msg := "串口写入失败:" + err.Error()
|
|
walk.MsgBox(mw, "错误", msg, walk.MsgBoxIconError)
|
|
return
|
|
}
|
|
mw.ch <- msg
|
|
mw.sendDatas += msg
|
|
_ = mw.sendData.SetText(mw.sendDatas)
|
|
}
|
|
|
|
func RecvMsg(mw *CSMainWindow) {
|
|
buff := make([]byte, 100)
|
|
for {
|
|
n, err := mw.serialPort.Read(buff)
|
|
if err != nil {
|
|
log.Println("读串口错误",err)
|
|
break
|
|
}
|
|
msg := string(buff[:n])
|
|
dim := " ---- "
|
|
cmd := <- mw.ch
|
|
if strings.Contains(msg,"\r\n") {
|
|
msg = strings.Trim(msg,"\r\n")
|
|
}
|
|
if !strings.Contains(cmd,"\r\n") {
|
|
cmd += "\r\n"
|
|
}
|
|
mw.recvDatas += msg + dim + cmd
|
|
_ = mw.recvData.SetText(mw.recvDatas)
|
|
cmd = ""
|
|
if n == 0 {
|
|
fmt.Println("\nEOF")
|
|
break
|
|
}
|
|
}
|
|
}
|