lijian 2 rokov pred
commit
3823c2a74a
16 zmenil súbory, kde vykonal 871 pridanie a 0 odobranie
  1. 91 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 9 0
      .idea/AT-Server.iml
  4. 8 0
      .idea/modules.xml
  5. 6 0
      .idea/vcs.xml
  6. 4 0
      config.toml
  7. 15 0
      go.mod
  8. 58 0
      go.sum
  9. 31 0
      main.go
  10. 282 0
      netAtSDK/at_client.go
  11. 5 0
      netAtSDK/at_sdk_test.go
  12. 1 0
      netAtSDK/structs.go
  13. 90 0
      server/.gitignore
  14. 126 0
      server/client.go
  15. 68 0
      server/crc.go
  16. 69 0
      server/server.go

+ 91 - 0
.gitignore

@@ -0,0 +1,91 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Go template
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+bin/
+

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 9 - 0
.idea/AT-Server.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/AT-Server.iml" filepath="$PROJECT_DIR$/.idea/AT-Server.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 4 - 0
config.toml

@@ -0,0 +1,4 @@
+[Server]
+Addr = "0.0.0.0"
+Port = 8999
+RunMode = "debug"

+ 15 - 0
go.mod

@@ -0,0 +1,15 @@
+module AT-Server
+
+go 1.17
+
+require github.com/gogf/gf v1.16.9
+
+require (
+	github.com/fatih/color v1.12.0 // indirect
+	github.com/fsnotify/fsnotify v1.4.9 // indirect
+	github.com/mattn/go-colorable v0.1.8 // indirect
+	github.com/mattn/go-isatty v0.0.12 // indirect
+	go.opentelemetry.io/otel v1.0.0 // indirect
+	go.opentelemetry.io/otel/trace v1.0.0 // indirect
+	golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
+)

+ 58 - 0
go.sum

@@ -0,0 +1,58 @@
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
+github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
+github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI=
+go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
+go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4=
+go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 31 - 0
main.go

@@ -0,0 +1,31 @@
+package main
+
+import (
+	"AT-Server/server"
+	"context"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/glog"
+	"github.com/gogf/gf/os/gproc"
+	"os"
+)
+
+func main() {
+	err := glog.SetLevelStr(g.Cfg().GetString("Server.RunMode"))
+	if err != nil {
+		panic(err)
+	}
+	ctx := context.Background()
+	srv := server.NewServer(
+		ctx,
+		g.Cfg().GetString("Server.Addr"),
+		g.Cfg().GetInt("Server.Port"),
+	)
+	go func() {
+		_ = srv.Start()
+	}()
+
+	gproc.AddSigHandlerShutdown(func(sig os.Signal) {
+		_ = srv.Stop()
+	})
+	gproc.Listen()
+}

+ 282 - 0
netAtSDK/at_client.go

