at_client.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. package netAtSDK
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/gogf/gf/encoding/gbinary"
  6. "github.com/gogf/gf/net/gtcp"
  7. "github.com/gogf/gf/os/glog"
  8. "github.com/gogf/gf/text/gstr"
  9. "time"
  10. )
  11. const timeOut = 5 * time.Second
  12. type ATClient struct {
  13. conn *gtcp.Conn
  14. password string
  15. }
  16. func NetATClient(conn *gtcp.Conn, password string) *ATClient {
  17. return &ATClient{
  18. conn: conn,
  19. password: password,
  20. }
  21. }
  22. // Save 保存配置
  23. func (a *ATClient) Save() error {
  24. _, err := a.send("AT&W")
  25. return err
  26. }
  27. // Restart 模块重启
  28. // AT+CFUN=1,1
  29. func (a *ATClient) Restart() error {
  30. _, err := a.send("CFUN=1,1")
  31. return err
  32. }
  33. // GetIMEI 查询串号
  34. // AT+GSN
  35. func (a *ATClient) GetIMEI() (imei string, err error) {
  36. buf, err := a.send("GSN")
  37. if err != nil {
  38. return
  39. }
  40. imei = buf[0]
  41. return
  42. }
  43. // GetICCID 查询ICCID
  44. // AT+ICCID
  45. func (a *ATClient) GetICCID() (iccid string, err error) {
  46. buf, err := a.send("ICCID")
  47. if err != nil {
  48. return
  49. }
  50. iccid = buf[0]
  51. iccid = gstr.Replace(iccid, "+ICCID:", "")
  52. return
  53. }
  54. // GetGPS 查询基站信息
  55. // AT+GPS:查询基站信息, +GPS: Lac:0x581b,CellId:0x0b8aa201
  56. func (a *ATClient) GetGPS() (gps string, err error) {
  57. buf, err := a.send("GPS")
  58. if err != nil {
  59. return
  60. }
  61. gps = buf[0]
  62. gps = gstr.Replace(gps, "+GPS: ", "")
  63. return
  64. }
  65. // SetDTUMode 设备工作模式
  66. // AT+DTUMODE:配置工作模式
  67. /*
  68. 第一个参数 0 不启用该通道; 1 TCP/UDP 透传; 2 MQTT 透传; 3 塔石 DTU 云连接; 4 塔石 IOT 云连接; 5 HTTP 传输模式; 6 阿里云直连
  69. B 取值范围 1~4,代表 4 个不同的 SOCKET 通道,省略时仅配置通道 1 默认值:+DTUMODE:1,0,0,0 默认第一路为 TCP/UDP 透传,其他几路为默认关闭
  70. */
  71. func (a *ATClient) SetDTUMode(mode int, channel int) error {
  72. _, err := a.send(fmt.Sprintf("DTUMODE=%d,%d", mode, channel))
  73. if err != nil {
  74. return err
  75. }
  76. return nil
  77. }
  78. // GetDTUMode 查询工作模式
  79. // 返回1,0,0,0格式
  80. func (a *ATClient) GetDTUMode() (mode string, err error) {
  81. buf, err := a.send("DTUMODE?")
  82. if err != nil {
  83. return
  84. }
  85. mode = buf[0]
  86. mode = gstr.Replace(mode, "+DTUMODE= ", "")
  87. return
  88. }
  89. // SetServerAddr 设置连接服务器地址
  90. /*
  91. 格式:AT+DSCADDR=A,"B","C",D
  92. A 取值范围 1-4,代表 4 个不同的 SOCKET
  93. 通道
  94. B 为 TCP 或者 UDP
  95. C 为服务器地址,可填域名或 IP,长度最大 255
  96. D 为端口号,范围 1~65535
  97. */
  98. func (a *ATClient) SetServerAddr(channel int, cType string, Ip string, port int) error {
  99. _, err := a.send(fmt.Sprintf("DSCADDR=%d, \"%s\", \"%s\", %d", channel, cType, Ip, port))
  100. if err != nil {
  101. return err
  102. }
  103. return nil
  104. }
  105. // GetServerAddr 查询连接服务器 地址
  106. // +DSCADDR:1,"tcp","cloud.tastek.cn",10067
  107. // +DSCADDR:2,"tcp","cloud.tastek.cn",10067
  108. func (a *ATClient) GetServerAddr() (ret []string, err error) {
  109. buf, err := a.send("DSCADDR?")
  110. if err != nil {
  111. return
  112. }
  113. for _, v := range buf {
  114. newV := gstr.Replace(v, "+DSCADDR:", "")
  115. ret = append(ret, newV)
  116. }
  117. return
  118. }
  119. // SetUARTParam AT+UARTCFG:串口参数设置
  120. /*
  121. 格式:AT+UARTCFG=A,B,C,D
  122. A 串口波特率,支持的波特率为 115200、57600、38400、19200、14400、9600、4800、 2400、1200
  123. B 数据位,取值范围 0~1 0 7 位数据位 1 8 位数据位
  124. C 校验位,取值范围 0~2 0 无校验 NONE 1 奇校验 ODD 2 偶校验 EVEN
  125. D 停止位,取值范围 0~1 0 1 位停止位 1 2 位停止位
  126. 默认值:+UARTCFG:9600,1,0,0(9600,8,N,1)
  127. */
  128. func (a *ATClient) SetUARTParam(rate, dFlag, cFlag, sFlag int) error {
  129. _, err := a.send(fmt.Sprintf("UARTCFG=%d,%d,%d,%d", rate, dFlag, cFlag, sFlag))
  130. return err
  131. }
  132. // GetUARTParam 获取串口配置
  133. // 返回: 115200,1,0,0
  134. func (a *ATClient) GetUARTParam() (ret string, err error) {
  135. buf, err := a.send("UARTCFG?")
  136. if err != nil {
  137. return
  138. }
  139. ret = gstr.Replace(buf[0], "+UARTCFG: ", "")
  140. return
  141. }
  142. // SetDTUID AT+DTUID:注册包设置
  143. /*
  144. 格式:AT+DTUID=A,B,C,"D"(,E)
  145. A 注册包模式,取值范围 0-3 0 不启用注册包 1 仅连接时上传 2 和数据一起上传,在数据前 3 包括 1,2
  146. B 注册包内容,取值范围 0-2 0 自定义注册包 1 IMEI(15 位模块对应的唯一识别码) 2 ICCID(20 位 SIM 卡对应编码)
  147. C 数据输入格式,取值范围 0-1 0 ASCII 格式 1 HEX 格式
  148. D 数据内容,最大长度为 512Byte
  149. E 可选参数,取值范围 1-4,分别代表 4 个 socket 通道,省略时仅配置通道 1 默认值:+DTUID: 0,0,0,"tas001"
  150. */
  151. func (a *ATClient) SetDTUID(mode, idType, format int, content string, channel ...int) error {
  152. cmd := fmt.Sprintf("DTUID=%d,%d,%d,\"%s\"", mode, idType, format, content)
  153. if len(channel) > 0 {
  154. cmd = cmd + fmt.Sprintf(",%d", channel[0])
  155. }
  156. _, err := a.send(cmd)
  157. return err
  158. }
  159. // GetDTUID 查询注册包配置
  160. /*
  161. 返回:1,0,0,"dtuid1",1
  162. 1,0,0,"dtuid1",1
  163. */
  164. func (a *ATClient) GetDTUID() (ret []string, err error) {
  165. buf, err := a.send("DTUID?")
  166. if err != nil {
  167. return
  168. }
  169. for _, v := range buf {
  170. newV := gstr.Replace(v, "+DTUID: ", "")
  171. ret = append(ret, gstr.TrimAll(newV))
  172. }
  173. return
  174. }
  175. // SetKeepAlive AT+KEEPALIVE:心跳包设置
  176. /*
  177. 格式:AT+KEEPALIVE=A,B,"C"(,D)
  178. A 心跳时间间隔,取值范围 0-3600 0 不启用 1-3600 固定时间间隔,单位秒
  179. B 数据输入格式 0 ASCII 模式 1 HEX 模式
  180. C 数据内容,最大长度 256 ,固件 3.4.0 之后版本支持特殊含义字段,例如$(IMEI)、 $(ICCID)、$(TIME)、$(CSQ)等
  181. D 可选参数,范围 1-4,分别代表 4 个 socket 通道,省略时仅配置通道 1 默认值:+KEEPALIVE: 0,0,"ping"
  182. */
  183. func (a *ATClient) SetKeepAlive(dur, format int, content string, channel ...int) error {
  184. cmd := fmt.Sprintf("KEEPALIVE=%d,%d,\"%s\"", dur, format, content)
  185. if len(channel) > 0 {
  186. cmd = cmd + fmt.Sprintf(",%d", channel[0])
  187. }
  188. _, err := a.send(cmd)
  189. return err
  190. }
  191. // GetKeepAlive 查询心跳包配置
  192. // +KEEPALIVE: 120,0,"keepalive",1
  193. func (a *ATClient) GetKeepAlive() (ret []string, err error) {
  194. buf, err := a.send("KEEPALIVE?")
  195. if err != nil {
  196. return
  197. }
  198. for _, v := range buf {
  199. newV := gstr.Replace(v, "+KEEPALIVE: ", "")
  200. ret = append(ret, gstr.TrimAll(newV))
  201. }
  202. return
  203. }
  204. // GetAll 查询所有参数
  205. // AT+DTUALL:查询所有参数
  206. /*
  207. +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
  208. +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
  209. */
  210. func (a *ATClient) GetAll() (result []string, err error) {
  211. result, err = a.send("DTUALL?")
  212. if err != nil {
  213. return
  214. }
  215. return
  216. }
  217. // SetPOLL AT+POLL:轮询使能
  218. /*
  219. 格式:AT+POLL=A,B,C
  220. A 自定义轮询使能,取值范围 0-1 0 关闭自定义轮询功能 1 开启自定义轮询功能
  221. B 轮询时间间隔,取值范围 1-3600,表示每条启用指令间的时间间隔,单位秒
  222. C 轮询数据输入格式,取值范围 0-1 0 ASCII 格式,设置为 0 表示之后输入的轮询指令均为以 ASCII 形式轮询, 即输入什么字串就轮询什么字串 1 HEX 格式,
  223. 设置为 1 表示之后输入的轮询指令需要满足 HEX 格式,轮 询时会自动转成 16 进制对应的 ASCII 字串 默认值:+POLL:0,10,1
  224. */
  225. func (a *ATClient) SetPOLL(sw, dur, format int) error {
  226. _, err := a.send(fmt.Sprintf("POLL=%d,%d,%d", sw, dur, format))
  227. return err
  228. }
  229. // GetPOLL 查询轮询使能
  230. // +POLL: 0,10,1
  231. func (a *ATClient) GetPOLL() (result string, err error) {
  232. buf, err := a.send("POLL?")
  233. result = gstr.Replace(buf[0], "+POLL: ", "")
  234. return
  235. }
  236. // SetPOLLStr 设置轮询字符串
  237. // AT+POLLSTR:轮询字串设置
  238. /*
  239. 格式:AT+POLLSTR=A,B,C,"D"
  240. A 轮询字串号,取值范围 1-10
  241. B 字串轮询使能,取值范围 0-1 0 禁用该条轮询 1 启用该条轮询
  242. C 字串 CRC 使能,取值范围 0-1 0 无操作 1 对所输入字串进行 Modbus CRC 校验并在轮询时添加在字串末尾
  243. D 轮询字串数据,如果在 AT+POLL 指令中设置了 HEX 标志位为 1,那么必须以 16 进制输入,
  244. 轮询时自动转换成 BIN 格式(例:所输入字串为"313233414243", 实际轮询的字串为"123ABC")
  245. 具体字符对应关系可以对照以下网址 http://ascii.911cha.com/
  246. 默认值:+POLLSTR:1,0,0,"313233
  247. */
  248. func (a *ATClient) SetPOLLStr(index, sw, crc int, str string) error {
  249. _, err := a.send(fmt.Sprintf("POLLSTR=%d,%d,%d,%s", index, sw, crc, str))
  250. return err
  251. }
  252. // GetPollStr 获取轮询字符串配置
  253. // +POLLSTR:1,0,0,"313233"
  254. func (a *ATClient) GetPollStr() (result []string, err error) {
  255. buf, err := a.send("POLLSTR?")
  256. if err != nil {
  257. return
  258. }
  259. for _, v := range buf {
  260. index := gstr.Pos(v, ":")
  261. if index >= 0 {
  262. result = append(result, gstr.TrimAll(v[index+1:]))
  263. }
  264. }
  265. return
  266. }
  267. // GetGPSInfo AT+GPSINFO:查询经纬度(精度 100m),
  268. // 查询经纬度信息,仅注册上基站后生效,属于原始 GPS 坐标
  269. // +GPSINFO: 030.1842195,120.2400433
  270. func (a *ATClient) GetGPSInfo() (gps string, err error) {
  271. buf, err := a.send("GPSINFO")
  272. if err != nil {
  273. return
  274. }
  275. gps = gstr.Replace(buf[0], "+GPSINFO: ", "")
  276. return
  277. }
  278. func (a *ATClient) send(cmd string) (ret []string, err error) {
  279. glog.Debugf("发送AT+%s", cmd)
  280. buf := a.prepareCommand(cmd)
  281. if _, err = a.conn.Write(buf); err != nil {
  282. return
  283. }
  284. timer := time.NewTimer(5 * time.Second)
  285. for {
  286. select {
  287. case <-timer.C:
  288. err = errors.New("读取AT响应超时")
  289. return
  290. default:
  291. buf, err = a.conn.RecvLine()
  292. if err != nil {
  293. return
  294. }
  295. str := string(buf)
  296. glog.Debugf("收到:%s", str)
  297. if gstr.Contains(str, "OK") {
  298. return
  299. }
  300. ret = append(ret, str)
  301. }
  302. }
  303. }
  304. func (a *ATClient) prepareCommand(cmd string) []byte {
  305. cmdStr := fmt.Sprintf("@DTU:%s:%s", a.password, cmd)
  306. return gbinary.EncodeString(cmdStr)
  307. }