socketESP8266.lua 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. --- 模块功能:数据链路激活、socketESP8266管理(创建、连接、数据收发、状态维护)
  2. -- @module socketESP8266
  3. -- @author openLuat
  4. -- @license MIT
  5. -- @copyright openLuat
  6. -- @release 2017.9.25
  7. -- require "link_wifi"
  8. require "utils"
  9. module(..., package.seeall)
  10. local powerCbFnc = nil
  11. -- local ril = ril_wifi
  12. local req = wifiRil.request
  13. local valid = {"3", "2", "1", "0"}
  14. local validSsl = {"3", "2", "1", "0"}
  15. local sockets = {}
  16. local socketsSsl = {}
  17. -- 单次发送数据最大值
  18. local SENDSIZE = 1460
  19. -- 缓冲区最大下标
  20. local INDEX_MAX = 256
  21. -- 用户自定义的DNS解析器
  22. local dnsParser
  23. local dnsParserToken = 0
  24. -- SOCKET 是否有可用
  25. -- @return 可用true,不可用false
  26. isReady = link.isReady
  27. local function isSocketActive(ssl)
  28. for _, c in pairs(ssl and socketsSsl or sockets) do
  29. if c.connected then
  30. return true
  31. end
  32. end
  33. end
  34. local function socketStatusNtfy()
  35. sys.publish("SOCKET_ACTIVE", isSocketActive() or isSocketActive(true))
  36. end
  37. local function stopConnectTimer(tSocket, id)
  38. if id and tSocket[id] and tSocket[id].co and coroutine.status(tSocket[id].co) == "suspended" and (tSocket[id].wait == "+SSLCONNECT" or tSocket[id].wait == "+CIPSTART") then
  39. -- and (tSocket[id].wait == "+SSLCONNECT" or (tSocket[id].protocol == "UDP" and tSocket[id].wait == "+CIPSTART")) then
  40. sys.timerStop(coroutine.resume, tSocket[id].co, false, "TIMEOUT")
  41. end
  42. end
  43. local function errorInd(error)
  44. local coSuspended = {}
  45. for k, v in pairs({sockets, socketsSsl}) do
  46. -- if #v ~= 0 then
  47. for _, c in pairs(v) do -- IP状态出错时,通知所有已连接的socket
  48. -- if c.connected or c.created then
  49. if error == 'CLOSED' and not c.ssl then
  50. c.connected = false
  51. socketStatusNtfy()
  52. end
  53. c.error = error
  54. if c.co and coroutine.status(c.co) == "suspended" then
  55. stopConnectTimer(v, c.id)
  56. -- coroutine.resume(c.co, false)
  57. table.insert(coSuspended, c.co)
  58. end
  59. -- end
  60. end
  61. -- end
  62. end
  63. for k, v in pairs(coSuspended) do
  64. if v and coroutine.status(v) == "suspended" then
  65. coroutine.resume(v, false)
  66. end
  67. end
  68. end
  69. sys.subscribe("IP_ERROR_IND", function()
  70. errorInd('IP_ERROR_IND')
  71. end)
  72. sys.subscribe('IP_SHUT_IND', function()
  73. errorInd('CLOSED')
  74. end)
  75. -- 订阅rsp返回的消息处理函数
  76. local function onSocketURC(data, prefix)
  77. local tag, id, result = string.match(data, "([SSL]*)[&]*(%d), *([%u :%d]+)")
  78. tSocket = (tag == "SSL" and socketsSsl or sockets)
  79. if not id or not tSocket[id] then
  80. log.error('socket: urc on nil socket', data, id, tSocket[id], socketsSsl[id])
  81. return
  82. end
  83. if result == "CONNECT" or result:match("CONNECT ERROR") or result:match("CONNECT FAIL") then
  84. if tSocket[id].wait == "+CIPSTART" or tSocket[id].wait == "+SSLCONNECT" then
  85. stopConnectTimer(tSocket, id)
  86. coroutine.resume(tSocket[id].co, result == "CONNECT")
  87. else
  88. log.error("socket: error urc", tSocket[id].wait)
  89. end
  90. return
  91. end
  92. if tag == "SSL" and string.find(result, "ERROR:") == 1 then
  93. return
  94. end
  95. if string.find(result, "ERROR") or result == "CLOSED" then
  96. if result == 'CLOSED' and not tSocket[id].ssl then
  97. tSocket[id].connected = false
  98. socketStatusNtfy()
  99. end
  100. tSocket[id].error = result
  101. stopConnectTimer(tSocket, id)
  102. coroutine.resume(tSocket[id].co, false)
  103. end
  104. end
  105. -- 创建socket函数
  106. local mt = {}
  107. mt.__index = mt
  108. local function socket(protocol, cert)
  109. local ssl = nil-- protocol:match("SSL")
  110. local id = table.remove(ssl and validSsl or valid)
  111. if not id then
  112. log.warn("socket.socket: too many sockets")
  113. return nil
  114. end
  115. local co = coroutine.running()
  116. if not co then
  117. log.warn("socket.socket: socket must be called in coroutine")
  118. return nil
  119. end
  120. -- 实例的属性参数表
  121. local o = {
  122. id = id,
  123. protocol = protocol,
  124. ssl = ssl,
  125. cert = cert,
  126. co = co,
  127. input = {},
  128. output = {},
  129. wait = "",
  130. connected = false,
  131. iSubscribe = false,
  132. subMessage = nil
  133. }
  134. tSocket = (ssl and socketsSsl or sockets)
  135. tSocket[id] = o
  136. if cert then
  137. local tmpPath, result, caConf, certConf, keyConf
  138. if cert.caCert then
  139. result = true
  140. tmpPath = (cert.caCert:sub(1, 1) == "/") and cert.caCert or ("/lua/" .. cert.caCert)
  141. if not io.exists("/at_ca.bin") then
  142. result = false
  143. else
  144. if crypto.md5("/at_ca.bin", "file") ~= crypto.md5(tmpPath, "file") then
  145. result = false
  146. end
  147. end
  148. if not result then
  149. io.writeFile("/at_ca.bin", io.readFile(tmpPath))
  150. req("AT+SYSFLASH=0,\"" .. "client_ca" .. "\",0,8192", nil, nil, nil, {
  151. id = id,
  152. path32 = "client_ca",
  153. path8955 = tmpPath
  154. })
  155. coroutine.yield()
  156. end
  157. caConf = true
  158. end
  159. if cert.clientCert then
  160. result = true
  161. tmpPath = (cert.clientCert:sub(1, 1) == "/") and cert.clientCert or ("/lua/" .. cert.clientCert)
  162. if not io.exists("/at_cert.bin") then
  163. result = false
  164. else
  165. if crypto.md5("/at_cert.bin", "file") ~= crypto.md5(tmpPath, "file") then
  166. result = false
  167. end
  168. end
  169. if not result then
  170. io.writeFile("/at_cert.bin", io.readFile(tmpPath))
  171. req("AT+SYSFLASH=0,\"" .. "client_cert" .. "\",0,8192", nil, nil, nil, {
  172. id = id,
  173. path32 = "client_cert",
  174. path8955 = tmpPath
  175. })
  176. coroutine.yield()
  177. end
  178. certConf = true
  179. end
  180. if cert.clientKey then
  181. result = true
  182. tmpPath = (cert.clientKey:sub(1, 1) == "/") and cert.clientKey or ("/lua/" .. cert.clientKey)
  183. if not io.exists("/at_key.bin") then
  184. result = false
  185. else
  186. if crypto.md5("/at_key.bin", "file") ~= crypto.md5(tmpPath, "file") then
  187. result = false
  188. end
  189. end
  190. if not result then
  191. io.writeFile("/at_key.bin", io.readFile(tmpPath))
  192. req("AT+SYSFLASH=0,\"" .. "client_key" .. "\",0,8192", nil, nil, nil, {
  193. id = id,
  194. path32 = "client_key",
  195. path8955 = tmpPath
  196. })
  197. coroutine.yield()
  198. end
  199. keyConf = true
  200. end
  201. if caConf and not certConf and not keyConf then
  202. req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 2, 0, 0))
  203. elseif not caConf and certConf and not keyConf then
  204. req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 1, 0, 0))
  205. elseif caConf and certConf and keyConf then
  206. req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 3, 0, 0))
  207. end
  208. else
  209. req(string.format("AT+CIPSSLCCONF=%d,%d", id, 0))
  210. end
  211. return setmetatable(o, mt)
  212. end
  213. --- 创建基于TCP的socket对象
  214. -- @bool[opt=nil] ssl 是否为ssl连接,true表示是,其余表示否
  215. -- @table[opt=nil] cert ssl连接需要的证书配置,只有ssl参数为true时,才参数才有意义,cert格式如下:
  216. -- {
  217. -- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
  218. -- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
  219. -- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
  220. -- clientPassword = "123456", --客户端证书文件密码[可选]
  221. -- }
  222. -- @return client,创建成功返回socket客户端对象;创建失败返回nil
  223. -- @usage
  224. -- c = socketESP8266.tcp()
  225. -- c = socketESP8266.tcp(true)
  226. -- c = socketESP8266.tcp(true, {caCert="ca.crt"})
  227. -- c = socketESP8266.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
  228. -- c = socketESP8266.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
  229. function tcp(ssl, cert)
  230. return socket((ssl == true and "SSL" or "TCP"), (ssl == true) and cert or nil)
  231. end
  232. --- 创建基于UDP的socket对象
  233. -- @return client,创建成功返回socket客户端对象;创建失败返回nil
  234. -- @usage c = socketESP8266.udp()
  235. function udp()
  236. return socket("UDP")
  237. end
  238. local sslInited
  239. local tSslInputCert, sSslInputCert = {}, ""
  240. local function sslInit()
  241. if not sslInited then
  242. sslInited = true
  243. req("AT+SSLINIT")
  244. end
  245. local i, item
  246. for i = 1, #tSslInputCert do
  247. item = table.remove(tSslInputCert, 1)
  248. req(item.cmd, item.arg)
  249. end
  250. tSslInputCert = {}
  251. end
  252. local function sslTerm()
  253. if sslInited then
  254. if not isSocketActive(true) then
  255. sSslInputCert, sslInited = ""
  256. req("AT+SSLTERM")
  257. end
  258. end
  259. end
  260. local function sslInputCert(t, f)
  261. if sSslInputCert:match(t .. f .. "&") then
  262. return
  263. end
  264. if not tSslInputCert then
  265. tSslInputCert = {}
  266. end
  267. local s = io.readFile((f:sub(1, 1) == "/") and f or ("/lua/" .. f))
  268. if not s then
  269. log.error("inputcrt err open", path)
  270. return
  271. end
  272. -- table.insert(tSslInputCert, {cmd = "AT+SSLCERT=0,\"" .. t .. "\",\"" .. f .. "\",1," .. s:len(), arg = s or ""})
  273. table.insert(tSslInputCert, {
  274. cmd = "AT+SYSFLASH=0,\"" .. t .. "\",\"" .. f .. "\",1," .. s:len(),
  275. arg = s or ""
  276. })
  277. sSslInputCert = sSslInputCert .. t .. f .. "&"
  278. end
  279. local path32, path8955
  280. --- 连接服务器
  281. -- @string address 服务器地址,支持ip和域名
  282. -- @param port string或者number类型,服务器端口
  283. -- @return bool result true - 成功,false - 失败
  284. -- @number timeout, 链接服务器最长超时时间
  285. -- @usage c = socketESP8266.tcp(); c:connect("www.baidu.com",80,5);
  286. function mt:connect(address, port, timeout)
  287. assert(self.co == coroutine.running(), "socket:connect: coroutine mismatch")
  288. if not link.isReady() then
  289. log.info("socket.connect: ip not ready")
  290. return false
  291. end
  292. if cc and cc.anyCallExist() then
  293. log.info("socket:connect: call exist, cannot connect")
  294. return false
  295. end
  296. self.address = address
  297. self.port = port
  298. if self.cert then
  299. local tConfigCert, i = {}
  300. -- if self.cert then
  301. -- if self.cert.caCert then
  302. -- sslInputCert("cacrt", self.cert.caCert)
  303. -- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"cacrt\",\"" .. self.cert.caCert .. "\"")
  304. -- end
  305. -- if self.cert.clientCert then
  306. -- sslInputCert("localcrt", self.cert.clientCert)
  307. -- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"localcrt\",\"" .. self.cert.clientCert .. "\",\"" .. (self.cert.clientPassword or "") .. "\"")
  308. -- end
  309. -- if self.cert.clientKey then
  310. -- sslInputCert("localprivatekey", self.cert.clientKey)
  311. -- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"localprivatekey\",\"" .. self.cert.clientKey .. "\"")
  312. -- end
  313. -- end
  314. -- sslInit()
  315. -- req(string.format("AT+SSLCREATE=%d,\"%s\",%d", self.id, address .. ":" .. port, (self.cert and self.cert.caCert) and 0 or 1))
  316. -- self.created = true
  317. -- for i = 1, #tConfigCert do
  318. -- req(tConfigCert[i])
  319. -- end
  320. -- req("AT+SSLCONNECT=" .. self.id)
  321. --
  322. req(string.format("AT+CIPSTART=%d,\"%s\",\"%s\",%s", self.id, "SSL", address, port))
  323. else
  324. req(string.format("AT+CIPSTART=%d,\"%s\",\"%s\",%s", self.id, self.protocol, address, port))
  325. end
  326. -- if self.ssl or self.protocol == "UDP" then sys.timerStart(coroutine.resume, 120000, self.co, false, "TIMEOUT") end
  327. sys.timerStart(coroutine.resume, (timeout or 120) * 1000, self.co, false, "TIMEOUT")
  328. wifiRil.regUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
  329. self.wait = self.ssl and "+SSLCONNECT" or "+CIPSTART"
  330. local r, s = coroutine.yield()
  331. if r == false and s == "DNS" then
  332. if self.ssl then
  333. self:sslDestroy()
  334. self.error = nil
  335. end
  336. require "http"
  337. -- 请求腾讯云免费HttpDns解析
  338. http.request("GET", "119.29.29.29/d?dn=" .. address, nil, nil, nil, 40000, function(result, statusCode, head, body)
  339. log.info("socket.httpDnsCb", result, statusCode, head, body)
  340. sys.publish("SOCKET_HTTPDNS_RESULT_" .. address .. "_" .. port, result, statusCode, head, body)
  341. end)
  342. local _, result, statusCode, head, body = sys.waitUntil("SOCKET_HTTPDNS_RESULT_" .. address .. "_" .. port)
  343. -- DNS解析成功
  344. if result and statusCode == "200" and body and body:match("^[%d%.]+") then
  345. return self:connect(body:match("^([%d%.]+)"), port)
  346. -- DNS解析失败
  347. else
  348. if dnsParser then
  349. dnsParserToken = dnsParserToken + 1
  350. dnsParser(address, dnsParserToken)
  351. local result, ip = sys.waitUntil("USER_DNS_PARSE_RESULT_" .. dnsParserToken, 40000)
  352. if result and ip and ip:match("^[%d%.]+") then
  353. return self:connect(ip:match("^[%d%.]+"), port)
  354. end
  355. end
  356. end
  357. end
  358. if r == false then
  359. if self.ssl then
  360. self:sslDestroy()
  361. end
  362. sys.publish("LIB_SOCKET_CONNECT_FAIL_IND", self.ssl, self.protocol, address, port)
  363. return false
  364. end
  365. self.connected = true
  366. socketStatusNtfy()
  367. return true
  368. end
  369. --- 异步收发选择器
  370. -- @number keepAlive 服务器和客户端最大通信间隔时间,也叫心跳包最大时间,单位秒
  371. -- @string pingreq 心跳包的字符串
  372. -- @return boole,false 失败,true 表示成功
  373. function mt:asyncSelect(keepAlive, pingreq)
  374. assert(self.co == coroutine.running(), "socket:asyncSelect: coroutine mismatch")
  375. if self.error then
  376. log.warn('socket.client:asyncSelect', 'error', self.error)
  377. return false
  378. end
  379. self.wait = "SOCKET_SEND"
  380. while #self.output ~= 0 do
  381. local data = table.concat(self.output)
  382. self.output = {}
  383. for i = 1, string.len(data), SENDSIZE do
  384. -- 按最大MTU单元对data分包
  385. local stepData = string.sub(data, i, i + SENDSIZE - 1)
  386. -- 发送AT命令执行数据发送
  387. req(string.format("AT+" .. (self.ssl and "SSL" or "CIP") .. "SEND=%d,%d", self.id, string.len(stepData)), stepData)
  388. self.wait = self.ssl and "+SSLSEND" or "+CIPSEND"
  389. if not coroutine.yield() then
  390. if self.ssl then
  391. self:sslDestroy()
  392. end
  393. sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
  394. return false
  395. end
  396. end
  397. end
  398. self.wait = "SOCKET_WAIT"
  399. sys.publish("SOCKET_SEND", self.id)
  400. if keepAlive and keepAlive ~= 0 then
  401. if type(pingreq) == "function" then
  402. sys.timerStart(pingreq, keepAlive * 1000)
  403. else
  404. sys.timerStart(self.asyncSend, keepAlive * 1000, self, pingreq or "\0")
  405. end
  406. end
  407. return coroutine.yield()
  408. end
  409. --- 异步发送数据
  410. -- @string data 数据
  411. -- @return result true - 成功,false - 失败
  412. -- @usage c = socketESP8266.tcp(); c:connect(); c:asyncSend("12345678");
  413. function mt:asyncSend(data)
  414. if self.error then
  415. log.warn('socket.client:asyncSend', 'error', self.error)
  416. return false
  417. end
  418. table.insert(self.output, data or "")
  419. if self.wait == "SOCKET_WAIT" then
  420. coroutine.resume(self.co, true)
  421. end
  422. return true
  423. end
  424. --- 异步接收数据
  425. -- @return nil, 表示没有收到数据
  426. -- @return data 如果是UDP协议,返回新的数据包,如果是TCP,返回所有收到的数据,没有数据返回长度为0的空串
  427. -- @usage c = socketESP8266.tcp(); c:connect()
  428. -- @usage data = c:asyncRecv()
  429. function mt:asyncRecv()
  430. if #self.input == 0 then
  431. return ""
  432. end
  433. if self.protocol == "UDP" then
  434. return table.remove(self.input)
  435. else
  436. local s = table.concat(self.input)
  437. self.input = {}
  438. return s
  439. end
  440. end
  441. --- 发送数据
  442. -- @string data 数据
  443. -- @return result true - 成功,false - 失败
  444. -- @usage c = socketESP8266.tcp(); c:connect(); c:send("12345678");
  445. function mt:send(data)
  446. assert(self.co == coroutine.running(), "socket:send: coroutine mismatch")
  447. if self.error then
  448. log.warn('socket.client:send', 'error', self.error)
  449. return false
  450. end
  451. if self.id == nil then
  452. log.warn('socket.client:send', 'closed')
  453. return false
  454. end
  455. for i = 1, string.len(data or ""), SENDSIZE do
  456. -- 按最大MTU单元对data分包
  457. local stepData = string.sub(data, i, i + SENDSIZE - 1)
  458. -- 发送AT命令执行数据发送
  459. req(string.format("AT+" .. (self.ssl and "SSL" or "CIP") .. "SEND=%d,%d", self.id, string.len(stepData)), stepData)
  460. self.wait = self.ssl and "+SSLSEND" or "+CIPSEND"
  461. if not coroutine.yield() then
  462. if self.ssl then
  463. self:sslDestroy()
  464. end
  465. sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
  466. return false
  467. end
  468. end
  469. return true
  470. end
  471. --- 接收数据
  472. -- @number[opt=0] timeout 可选参数,接收超时时间,单位毫秒
  473. -- @string[opt=nil] msg 可选参数,控制socket所在的线程退出recv阻塞状态
  474. -- @bool[opt=nil] msgNoResume 可选参数,控制socket所在的线程退出recv阻塞状态,false或者nil表示“在recv阻塞状态,收到msg消息,可以退出阻塞状态”,true表示不退出
  475. -- @return result 数据接收结果,true表示成功,false表示失败
  476. -- @return data 如果成功的话,返回接收到的数据;超时时返回错误为"timeout";msg控制退出时返回msg的字符串
  477. -- @return param 如果是msg返回的false,则data的值是msg,param的值是msg的参数
  478. -- @usage c = socketESP8266.tcp(); c:connect()
  479. -- @usage result, data = c:recv()
  480. -- @usage false,msg,param = c:recv(60000,"publish_msg")
  481. function mt:recv(timeout, msg, msgNoResume)
  482. assert(self.co == coroutine.running(), "socket:recv: coroutine mismatch")
  483. if self.error then
  484. log.warn('socket.client:recv', 'error', self.error)
  485. return false
  486. end
  487. self.msgNoResume = msgNoResume
  488. if msg and not self.iSubscribe then
  489. self.iSubscribe = msg
  490. self.subMessage = function(data)
  491. -- if data then table.insert(self.output, data) end
  492. if (self.wait == "+RECEIVE" or self.wait == "+SSL RECEIVE") and not self.msgNoResume then
  493. if data then
  494. table.insert(self.output, data)
  495. end
  496. coroutine.resume(self.co, 0xAA)
  497. end
  498. end
  499. sys.subscribe(msg, self.subMessage)
  500. end
  501. if msg and #self.output ~= 0 then
  502. sys.publish(msg, false)
  503. end
  504. if #self.input == 0 then
  505. self.wait = self.ssl and "+SSL RECEIVE" or "+RECEIVE"
  506. if timeout and timeout > 0 then
  507. local r, s = sys.wait(timeout)
  508. -- if not r then
  509. -- return false, "timeout"
  510. -- elseif r and r == msg then
  511. -- return false, r, s
  512. -- else
  513. -- if self.ssl and not r then self:sslDestroy() end
  514. -- return r, s
  515. -- end
  516. if r == nil then
  517. return false, "timeout"
  518. elseif r == 0xAA then
  519. local dat = table.concat(self.output)
  520. self.output = {}
  521. return false, msg, dat
  522. else
  523. if self.ssl and not r then
  524. self:sslDestroy()
  525. end
  526. return r, s
  527. end
  528. else
  529. local r, s = coroutine.yield()
  530. if r == 0xAA then
  531. local dat = table.concat(self.output)
  532. self.output = {}
  533. return false, msg, dat
  534. else
  535. return r, s
  536. end
  537. end
  538. end
  539. if self.protocol == "UDP" then
  540. return true, table.remove(self.input)
  541. else
  542. local s = table.concat(self.input)
  543. self.input = {}
  544. return true, s
  545. end
  546. end
  547. function mt:sslDestroy()
  548. assert(self.co == coroutine.running(), "socket:sslDestroy: coroutine mismatch")
  549. if self.ssl and (self.connected or self.created) then
  550. self.connected = false
  551. self.created = false
  552. req("AT+SSLDESTROY=" .. self.id)
  553. self.wait = "+SSLDESTROY"
  554. coroutine.yield()
  555. socketStatusNtfy()
  556. end
  557. end
  558. --- 销毁一个socket
  559. -- @return nil
  560. -- @usage c = socket.tcp(); c:connect(); c:send("123"); c:close()
  561. function mt:close(slow)
  562. assert(self.co == coroutine.running(), "socket:close: coroutine mismatch")
  563. if self.iSubscribe then
  564. sys.unsubscribe(self.iSubscribe, self.subMessage)
  565. self.iSubscribe = false
  566. end
  567. if self.connected or self.created then
  568. self.connected = false
  569. self.created = false
  570. req(self.ssl and ("AT+SSLDESTROY=" .. self.id) or ("AT+CIPCLOSE=" .. self.id .. (slow and ",0" or "")))
  571. self.wait = self.ssl and "+SSLDESTROY" or "+CIPCLOSE"
  572. coroutine.yield()
  573. socketStatusNtfy()
  574. end
  575. if self.id ~= nil then
  576. wifiRil.deRegUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
  577. table.insert((self.ssl and validSsl or valid), 1, self.id)
  578. if self.ssl then
  579. socketsSsl[self.id] = nil
  580. else
  581. sockets[self.id] = nil
  582. end
  583. self.id = nil
  584. end
  585. end
  586. local function onResponse(cmd, success, response, intermediate)
  587. local prefix = string.match(cmd, "AT(%+%u+)")
  588. local id = string.match(cmd, "AT%+%u+=(%d)")
  589. if response == '+PDP: DEACT' then
  590. sys.publish('PDP_DEACT_IND')
  591. end -- cipsend 如果正好pdp deact会返回+PDP: DEACT作为回应
  592. local tSocket = prefix:match("SSL") and socketsSsl or sockets
  593. if not tSocket[id] then
  594. log.warn('socket: response on nil socket', cmd, response)
  595. return
  596. end
  597. log.info("cmd",cmd, success, response, intermediate)
  598. if cmd:match("^AT%+SSLCREATE") then
  599. tSocket[id].createResp = response
  600. end
  601. if tSocket[id].wait == prefix then
  602. if (prefix == "+CIPSTART" or prefix == "+SSLCONNECT") and success then
  603. -- CIPSTART,SSLCONNECT 返回OK只是表示被接受
  604. return
  605. end
  606. if prefix == '+CIPSEND' then
  607. if response == 'OK' then
  608. -- 返回OK只是表示被接受
  609. return
  610. end
  611. if response ~= 'SEND OK' and response ~= 'OK' then
  612. local acceptLen = response:match("Recv (%d+) bytes")
  613. log.info("testlen", acceptLen, cmd:match("AT%+%u+=%d,(%d+)"))
  614. if acceptLen then
  615. if acceptLen ~= cmd:match("AT%+%u+=%d,(%d+)") then
  616. success = false
  617. else
  618. return
  619. end
  620. else
  621. success = false
  622. end
  623. end
  624. elseif prefix == "+SSLSEND" then
  625. if response:match("%d, *([%u%d :]+)") ~= 'SEND OK' then
  626. success = false
  627. end
  628. end
  629. local reason, address
  630. if not success then
  631. if prefix == "+CIPSTART" then
  632. address = cmd:match("AT%+CIPSTART=%d,\"%a+\",\"(.+)\",%d+")
  633. elseif prefix == "+SSLCONNECT" and (tSocket[id].createResp or ""):match("SSL&%d+,CREATE ERROR: 4") then
  634. address = tSocket[id].address or ""
  635. end
  636. if address and not address:match("^[%d%.]+$") then
  637. reason = "DNS"
  638. end
  639. end
  640. if not reason and not success then
  641. tSocket[id].error = response
  642. end
  643. stopConnectTimer(tSocket, id)
  644. coroutine.resume(tSocket[id].co, success, reason)
  645. end
  646. end
  647. local function onSocketReceiveUrc(urc, prefix, cmd)
  648. local len, datatest = string.match(urc, "+CIPRECVDATA:(%d+),(.+)")
  649. local id = string.match(cmd, "+CIPRECVDATA=(%d),")
  650. tSocket = (tag == "SSL" and socketsSsl or sockets)
  651. len = tonumber(len)
  652. if len == 0 then
  653. return urc
  654. end
  655. if string.len(datatest) == len then
  656. sys.publish("SOCKET_RECV", id)
  657. if tSocket[id].wait == "+RECEIVE" then
  658. coroutine.resume(tSocket[id].co, true, datatest)
  659. else -- 数据进缓冲区,缓冲区溢出采用覆盖模式
  660. if #tSocket[id].input > INDEX_MAX then
  661. tSocket[id].input = {}
  662. end
  663. table.insert(tSocket[id].input, datatest)
  664. end
  665. elseif string.len(datatest) < len then
  666. local cache = {}
  667. table.insert(cache, datatest)
  668. len = len - string.len(datatest)
  669. local function filter(data)
  670. -- 剩余未收到的数据长度
  671. if string.len(data) >= len then -- at通道的内容比剩余未收到的数据多
  672. -- 截取网络发来的数据
  673. table.insert(cache, string.sub(data, 1, len))
  674. -- 剩下的数据扔给at进行后续处理
  675. data = string.sub(data, len + 1, -1)
  676. if not tSocket[id] then
  677. log.warn('socket: receive on nil socket', id)
  678. else
  679. sys.publish("SOCKET_RECV", id)
  680. local s = table.concat(cache)
  681. if tSocket[id].wait == "+RECEIVE" or tSocket[id].wait == "+SSL RECEIVE" then
  682. coroutine.resume(tSocket[id].co, true, s)
  683. else -- 数据进缓冲区,缓冲区溢出采用覆盖模式
  684. if #tSocket[id].input > INDEX_MAX then
  685. tSocket[id].input = {}
  686. end
  687. table.insert(tSocket[id].input, s)
  688. end
  689. end
  690. return data
  691. else
  692. table.insert(cache, data)
  693. len = len - string.len(data)
  694. return "", filter
  695. end
  696. end
  697. return filter
  698. end
  699. end
  700. wifiRil.regUrc("+CIPCLOSE", onResponse)
  701. wifiRil.regRsp("+CIPSEND", onResponse)
  702. wifiRil.regRsp("+CIPSTART", onResponse)
  703. wifiRil.regRsp("+SSLDESTROY", onResponse)
  704. wifiRil.regRsp("+SSLCREATE", onResponse)
  705. wifiRil.regRsp("+SSLSEND", onResponse)
  706. wifiRil.regRsp("+SSLCONNECT", onResponse)
  707. wifiRil.regUrc("+CIPRECVDATA", onSocketReceiveUrc)
  708. wifiRil.regUrc("+SSL RECEIVE", onSocketReceiveUrc)
  709. wifiRil.regRsp("+SYSFLASH", function(cmd, result, response, intermediate, param)
  710. if cmd:find("AT%+SYSFLASH=0") then
  711. req("AT+SYSFLASH=1,\"" .. param.path32 .. "\",0," .. io.fileSize(param.path8955), io.readFile(param.path8955), nil, nil, param.id)
  712. elseif cmd:find("AT%+SYSFLASH=1") then
  713. local tSocket = sockets
  714. coroutine.resume(tSocket[param].co)
  715. end
  716. end)
  717. local recvId
  718. getRecvId = function() return recvId end
  719. local errorNum = 0
  720. local function wifiUrc(data, prefix)
  721. log.info("urc上报", data, prefix)
  722. if prefix == "STATUS" then
  723. if not connecting or not connected then
  724. local state = data:sub(8, -1)
  725. if state == "0" or state == "1" or state == "5" then
  726. errorNum = errorNum + 1
  727. if errorNum > 5 then
  728. wifiRil.request("AT+CWMODE=1")
  729. -- wifiRil.request("AT+CWSTARTSMART=3")
  730. wifiRil.request("AT+CWJAP=\"Luat_XIAOMIPRO\",\"Air123456\"")
  731. else
  732. wifiRil.request("AT+CIPSTATUS", nil, nil, 5000)
  733. end
  734. elseif state == "2" then
  735. errorNum = 0
  736. connected = true
  737. sys.publish("IP_READY_IND")
  738. return
  739. end
  740. end
  741. elseif prefix == "WIFI GOT IP" then
  742. connecting = false
  743. connected = true
  744. link.setReady(link.ESP8266, true)
  745. sys.publish("IP_READY_IND")
  746. return
  747. elseif prefix == "WIFI CONNECTED" then
  748. connecting = true
  749. return
  750. elseif prefix == "Smart get wifi info" then
  751. stopConfig = sys.timerStart(wifiRil.request, 20000, "AT+CWSTOPSMART")
  752. return
  753. elseif prefix == "smartconfig connected wifi" then
  754. connecting = true
  755. if sys.timerIsActive(stopConfig) then sys.timerStop(stopConfig) end
  756. wifiRil.request("AT+CWSTOPSMART", nil, nil, 6000)
  757. elseif prefix == "+IPD" then
  758. log.info("rcv data", data)
  759. local lid, dataLen = string.match(data, "%+IPD,(%d),(%d+)")
  760. recvId = lid
  761. wifiRil.request(string.format("AT+CIPRECVDATA=%d,%d", lid, dataLen))
  762. elseif prefix == "+CIPRECVLEN" then
  763. log.info("rcv test", prefix, data)
  764. local lid = {string.match(data, "%+CIPRECVLEN:(.+),(.+),(.+),(.+),(.+)")}
  765. for k, v in pairs(lid) do
  766. if v ~= "-1" and v ~= "0" then
  767. wifiRil.request(string.format("AT+CIPRECVDATA=%d,%d", k - 1, 2147483647))
  768. end
  769. end
  770. end
  771. end
  772. local function wifiRsp(cmd, success, response, intermediate)
  773. log.info("wifi", cmd, success, response, intermediate)
  774. if cmd == "AT+CWSTARTSMART=3" then
  775. if success then smartConfig = true end
  776. elseif cmd == "AT+CWSTOPSMART" then
  777. if connecting then
  778. connecting = false
  779. connected = true
  780. sys.publish("IP_READY_IND")
  781. else
  782. sys.timerStart(wifiRil.request, 2000, "AT+CWSTARTSMART=3")
  783. end
  784. end
  785. end
  786. wifiRil.regUrc("+IPD", wifiUrc)
  787. wifiRil.regUrc("STATUS", wifiUrc)
  788. wifiRil.regUrc("WIFI GOT IP", wifiUrc)
  789. wifiRil.regUrc("WIFI CONNECTED", wifiUrc)
  790. wifiRil.regUrc("smartconfig connected wifi", wifiUrc)
  791. wifiRil.regUrc("Smart get wifi info", wifiUrc)
  792. wifiRil.regRsp("+CWSTARTSMART", wifiRsp)
  793. wifiRil.regRsp("+CWSTOPSMART", wifiRsp)
  794. wifiRil.regUrc("+CIPRECVLEN", wifiUrc)
  795. wifiRil.request("AT+CWMODE=1")
  796. wifiRil.request("AT+CIPRECVMODE=1")
  797. wifiRil.request("AT+CIPMODE=0")
  798. wifiRil.request("AT+CIPMUX=1")
  799. wifiRil.request("AT+CIPSTATUS")
  800. function printStatus()
  801. log.info('socket.printStatus', 'valid id', table.concat(valid), table.concat(validSsl))
  802. for m, n in pairs({sockets, socketsSsl}) do
  803. for _, client in pairs(n) do
  804. for k, v in pairs(client) do
  805. log.info('socket.printStatus', 'client', client.id, k, v)
  806. end
  807. end
  808. end
  809. end
  810. -- 设置TCP层自动重传的参数
  811. -- @number[opt=4] retryCnt 重传次数;取值范围0到12
  812. -- @number[opt=16] retryMaxTimeout 限制每次重传允许的最大超时时间(单位秒),取值范围1到16
  813. -- @return nil
  814. -- @usage
  815. -- setTcpResendPara(3,8)
  816. -- setTcpResendPara(4,16)
  817. function setTcpResendPara(retryCnt, retryMaxTimeout)
  818. req("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
  819. wifiRil.setDataTimeout(((retryCnt or 4) * (retryMaxTimeout or 16) + 60) * 1000)
  820. end
  821. function setIpStatis(interval)
  822. end
  823. -- 设置用户自定义的DNS解析器.
  824. -- 通过域名连接服务器时,DNS解析的过程如下:
  825. -- 1、使用core中提供的方式,连接运营商DNS服务器解析,如果解析成功,则结束;如果解析失败,走第2步
  826. -- 2、使用脚本lib中提供的免费腾讯云HttpDns解析,如果解析成功,则结束;如果解析失败,走第3步
  827. -- 3、如果存在用户自定义的DNS解析器,则使用此处用户自定义的DNS解析器去解析
  828. -- @function[opt=nil] parserFnc 用户自定义的DNS解析器函数,函数的调用形式为:
  829. -- parserFnc(domainName,token),调用接口后会等待解析结果的消息通知或者40秒超时失败
  830. -- domainName:string类型,表示域名,例如"www.baidu.com"
  831. -- token:string类型,此次DNS解析请求的token,例如"1"
  832. -- 解析结束后,要publish一个消息来通知解析结果,消息参数中的ip地址最多返回一个,sys.publish("USER_DNS_PARSE_RESULT_"..token,ip),例如:
  833. -- sys.publish("USER_DNS_PARSE_RESULT_1","115.239.211.112")
  834. -- 表示解析成功,解析到1个IP地址115.239.211.112
  835. -- sys.publish("USER_DNS_PARSE_RESULT_1")
  836. -- 表示解析失败
  837. -- @return nil
  838. -- @usage socket.setDnsParser(parserFnc)
  839. function setDnsParser(parserFnc)
  840. dnsParser = parserFnc
  841. end
  842. -- 设置数据发送模式(在网络准备就绪之前调用此接口设置).
  843. -- 如果设置为快发模式,注意如下两点:
  844. -- 1、通过send接口发送的数据,如果成功发送到服务器,设备端无法获取到这个成功状态
  845. -- 2、通过send接口发送的数据,如果发送失败,设备端可以获取到这个失败状态
  846. -- 慢发模式可以获取到send接口发送的成功或者失败
  847. --
  848. -- ****************************************************************************************************************************************************************
  849. -- TCP协议发送数据时,数据发送出去之后,必须等到服务器返回TCP ACK包,才认为数据发送成功,在网络较差的情况下,这种ACK确认就会导致发送过程很慢。
  850. -- 从而导致用户程序后续的AT处理逻辑一直处于等待状态。例如执行AT+CIPSEND动作发送一包数据后,接下来要执行AT+QTTS播放TTS,但是CIPSEND一直等了1分钟才返回SEND OK,
  851. -- 这时AT+QTTS就会一直等待1分钟,可能不是程序中想看到的。
  852. -- 此时就可以设置为快发模式,AT+CIPSEND可以立即返回一个结果,此结果表示“数据是否被缓冲区所保存”,从而不影响后续其他AT指令的及时执行
  853. --
  854. -- AT版本可以通过AT+CIPQSEND指令、Luat版本可以通过socket.setSendMode接口设置发送模式为快发或者慢发
  855. --
  856. -- 快发模式下,在core中有一个1460*7=10220字节的缓冲区,要发送的数据首先存储到此缓冲区,然后在core中自动循环发送。
  857. -- 如果此缓冲区已满,则AT+CIPSEND会直接返回ERROR,socket:send接口也会直接返回失败
  858. --
  859. -- 同时满足如下几种条件,适合使用快发模式:
  860. -- 1. 发送的数据量小,并且发送频率低,数据发送速度远远不会超过core中的10220字节大小;
  861. -- 没有精确地判断标准,可以简单的按照3分钟不超过10220字节来判断;曾经有一个不适合快发模式的例子如下:
  862. -- 用户使用Luat版本的http上传一个几十K的文件,设置了快发模式,导致一直发送失败,因为循环的向core中的缓冲区插入数据,
  863. -- 插入数据的速度远远超过发送数据到服务器的速度,所以很快就导致缓冲区慢,再插入数据时,就直接返回失败
  864. -- 2. 对每次发送的数据,不需要确认发送结果
  865. -- 3. 数据发送功能不能影响其他功能的及时响应
  866. -- ****************************************************************************************************************************************************************
  867. --
  868. -- @number[opt=0] mode 数据发送模式,0表示慢发,1表示快发
  869. -- @return nil
  870. -- @usage socket.setSendMode(1)
  871. -- function setSendMode(mode)
  872. -- linkTest.setSendMode(mode)
  873. -- end
  874. function open(para)
  875. powerCbFnc = para["powerFunc"] or nil
  876. if powerCbFnc then
  877. powerCbFnc(true)
  878. end
  879. end
  880. function close()
  881. if powerCbFnc then
  882. powerCbFnc(false)
  883. end
  884. end