@@ -0,0 +1,282 @@
+package netAtSDK
+
+import (
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/encoding/gbinary"
+	"github.com/gogf/gf/net/gtcp"
+	"github.com/gogf/gf/os/glog"
+	"github.com/gogf/gf/text/gstr"
+	"time"
+)
+
+const timeOut = 5 * time.Second
+
+type ATClient struct {
+	conn     *gtcp.Conn
+	password string
+}
+
+func NetATClient(conn *gtcp.Conn, password string) *ATClient {
+	return &ATClient{
+		conn:     conn,
+		password: password,
+	}
+}
+
+// Save 保存配置
+func (a *ATClient) Save() error {
+	_, err := a.send("AT&W")
+	return err
+}
+
+// GetIMEI 查询串号
+// AT+GSN
+func (a *ATClient) GetIMEI() (imei string, err error) {
+	buf, err := a.send("GSN")
+	if err != nil {
+		return
+	}
+	imei = buf[0]
+	return
+}
+
+// GetICCID  查询ICCID
+// AT+ICCID
+func (a *ATClient) GetICCID() (iccid string, err error) {
+	buf, err := a.send("ICCID")
+	if err != nil {
+		return
+	}
+	iccid = buf[0]
+	iccid = gstr.Replace(iccid, "+ICCID:", "")
+	return
+}
+
+// GetGPS 查询基站信息
+// AT+GPS:查询基站信息, +GPS: Lac:0x581b,CellId:0x0b8aa201
+func (a *ATClient) GetGPS() (gps string, err error) {
+	buf, err := a.send("GPS")
+	if err != nil {
+		return
+	}
+	gps = buf[0]
+	gps = gstr.Replace(gps, "+GPS: ", "")
+	return
+}
+
+// SetDTUMode 设备工作模式
+// AT+DTUMODE:配置工作模式
+/*
+第一个参数 0 不启用该通道; 1 TCP/UDP 透传; 2 MQTT 透传; 3 塔石 DTU 云连接; 4 塔石 IOT 云连接; 5 HTTP 传输模式; 6 阿里云直连
+B 取值范围 1~4,代表 4 个不同的 SOCKET 通道,省略时仅配置通道 1 默认值:+DTUMODE:1,0,0,0 默认第一路为 TCP/UDP 透传,其他几路为默认关闭
+*/
+func (a *ATClient) SetDTUMode(mode int, channel int) error {
+	_, err := a.send(fmt.Sprintf("DTUMODE=%d,%d", mode, channel))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetDTUMode 查询工作模式
+// 返回1,0,0,0格式
+func (a *ATClient) GetDTUMode() (mode string, err error) {
+	buf, err := a.send("DTUMODE?")
+	if err != nil {
+		return
+	}
+	mode = buf[0]
+	mode = gstr.Replace(mode, "+DTUMODE= ", "")
+	return
+}
+
+// SetServerAddr 设置连接服务器地址
+/*
+格式:AT+DSCADDR=A,"B","C",D
+A 取值范围 1-4,代表 4 个不同的 SOCKET
+通道
+B 为 TCP 或者 UDP
+C 为服务器地址,可填域名或 IP,长度最大 255
+D 为端口号,范围 1~65535
+*/
+func (a *ATClient) SetServerAddr(channel int, cType string, Ip string, port int) error {
+	_, err := a.send(fmt.Sprintf("DSCADDR=%d, \"%s\", \"%s\", %d", channel, cType, Ip, port))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetServerAddr 查询连接服务器 地址
+// +DSCADDR:1,"tcp","cloud.tastek.cn",10067
+// +DSCADDR:2,"tcp","cloud.tastek.cn",10067
+func (a *ATClient) GetServerAddr() (ret []string, err error) {
+	buf, err := a.send("DSCADDR?")
+	if err != nil {
+		return
+	}
+	for _, v := range buf {
+		newV := gstr.Replace(v, "+DSCADDR:", "")
+		ret = append(ret, newV)
+	}
+	return
+}
+
+// SetUARTParam AT+UARTCFG:串口参数设置
+/*
+格式:AT+UARTCFG=A,B,C,D
+A 串口波特率,支持的波特率为 115200、57600、38400、19200、14400、9600、4800、 2400、1200
+B 数据位,取值范围 0~1 0 7 位数据位 1 8 位数据位
+C 校验位,取值范围 0~2 0 无校验 NONE 1 奇校验 ODD 2 偶校验 EVEN
+D 停止位,取值范围 0~1 0 1 位停止位 1 2 位停止位
+默认值:+UARTCFG:9600,1,0,0(9600,8,N,1)
+*/
+func (a *ATClient) SetUARTParam(rate, dFlag, cFlag, sFlag int) error {
+	_, err := a.send(fmt.Sprintf("UARTCFG=%d,%d,%d,%d", rate, dFlag, cFlag, sFlag))
+	return err
+}
+
+// GetUARTParam 获取串口配置
+// 返回: 115200,1,0,0
+func (a *ATClient) GetUARTParam() (ret string, err error) {
+	buf, err := a.send("UARTCFG?")
+	if err != nil {
+		return
+	}
+	ret = gstr.Replace(buf[0], "+UARTCFG: ", "")
+	return
+}
+
+// SetDTUID AT+DTUID:注册包设置
+/*
+格式:AT+DTUID=A,B,C,"D"(,E)
+A 注册包模式,取值范围 0-3 0 不启用注册包 1 仅连接时上传 2 和数据一起上传,在数据前 3 包括 1,2
+B 注册包内容,取值范围 0-2 0 自定义注册包 1 IMEI(15 位模块对应的唯一识别码) 2 ICCID(20 位 SIM 卡对应编码)
+C 数据输入格式,取值范围 0-1 0 ASCII 格式 1 HEX 格式
+D 数据内容,最大长度为 512Byte
+E 可选参数,取值范围 1-4,分别代表 4 个 socket 通道,省略时仅配置通道 1 默认值:+DTUID: 0,0,0,"tas001"
+*/
+func (a *ATClient) SetDTUID(mode, idType, format int, content string, channel ...int) error {
+	cmd := fmt.Sprintf("DTUID=%d,%d,%d,\"%s\"", mode, idType, format, content)
+	if len(channel) > 0 {
+		cmd = cmd + fmt.Sprintf(",%d", channel[0])
+	}
+	_, err := a.send(cmd)
+	return err
+}
+
+// GetDTUID 查询注册包配置
+/*
+返回:1,0,0,"dtuid1",1
+1,0,0,"dtuid1",1
+*/
+func (a *ATClient) GetDTUID() (ret []string, err error) {
+	buf, err := a.send("DTUID?")
+	if err != nil {
+		return
+	}
+	for _, v := range buf {
+		newV := gstr.Replace(v, "+DTUID: ", "")
+		ret = append(ret, newV)
+	}
+	return
+}
+
+// SetKeepAlive AT+KEEPALIVE:心跳包设置
+/*
+格式:AT+KEEPALIVE=A,B,"C"(,D)
+A 心跳时间间隔,取值范围 0-3600   0 不启用 1-3600 固定时间间隔,单位秒
+B 数据输入格式 0 ASCII 模式 1 HEX 模式
+C 数据内容,最大长度 256 ,固件 3.4.0 之后版本支持特殊含义字段,例如$(IMEI)、 $(ICCID)、$(TIME)、$(CSQ)等
+D 可选参数,范围 1-4,分别代表 4 个 socket 通道,省略时仅配置通道 1 默认值:+KEEPALIVE: 0,0,"ping"
+*/
+func (a *ATClient) SetKeepAlive(dur, format int, content string, channel ...int) error {
+	cmd := fmt.Sprintf("KEEPALIVE=%d,%d,\"%s\"", dur, format, content)
+	if len(channel) > 0 {
+		cmd = cmd + fmt.Sprintf(",%d", channel[0])
+	}
+	_, err := a.send(cmd)
+	return err
+}
+
+// GetKeepAlive 查询心跳包配置
+// +KEEPALIVE: 120,0,"keepalive",1
+func (a *ATClient) GetKeepAlive() (ret []string, err error) {
+	buf, err := a.send("KEEPALIVE?")
+	if err != nil {
+		return
+	}
+	for _, v := range buf {
+		newV := gstr.Replace(v, "+KEEPALIVE: ", "")
+		ret = append(ret, newV)
+	}
+	return
+}
+
+// GetAll 查询所有参数
+// AT+DTUALL:查询所有参数
+/*
++DTUMODE:1,0,0,0 +TCPMODBUS:0,0,0,0 +UARTCFG:9600,1,0,0 +WORKMODE:0 +REVNUM:"10086",4,0 +CSTT:"","","" +RELINKTIME:3 +DSCTIME:300 +ACKTIME: 0 +PORTTIME: 0 +DTUFILTER:0 ------------SOCKET_1------------ +DSCADDR:1,"tcp","122.231.164.87",10158
++KEEPALIVE:0,0,"ping",1 +DTUID:0,0,0,"tas001",1 +DTUCLOUD:2,"866262040274796","42ee4b0449154f959be44fc242337599",1 ------------SOCKET_2------------ +DSCADDR:2,"tcp","cloud.tastek.cn",10067 +KEEPALIVE:0,0,"ping",2 +DTUID:0,0,0,"tas002",2 +DTUCLOUD:0,"cloudID","cloudPWD",2 ------------SOCKET_3------------ +DSCADDR:3,"tcp","cloud.tastek.cn",10067 +KEEPALIVE:0,0,"ping",3 +DTUID:0,0,0,"tas003",3 +DTUCLOUD:0,"20060059","123456",3 ------------SOCKET_4------------ +DSCADDR:4,"tcp","cloud.tastek.cn",10067 +KEEPALIVE:0,0,"ping",4 +DTUID:0,0,0,"tas003",4 +DTUCLOUD:0,"20060059","123456",4
+
+*/
+func (a *ATClient) GetAll() (result []string, err error) {
+	result, err = a.send("DTUALL?")
+	if err != nil {
+		return
+	}
+	return
+}
+
+// SetPOLL AT+POLL:轮询使能
+/*
+格式:AT+POLL=A,B,C
+A 自定义轮询使能,取值范围 0-1 0 关闭自定义轮询功能 1 开启自定义轮询功能
+B 轮询时间间隔,取值范围 1-3600,表示每条启用指令间的时间间隔,单位秒
+C 轮询数据输入格式,取值范围 0-1 0 ASCII 格式,设置为 0 表示之后输入的轮询指令均为以 ASCII 形式轮询, 即输入什么字串就轮询什么字串 1 HEX 格式,设置为 1 表示之后输入的轮询指令需要满足 HEX 格式,轮 询时会自动转成 16 进制对应的 ASCII 字串 默认值:+POLL:0,10,1
+*/
+func (a *ATClient) SetPOLL(sw, dur, format int) error {
+	_, err := a.send(fmt.Sprintf("POLL=%d,%d,%d", sw, dur, format))
+	return err
+}
+
+// GetPOLL 查询轮询使能
+// +POLL: 0,10,1
+func (a *ATClient) GetPOLL() (result string, err error) {
+	buf, err := a.send("POLL?")
+	result = gstr.Replace(buf[0], "+POLL: ", "")
+	return
+}
+
+func (a *ATClient) send(cmd string) (ret []string, err error) {
+	glog.Debugf("发送AT+%s", cmd)
+	buf := a.prepareCommand(cmd)
+	if _, err = a.conn.Write(buf); err != nil {
+		return
+	}
+	timer := time.NewTimer(5 * time.Second)
+	for {
+		select {
+		case <-timer.C:
+			err = errors.New("读取AT响应超时")
+			return
+		default:
+			buf, err = a.conn.RecvLine()
+			if err != nil {
+				return
+			}
+			str := string(buf)
+			glog.Debugf("收到:%s, %2X", str, buf)
+			if gstr.Contains(str, "OK") {
+				return
+			}
+			ret = append(ret, str)
+		}
+	}
+}
+
+func (a *ATClient) prepareCommand(cmd string) []byte {
+	cmdStr := fmt.Sprintf("@DTU:%s:%s", a.password, cmd)
+	return gbinary.EncodeString(cmdStr)
+}

+ 5 - 0
netAtSDK/at_sdk_test.go

@@ -0,0 +1,5 @@
+package netAtSDK
+
+func startTestServer() {
+
+}

+ 1 - 0
netAtSDK/structs.go

@@ -0,0 +1 @@
+package netAtSDK

+ 90 - 0
server/.gitignore

@@ -0,0 +1,90 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Go template
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+

+ 126 - 0
server/client.go

@@ -0,0 +1,126 @@
+package server
+
+import (
+	"AT-Server/netAtSDK"
+	"github.com/gogf/gf/encoding/gbinary"
+	"github.com/gogf/gf/net/gtcp"
+	"github.com/gogf/gf/os/glog"
+	"io"
+	"net"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+)
+
+const defaultPassword = "0000"
+
+type Client struct {
+	Id            string
+	srv           *Server
+	conn          *gtcp.Conn
+	closeChan     chan struct{}
+	closeHandler  func(id string, c *Client)
+	onReg         func(id string, c *Client)
+	isReg         bool
+	mu            sync.Mutex
+	waitingATResp bool
+	atClient      *netAtSDK.ATClient
+}
+
+func NewClient(s *Server, conn *gtcp.Conn) *Client {
+	return &Client{
+		srv:       s,
+		conn:      conn,
+		atClient:  netAtSDK.NetATClient(conn, defaultPassword),
+		closeChan: make(chan struct{}),
+	}
+}
+
+func (c *Client) SetId(id string) {
+	c.Id = id
+}
+
+func (c *Client) ReadLoop() {
+	for {
+		select {
+		case <-c.closeChan:
+			return
+		default:
+			buf, err := c.conn.Recv(-1)
+			if err != nil {
+				c.readError(err)
+				glog.Error(c.srv.ctx, err.Error())
+				return
+			}
+			if len(buf) > 0 {
+				if !c.isReg {
+					id := gbinary.DecodeToString(buf)
+					glog.Debugf("收到注册包!id:%s", id)
+					c.SetId(id)
+					c.isReg = true
+					if c.onReg != nil {
+						c.onReg(c.Id, c)
+					}
+					return
+				}
+			}
+		}
+	}
+}
+
+func (c *Client) readError(err error) {
+	defer c.closeConnection()
+	if err == io.EOF || isErrConnReset(err) {
+		return
+	}
+	glog.Errorf("读取数据发生错误:%s", err.Error())
+}
+func (c *Client) closeConnection() {
+	_ = c.conn.Close()
+	c.conn = nil
+	close(c.closeChan)
+	c.isReg = false
+	if c.closeHandler != nil {
+		c.closeHandler(c.Id, c)
+	}
+}
+
+// isErrConnReset read: connection reset by peer
+func isErrConnReset(err error) bool {
+	if ne, ok := err.(*net.OpError); ok {
+		return strings.Contains(ne.Err.Error(), syscall.ECONNRESET.Error())
+	}
+	return false
+}
+
+func (c *Client) send(buf []byte) error {
+	if c.conn == nil {
+		return nil
+	}
+	glog.Debugf("----->%2X", buf)
+	err := c.conn.Send(buf)
+	if err != nil {
+		glog.Error(c.srv.ctx, err)
+		c.closeConnection()
+		return err
+	}
+	return nil
+}
+
+func (c *Client) TestGPS() {
+	for {
+		select {
+		case <-c.closeChan:
+			return
+		default:
+			str, err := c.atClient.GetGPS()
+			if err != nil {
+				glog.Debugf("查询出错:%s", err)
+				continue
+			}
+			glog.Debugf("查询结果 :%s", str)
+		}
+		time.Sleep(10 * time.Second)
+	}
+}

+ 68 - 0
server/crc.go

@@ -0,0 +1,68 @@
+package server
+
+// Table of CRC values for high–order byte
+var crcHighBytes = []byte{
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+}
+
+// Table of CRC values for low-order byte
+var crcLowBytes = []byte{
+	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
+	0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
+	0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
+	0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
+	0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
+	0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
+	0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
+	0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
+	0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
+	0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
+	0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
+	0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
+	0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
+	0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
+	0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
+	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40,
+}
+
+// Cyclical Redundancy Checking
+type crc struct {
+	high byte
+	low  byte
+}
+
+func (crc *crc) reset() *crc {
+	crc.high = 0xFF
+	crc.low = 0xFF
+	return crc
+}
+
+func (crc *crc) pushBytes(bs []byte) *crc {
+	var idx, b byte
+
+	for _, b = range bs {
+		idx = crc.low ^ b
+		crc.low = crc.high ^ crcHighBytes[idx]
+		crc.high = crcLowBytes[idx]
+	}
+	return crc
+}
+
+func (crc *crc) value() uint16 {
+	return uint16(crc.high)<<8 | uint16(crc.low)
+}

+ 69 - 0
server/server.go

@@ -0,0 +1,69 @@
+package server
+
+import (
+	"AT-Server/netAtSDK"
+	"context"
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/container/gmap"
+	"github.com/gogf/gf/net/gtcp"
+	"github.com/gogf/gf/os/glog"
+)
+
+type Server struct {
+	srv     *gtcp.Server
+	ctx     context.Context
+	addr    string
+	port    int
+	clients *gmap.Map
+}
+
+func NewServer(ctx context.Context, addr string, port int) *Server {
+	return &Server{
+		clients: gmap.New(),
+		ctx:     ctx,
+		addr:    addr,
+		port:    port,
+	}
+}
+
+func (s *Server) Start() error {
+	glog.Printf("服务端启动[%s:%d]", s.addr, s.port)
+	srv := gtcp.NewServer(fmt.Sprintf("%s:%d", s.addr, s.port), s.onClientConnect)
+	s.srv = srv
+	return s.srv.Run()
+}
+
+func (s *Server) Stop() error {
+	s.clients.Iterator(func(k interface{}, v interface{}) bool {
+		glog.Debugf("客户端:%s,退出", k)
+		client := v.(*Client)
+		client.closeConnection()
+		return true
+	})
+	return s.srv.Close()
+}
+
+func (s *Server) onClientConnect(conn *gtcp.Conn) {
+	glog.Debugf("新的设备接入:%s", conn.RemoteAddr())
+	client := NewClient(s, conn)
+	client.closeHandler = func(id string, c *Client) {
+		s.clients.Remove(id)
+		glog.Debugf("客户端下线:%s", id)
+	}
+	client.onReg = func(id string, c *Client) {
+		glog.Debugf("客户端上线:%s", id)
+		s.clients.Set(id, c)
+	}
+	go client.TestGPS()
+	go client.ReadLoop()
+}
+
+// GetATClient 获取AT操作
+func (s *Server) GetATClient(imei string) (*netAtSDK.ATClient, error) {
+	if !s.clients.Contains(imei) {
+		return nil, errors.New("客户端不存在:" + imei)
+	}
+	client := s.clients.Get(imei).(*Client)
+	return client.atClient, nil
+}