rtkv.lua 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. --[[
  2. @module rtkv
  3. @summary 远程KV数据库
  4. @version 1.0
  5. @date 2023.07.17
  6. @author wendal
  7. @tag LUAT_USE_NETWORK
  8. @usage
  9. -- 是否还在为上报几个数据值而烦恼?
  10. -- 是否还在为数据存入数据库而头痛不已?
  11. -- 没有外网服务器, 内网穿透又很麻烦?
  12. -- 不懂mqtt, 也没有下发需求, 只是想上报一些值?
  13. -- 那本API就很适合您
  14. -- 它可以:
  15. -- 将数据存到服务器,例如温湿度,GPS坐标,GPIO状态
  16. -- 读取服务器的数据,例如OTA信息
  17. -- 服务器会保存历史记录,也支持绘制成图表
  18. -- 它不可以:
  19. -- 实时下发数据给设备
  20. -- 上传巨量数据
  21. -- 网站首页, 输入设备识别号就能看数据 https://rtkv.air32.cn
  22. -- 示例设备 http://rtkv.air32.cn/d/6055F9779010
  23. -- 场景举例1, 上报温湿度数据到服务器, 然后网站查看地址是 XXX
  24. rtkv.setup()
  25. sys.taskInit(function()
  26. sys.waitUntil("IP_READY")
  27. while 1 do
  28. local val,result = sensor.ds18b20(17, true)
  29. if result then
  30. rtkv.set("ds18b20_temp", val)
  31. end
  32. sys.wait(60*1000) -- 一分钟上报一次
  33. end
  34. end)
  35. -- 场景举例2, 简易版OTA
  36. rtkv.setup()
  37. sys.taskInit(function()
  38. sys.waitUntil("IP_READY")
  39. sys.wait(1000)
  40. while 1 do
  41. local ota_version = rtkv.get("ota_version")
  42. if ota_version and ota_version ~= _G.VERSION then
  43. local ota_url = rtkv.get("ota_url")
  44. if ota_url then
  45. -- 执行OTA, 以esp32c3为例
  46. local code = http.request("GET", ota_url, nil, nil, {dst="/update.bin"}).wait()
  47. if code and code == 200 then
  48. log.info("ota", "ota包下载完成, 5秒后重启")
  49. sys.wait(5000)
  50. rtos.reboot()
  51. end
  52. end
  53. end
  54. sys.wait(4*3600*1000) -- 4小时检查一次
  55. end
  56. end)
  57. -- 场景举例3, 非实时下发控制
  58. rtkv.setup()
  59. sys.taskInit(function()
  60. local LED = gpio.setup(27, 0, nil, gpio.PULLUP)
  61. local INPUT = gpio.setup(22, nil)
  62. sys.waitUntil("IP_READY")
  63. sys.wait(1000)
  64. while 1 do
  65. local gpio27 = rtkv.get("gpio27")
  66. if gpio27 then
  67. LED(gpio27 == "1" and 1 or 0)
  68. end
  69. rtkv.set("gpio22", INPUT()) -- 上报GPIO22的状态
  70. sys.wait(15*1000) -- 15秒查询一次
  71. end
  72. end)
  73. ]]
  74. local rtkv = {}
  75. --[[
  76. rtkv初始化
  77. @api rtkv.setup(conf)
  78. @table 配置信息,详细说明看下面的示例
  79. @return nil 没有返回值
  80. @usage
  81. -- 本函数只需要调用一次, 通常在main.lua里
  82. -- 默认初始化, 开启了调试日志
  83. rtkv.setup()
  84. -- 初始化,并关闭调试日志
  85. rtkv.setup({nodebug=true})
  86. -- 详细初始化, 可以只填需要配置的项
  87. rtkv.setup({
  88. apiurl = "http://rtkv.air32.cn", -- 服务器地址,可以自行部署 https://gitee.com/openLuat/luatos-service-rtkv
  89. device = "abc", -- 设备识别号,只能是英文字符+数值,区别大小写
  90. token = "123456", -- 设备密钥, 默认是设备的唯一id, 即mcu.unique_id()
  91. nodebug = false, -- 关闭调试日志,默认false
  92. timeout = 3000, -- 请求超时, 单位毫秒, 默认3000毫秒
  93. })
  94. -- 关于device值的默认值
  95. -- 若支持4G, 会取IMEI
  96. -- 若支持wifi, 会取MAC
  97. -- 其余情况取 mcu.unique_id() 即设备的唯一id
  98. ]]
  99. function rtkv.setup(conf)
  100. if not conf then
  101. conf = {}
  102. end
  103. rtkv.conf = conf
  104. if not rtkv.conf.apiurl then
  105. conf.apiurl = "http://rtkv.air32.cn"
  106. end
  107. if not conf.device then
  108. if mobile then
  109. conf.device = mobile.imei()
  110. elseif wlan then
  111. conf.device = wlan.getMac()
  112. else
  113. conf.device = mcu.unique_id():toHex()
  114. end
  115. end
  116. if not conf.token then
  117. conf.token = mcu.unique_id():toHex()
  118. end
  119. if not conf.timeout then
  120. conf.timeout = 3000
  121. end
  122. if not conf.nodebug then
  123. -- log.info("rtkv", "apiurl", conf.apiurl)
  124. log.info("rtkv", "device", conf.device)
  125. log.info("rtkv", "token", conf.token)
  126. log.info("rtkv", "pls visit", conf.apiurl .. "/d/" .. conf.device)
  127. end
  128. return true
  129. end
  130. --[[
  131. 设置指定键对应的值
  132. @api rtkv.set(key, value)
  133. @string 键, 不能为nil,建议只使用英文字母/数字
  134. @string 值, 不能为nil,一般建议不超过512字节
  135. @return bool 成功返回true, 否则返回nil
  136. @usage
  137. -- 如果关心执行结果, 则需要在task里执行
  138. -- 非task上下文, 会返回nil, 然后后台执行
  139. rtkv.set("age", "18")
  140. rtkv.set("version", _G.VERSION)
  141. rtkv.set("project", _G.PROJECT)
  142. -- 关于值的类型的说明
  143. -- 支持传入字符串,布尔值,整数,浮点数, 最终还是会转为字符串上传
  144. -- 通过 rtkv.get 获取值的时候, 返回的值的类型也会是字符串
  145. ]]
  146. function rtkv.set(key, value)
  147. if not rtkv.conf or not key or not value then
  148. return
  149. end
  150. local url = rtkv.conf.apiurl .. "/api/rtkv/set?"
  151. url = url .. "device=" .. rtkv.conf.device
  152. url = url .. "&token=" .. rtkv.conf.token
  153. url = url .. "&key=" .. tostring(key):urlEncode()
  154. url = url .. "&value=" .. tostring(value):urlEncode()
  155. if rtkv.conf.debug then
  156. log.debug("rtkv", url)
  157. end
  158. local co, ismain = coroutine.running()
  159. if ismain then
  160. sys.taskInit(http.request, "GET", url)
  161. else
  162. local code, headers, body = http.request("GET", url, nil, nil, {timeout=rtkv.conf.timeout}).wait()
  163. if rtkv.conf.debug then
  164. log.info("rtkv", code, body)
  165. end
  166. if code and code == 200 and body == "ok" then
  167. return true
  168. end
  169. end
  170. end
  171. --[[
  172. 批量设置键值
  173. @api rtkv.sets(datas)
  174. @table 需要设置的键值对
  175. @return bool 成功返回true, 否则返回nil
  176. @usage
  177. -- 如果关心执行结果, 则需要在task里执行
  178. -- 非task上下文, 会返回nil, 然后后台执行
  179. rtkv.sets({
  180. age = "18",
  181. vbat = 4193,
  182. temp = 23423
  183. })
  184. ]]
  185. function rtkv.sets(datas)
  186. local conf = rtkv.conf
  187. if not conf or not datas then
  188. return
  189. end
  190. local url = conf.apiurl .. "/api/rtkv/sets"
  191. local rbody = json.encode({
  192. device = conf.device,
  193. token = conf.token,
  194. data = datas
  195. })
  196. if not rbody then
  197. log.info("rtkv", "rbody is nil")
  198. return
  199. end
  200. if not conf.nodebug then
  201. log.debug("rtkv", url, rbody)
  202. end
  203. local rheaders = {}
  204. rheaders["Content-Type"] = "application/json"
  205. local co, ismain = coroutine.running()
  206. if ismain then
  207. sys.taskInit(http.request, "POST", url, rheaders, rbody, {timeout=conf.timeout})
  208. else
  209. local code, headers, body = http.request("POST", url, rheaders, rbody, {timeout=conf.timeout}).wait()
  210. if not conf.nodebug then
  211. log.info("rtkv", code, body)
  212. end
  213. if code and code == 200 and body == "ok" then
  214. return true
  215. end
  216. end
  217. end
  218. --[[
  219. 获取指定键对应的值
  220. @api rtkv.get(key)
  221. @string 键, 不能为nil,长度需要2字节以上
  222. @return string 成功返回字符,其他情况返回nil
  223. @usage
  224. -- 注意, 必须在task里执行,否则必返回nil
  225. local age = rtkv.get("age")
  226. ]]
  227. function rtkv.get(key)
  228. local conf = rtkv.conf
  229. if not conf or key then
  230. return
  231. end
  232. local url = conf.apiurl .. "/api/rtkv/get?"
  233. url = url .. "device=" .. conf.device
  234. url = url .. "&token=" .. conf.token
  235. url = url .. "&key=" .. tostring(key):urlEncode()
  236. if not conf.nodebug then
  237. log.debug("rtkv", "url", url)
  238. end
  239. local co, ismain = coroutine.running()
  240. if ismain then
  241. log.warn("rtkv", "must call in a task/thread")
  242. return
  243. else
  244. local code, headers, body = http.request("GET", url, nil, nil, {timeout=conf.timeout}).wait()
  245. if not conf.nodebug then
  246. log.info("rtkv", code, body)
  247. end
  248. if code and code == 200 and body == "ok" then
  249. return true
  250. end
  251. end
  252. end
  253. return rtkv