Luat全系列模块支持免费OTA远程升级,并提供例程将该功能部署到自己服务器上

详解luat升级服务器所需要流程,服务器要求,参数传递,示例代码

准备姿势, update升级流程

先看一眼链接哦, 对升级过程有个认识
http://oldask.openluat.com/article/93

**升级流程

模块读取自身信息—拼接字符串—get请求服务器—服务器收到请求—处理请求—如果版本号低于最新版—返回200—模块下载升级包—下载成功自动重启—升级成功
如果版本号大于等于最新版—返回404错误码—模块停止升级检查。

再次强调重点

  1. 服务器返回200, 设备将读取响应,作为升级文件
  2. 服务器返回3XX, 重定向到新的地址下载
  3. 服务器返回4XX, 设备无需升级

设备端代码

  1. require "update"
  2. update.request(nil,"iot.nutz.cn/api/site/firmware_upgrade")
  3. // 域名 + 升级URI, 用ip也是可以的
  4. // 仅支持http协议,除非自行扩展update.lua

4G模块的升级报错代码,请查阅 http://oldask.openluat.com/article/90

划重点: 要用外网服务器!!! 局域网可访问不了!!!

服务器端

设备会发什么上来:

  1. URI: /luat/update 自定义即可,服务器与设备要一致
  2. 参数5个,均通过URL传递
  • project_key 产品密钥,自行定义,可以不管
  • imei 设备识别号
  • firmware_name 当前固件名称,相当于Lod版本
  • version 用户自定义版本号
  • need_oss_url 是否需要oss路径(仅供实现了CDN之后使用),可以无视

服务器端接收到这些参数后,根据业务逻辑决定返回的内容

例如, 代码这样写,用的lod是Luat_V0017_ASR1802

  1. PRODUCT_KEY = "sadfsaqwerOKMGUFI"
  2. PROJECT = "ALIYUN"
  3. VERSION = "2.0.0"

那么, URL参数会是这样

  1. project_key=sadfsaqwerOKMGUFI
  2. imei=86902342332452
  3. firmware_name=ALIYUN_Luat_V0007_ASR1802
  4. version=2.0.0
  5. need_oss_url=0

另有完整服务器端实现,请查阅 http://oldask.openluat.com/article/878

代码示例

伪代码版

  1. // 根据imei查出设备
  2. var dev = sql("select * from t_dev where imie=" + imei);
  3. if (dev == null) {
  4. // 没有这个设备,不准升级, or 自动插入记录
  5. return resp(403);
  6. }
  7. // 根据设备查出升级计划
  8. var updatePlan = sql("select * from t_update_plan where dev_id=" + dev.id)
  9. if (updatePlan == null) {
  10. // 没有对应的升级计划,不准升级
  11. return resp(403);
  12. }
  13. // 固件版本是否对应
  14. if (firmware_name != updatePlan.firmware_name) {
  15. // 固件版本不对应,无法升级
  16. return resp(404); // 不是返回200就行
  17. }
  18. // 软件版本号是否一致
  19. if (version == updatePlan.version) {
  20. // 软件版本一样,无需升级
  21. return resp(404);
  22. }
  23. // 需要升级,写入升级文件
  24. resp.write(updatePlan.file)
  25. // 结束.

java版(使用nutz实现)

  1. @IocBean
  2. @At("/luat")
  3. public class LuatUpdateModule {
  4. @Fail("http:500")
  5. @Ok("void")
  6. @At("/update")
  7. public void update(String project_key, String imei, String firmware_name, String version, int need_oss_url, HttpServletResponse resp) throws FileNotFoundException, IOException {
  8. // TODO 根据 imei 查出设备
  9. // TODO 根据设备查出升级计划
  10. String expectVersion = "2.0.1";
  11. // 判断版本号
  12. if (expectVersion.equalsIgnoreCase(version)) {
  13. resp.setStatus(404); // 不需要升级
  14. return;
  15. }
  16. try (FileInputStream ins = new FileInputStream("/data/luat/update/" + expectVersion + "/update.bin")) {
  17. Streams.writeAndClose(resp.getOutputStream(), ins);
  18. }
  19. }
  20. }

php例子,由罗耀锋提供

  1. public function actionUpgrade() {
  2. $imei = UtilsApp::getParamterByParamterName("imei");
  3. $ver = UtilsApp::getParamterByParamterName("version");
  4. $updates = CardUpgrade::find()->all();
  5. foreach ( $updates as $update) {
  6. $verInt = intval($ver);
  7. $updateVer = intval($update->ver);
  8. if ($updateVer>$verInt) {
  9. Yii::$app->getResponse()->sendFile(Yii::$app->basePath . '/web/' . $update->filepath, 'update.bin');
  10. return;
  11. }
  12. }
  13. Yii::$app->response->statusCode=500;
  14. }

基于UDP的升级检查服务 python版

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. device_upgrade_server.py
  5. python3, ubuntu16.04, mysql.
  6. 简单的升级服务,无数据校验等,仅供参考。
  7. 升级的配置存在mysql,是由另外的网页程序实现的。
  8. 需要安装sqlalchemy和gevent
  9. pip3 install sqlalchemy
  10. pip3 install gevent
  11. mysql的python接口
  12. apt-get install python3-mysql.connector
  13. """
  14. from gevent.server import DatagramServer # 使用gevent的udp服务器。也可以直接用socket收。
  15. from gevent import monkey
  16. monkey.patch_all()
  17. import gevent
  18. import os
  19. import sys
  20. import time
  21. import datetime
  22. import re
  23. import traceback
  24. import logging
  25. logging.basicConfig(level = logging.DEBUG)
  26. dbg = logging.debug
  27. def print_exception_info(): # 打印trace信息
  28. #dbg(type(sys.exc_info()[1]))
  29. #dbg(sys.exc_info()[1])
  30. traceback.print_exc()
  31. #exc_type, exc_obj, exc_tb = sys.exc_info()
  32. #fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
  33. #dbg(exc_type, fname, exc_tb.tb_lineno)
  34. import sqlalchemy
  35. from sqlalchemy import *
  36. from sqlalchemy import create_engine
  37. from sqlalchemy.orm import sessionmaker
  38. # mysql数据库配置
  39. DB = 'mysql+mysqlconnector://name:password@127.0.0.1:3306/device_framework_upgrade'
  40. dbengine = create_engine(DB, echo = False, pool_size = 512, max_overflow = 0, pool_timeout = 0, pool_recycle = 5)
  41. Session = sessionmaker(bind = dbengine)
  42. '''
  43. device's data look like "862991443753386,A9335_V1723_B3633_WD1,1.1.8" and "Get6,12"
  44. 数据格式
  45. 1.recv [imei,project_name,version]
  46. 2.send [LUAUPDATE,upgrade_id,package_count,last_package_size]
  47. 3.recv [Getn,upgrade_id]
  48. 4.send [data]
  49. ...
  50. '''
  51. PACKAGE_SIZE = 1022
  52. start_cmd_pattern = re.compile("^(\d{15}),(\w+),(\d+\.\d+\.\d+)$")
  53. get_cmd_pattern = re.compile("^Get(\d+),(\d+)$")
  54. class SingleUpgradeServer(DatagramServer):
  55. def __init__(self, *args, **kwargs):
  56. DatagramServer.__init__(self, *args, **kwargs)
  57. def handle(self, data_org, address): # 接收数据
  58. try:
  59. dbg('=================================================')
  60. dbg(('=== %s %s: got data_org') % (time.ctime(), address[0]))
  61. dbg('=================================================')
  62. # 连接数据库
  63. dbsession = Session()
  64. data = data_org.decode('ascii') # ascii解码
  65. dbg(data)
  66. function = None
  67. reply = bytearray()
  68. # 匹配命令
  69. match = start_cmd_pattern.match(data)
  70. if match:
  71. function = 0
  72. else:
  73. match = get_cmd_pattern.match(data)
  74. if match:
  75. function = 1
  76. dbg('function = {}'.format(function))
  77. if function == 0: # match start_cmd_pattern
  78. imei, project_name, version = match.groups()
  79. # 在数据库中根据项目名查找固件
  80. upgrade = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade where project_name = :project_name and status = 1"), {"project_name":project_name}).fetchone()
  81. if upgrade is None:
  82. reply = "no upgrade".encode('ascii')
  83. dbg("no upgrade")
  84. # 检查imei是否在升级范围内
  85. in_range = False
  86. imei_ranges = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade_imei_range where upgrade_id = :upgrade_id"), {"upgrade_id":upgrade.id}).fetchall()
  87. for imei_range in imei_ranges:
  88. if imei_range.starting_imei <= imei and imei <= imei_range.ending_imei:
  89. in_range = True
  90. break
  91. if not in_range:
  92. reply = "imei range error".encode('ascii')
  93. dbg("range error")
  94. # 检查设备版本号是否最新
  95. v1, v2, v3 = upgrade["version"].split('.')
  96. server_version = (int(v1) << 16) + (int(v2) << 8) + int(v3)
  97. v1, v2, v3 = version.split('.')
  98. device_version = (int(v1) << 16) + (int(v2) << 8) + int(v3)
  99. if device_version >= server_version:
  100. reply = "version error".encode('ascii')
  101. dbg("version error")
  102. else:
  103. # 读取文件信息
  104. file_path = upgrade['file_path']
  105. dbg(file_path)
  106. file_size = os.path.getsize(file_path)
  107. last_package_size = file_size % PACKAGE_SIZE
  108. package_count = int(file_size / PACKAGE_SIZE)
  109. if last_package_size != 0:
  110. package_count += 1
  111. dbg((file_size, package_count, last_package_size))
  112. reply = ("LUAUPDATE,%d,%d,%d" % (upgrade["id"], package_count, last_package_size)).encode('ascii')
  113. elif function == 1: # match get_cmd_pattern
  114. index, upgrade_id = match.groups()
  115. dbg("get")
  116. # 查找文件
  117. upgrade = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade where id = :id and status = 1"), {"id":int(upgrade_id)}).fetchone()
  118. file_path = upgrade['file_path']
  119. dbg("file is %s" % file_path)
  120. # 读取文件数据
  121. fd = open(file_path, 'rb')
  122. fd.seek((int(index) - 1) * PACKAGE_SIZE)
  123. reply = bytearray([int(int(index) / 256), int(index) % 256]) + fd.read(PACKAGE_SIZE)
  124. else:
  125. reply = "unknown function".encode('ascii')
  126. except:
  127. dbg("error")
  128. print_exception_info()
  129. reply = "error all".encode('ascii')
  130. finally:
  131. # 回复数据给设备
  132. self.socket.sendto(reply, address)
  133. if dbsession:
  134. dbsession.close()
  135. if __name__ == '__main__':
  136. dbg('device_upgrade_server.py receiving datagrams on %s:%d' % ('', 2234))
  137. try:
  138. # 启动udp服务器
  139. SingleUpgradeServer('%s:%d' % ('', 2234)).serve_forever()
  140. except:
  141. print_exception_info()
  • 发表于 2018-10-15 17:20
  • 阅读 ( 9366 )
  • 分类:默认分类

1 条评论

请先 登录 后评论
不写代码的码农
技术销售Wendal

软件工程师

15 篇文章

作家榜 »

  1. 技术销售Delectate 43 文章
  2. 陈夏 26 文章
  3. 国梁 24 文章
  4. miuser 21 文章
  5. 晨旭 20 文章
  6. 朱天华 19 文章
  7. 金艺 19 文章
  8. 杨奉武 18 文章