ble_fota_main.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. --[[
  2. @module ble_fota_main
  3. @summary 蓝牙FOTA升级功能模块
  4. @version 1.0
  5. @date 2025.01.20
  6. @author 孟伟
  7. @usage
  8. -- 蓝牙FOTA升级功能
  9. -- 提供通过蓝牙低功耗(BLE)接收升级包数据进行固件升级的功能
  10. 用法:
  11. 1. 先把脚本和固件烧录到Air8000模块中,并确认设备正常启动
  12. 2. 模块启动后会自动开启BLE广播,广播名称为"Air8000_FOTA"
  13. 3. 在电脑端操作:运行ble_test.py脚本连接设备并发送升级固件
  14. 注意:确保升级文件名为正确格式,并且与ble_test.py在同一目录下
  15. 4. 观察日志输出确认升级进度
  16. 5. 模块接收并验证固件成功后,会自动重启并应用新固件
  17. BLE通讯过程说明
  18. 蓝牙FOTA升级通过BLE特征值进行命令控制和数据传输:
  19. 协议流程:
  20. 1. 上位机通过BLE扫描并连接名为"Air8000_FOTA"的设备
  21. 2. 上位机向命令特征值(F001)发送开始升级命令(0x01)和固件大小
  22. 3. 设备初始化FOTA功能并准备接收数据
  23. 4. 上位机向数据特征值(F002)分包发送固件数据
  24. 5. 设备接收并保存数据到临时文件
  25. 6. 上位机发送结束升级命令(0x02)
  26. 7. 设备验证固件完整性并执行FOTA升级流程
  27. 8. 升级成功后设备自动重启
  28. 注意:
  29. - 本demo使用特定的服务UUID(F000)和特征值UUID(F001/F002)进行通信
  30. - 升级过程中如果连接断开,设备会自动终止升级并清理临时文件
  31. - 升级包大小不能超过设备的存储空间
  32. - 本文件没有对外接口,直接在main.lua中require "ble_fota_main"就可以加载运行;
  33. ]]
  34. -- 配置参数
  35. local config = {
  36. device_name = "Air8000_FOTA", -- 设备广播名称
  37. service_uuid = "F000", -- FOTA服务UUID(短格式)
  38. char_uuid_cmd = "F001", -- 命令特征值UUID
  39. char_uuid_data = "F002", -- 数据特征值UUID
  40. max_packet_size = 20 -- BLE数据包最大长度(字节)
  41. }
  42. -- 升级状态管理
  43. local upgrade_state = {
  44. is_upgrading = false, -- 是否正在升级
  45. ble_connected = false, -- 是否已连接蓝牙设备
  46. total_size = 0, -- 总文件大小(字节)
  47. received_size = 0, -- 已接收大小(字节)
  48. upgrade_file = "/ble_fota.bin" -- 临时升级文件路径
  49. }
  50. -- GATT服务数据库定义
  51. -- 这里定义了BLE设备提供的服务和特征值
  52. -- GATT数据库定义
  53. local att_db = {
  54. string.fromHex(config.service_uuid), -- Service UUID
  55. {
  56. string.fromHex(config.char_uuid_cmd),
  57. ble.WRITE | ble.WRITE_CMD
  58. },
  59. {
  60. string.fromHex(config.char_uuid_data),
  61. ble.WRITE | ble.WRITE_CMD
  62. },
  63. -- Characteristic 3: Status (Notify + Read)
  64. -- {
  65. -- string.fromHex(config.char_uuid_status),
  66. -- ble.NOTIFY | ble.READ
  67. -- }
  68. }
  69. -- 处理FOTA命令
  70. -- @param cmd_data 命令数据,格式:[命令码(1字节)] 或 [命令码(1字节) + 文件大小(4字节)]
  71. local function handle_command(cmd_data)
  72. log.info("FOTA_CMD", "收到命令数据:", cmd_data:toHex(), "长度:", #cmd_data)
  73. -- 检查命令数据是否有效
  74. if #cmd_data < 1 then
  75. log.error("FOTA_CMD", "命令数据为空")
  76. return
  77. end
  78. -- 解析命令码(第一个字节)
  79. local cmd = cmd_data:byte(1)
  80. log.info("FOTA_CMD", "解析命令码:", cmd, string.format("(0x%02X)", cmd))
  81. -- 命令0x01:开始升级
  82. if cmd == 0x01 then
  83. log.info("FOTA_CMD", "处理开始升级命令")
  84. -- 检查命令格式:需要至少5字节(1字节命令码 + 4字节文件大小)
  85. if #cmd_data >= 5 then
  86. -- 解析文件大小(小端序,从第2字节开始)
  87. local total_size = string.unpack("<I4", cmd_data, 2)
  88. log.info("FOTA_CMD", "文件总大小:", total_size, "字节")
  89. -- 初始化FOTA子系统
  90. log.info("FOTA_CMD", "初始化FOTA子系统...")
  91. if fota.init() then
  92. log.info("FOTA_CMD", "FOTA初始化成功")
  93. -- 等待FOTA底层准备就绪
  94. log.info("FOTA_CMD", "等待FOTA底层准备...")
  95. if fota.wait() then
  96. log.info("FOTA_CMD", "FOTA底层准备就绪")
  97. -- 删除旧的临时文件(如果存在)
  98. if os.remove(upgrade_state.upgrade_file) then
  99. log.info("FOTA_CMD", "已清理旧临时文件")
  100. end
  101. -- 更新升级状态
  102. upgrade_state.is_upgrading = true
  103. upgrade_state.total_size = total_size
  104. upgrade_state.received_size = 0
  105. log.info("FOTA_CMD", "升级状态已设置",
  106. "总大小:", upgrade_state.total_size,
  107. "临时文件:", upgrade_state.upgrade_file)
  108. log.info("FOTA_CMD", "准备接收固件数据...")
  109. else
  110. log.error("FOTA_CMD", "FOTA底层准备失败")
  111. fota.finish(false)
  112. end
  113. else
  114. log.error("FOTA_CMD", "FOTA初始化失败")
  115. end
  116. else
  117. log.error("FOTA_CMD", "开始命令格式错误,长度不足")
  118. end
  119. -- 命令0x02:结束升级
  120. elseif cmd == 0x02 then
  121. log.info("FOTA_CMD", "处理结束升级命令")
  122. -- 检查是否处于升级状态
  123. if not upgrade_state.is_upgrading then
  124. log.warn("FOTA_CMD", "未处于升级状态,忽略结束命令")
  125. return
  126. end
  127. -- 验证文件完整性
  128. log.info("FOTA_CMD", "验证文件完整性...")
  129. log.info("FOTA_CMD", "已接收:", upgrade_state.received_size, "字节")
  130. log.info("FOTA_CMD", "应接收:", upgrade_state.total_size, "字节")
  131. if upgrade_state.received_size == upgrade_state.total_size then
  132. log.info("FOTA_CMD", "文件完整性验证通过")
  133. -- 执行FOTA升级
  134. log.info("FOTA_CMD", "开始执行FOTA升级...")
  135. local result, isDone = fota.file(upgrade_state.upgrade_file)
  136. log.info("FOTA_CMD", "FOTA升级结果:", "result:", result, "isDone:", isDone)
  137. if result and isDone then
  138. log.info("FOTA_CMD", " FOTA升级成功!")
  139. -- 延迟重启,给用户一些反应时间
  140. log.info("FOTA_CMD", "2秒后设备将自动重启...")
  141. -- 延迟2秒后重启设备
  142. sys.timerStart(function()
  143. -- 完成FOTA流程并重启
  144. fota.finish(true)
  145. log.info("FOTA_CMD", "正在重启设备...")
  146. rtos.reboot()
  147. end, 2000)
  148. else
  149. log.error("FOTA_CMD", "FOTA升级失败")
  150. end
  151. else
  152. log.error("FOTA_CMD", "文件不完整,升级失败")
  153. end
  154. -- 清理升级状态(无论成功还是失败)
  155. log.info("FOTA_CMD", "清理升级状态...")
  156. upgrade_state.is_upgrading = false
  157. -- 删除临时文件
  158. if upgrade_state.upgrade_file then
  159. if os.remove(upgrade_state.upgrade_file) then
  160. log.info("FOTA_CMD", "已删除临时文件")
  161. else
  162. log.warn("FOTA_CMD", "删除临时文件失败")
  163. end
  164. end
  165. -- 结束FOTA流程
  166. fota.finish(false)
  167. log.info("FOTA_CMD", "升级流程结束")
  168. else
  169. log.warn("FOTA_CMD", "未知命令码:", cmd, string.format("(0x%02X)", cmd))
  170. end
  171. end
  172. -- 处理FOTA数据
  173. -- @param data 固件数据块
  174. local function handle_data(data)
  175. log.info("FOTA_DATA", "收到数据包,长度:", #data, "字节")
  176. -- 检查是否处于升级状态
  177. if not upgrade_state.is_upgrading then
  178. log.warn("FOTA_DATA", "未处于升级状态,忽略数据")
  179. return
  180. end
  181. -- 保存数据到临时文件
  182. log.info("FOTA_DATA", "写入文件:", upgrade_state.upgrade_file)
  183. local file = io.open(upgrade_state.upgrade_file, "ab")
  184. if file then
  185. -- 写入数据
  186. file:write(data)
  187. file:close()
  188. -- 更新接收状态
  189. upgrade_state.received_size = upgrade_state.received_size + #data
  190. -- 计算并显示进度
  191. local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
  192. -- 每50个数据包或完成时打印进度
  193. if upgrade_state.received_size % (config.max_packet_size * 50) == 0 or
  194. upgrade_state.received_size >= upgrade_state.total_size then
  195. log.info("FOTA_DATA", "升级进度:", progress, "%",
  196. "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
  197. end
  198. log.info("FOTA_DATA", "数据写入成功,当前总计:", upgrade_state.received_size, "字节")
  199. else
  200. log.error("FOTA_DATA", "打开文件失败:", upgrade_state.upgrade_file)
  201. -- 文件操作失败,终止升级
  202. upgrade_state.is_upgrading = false
  203. fota.finish(false)
  204. end
  205. end
  206. -- BLE事件回调函数
  207. -- @param ble_dev BLE设备对象
  208. -- @param event 事件类型
  209. -- @param param 事件参数
  210. local function ble_event_cb(ble_dev, event, param)
  211. log.info("BLE_EVENT", "收到BLE事件:", event)
  212. -- 根据LuatOS BLE事件枚举处理不同事件
  213. if event == ble.EVENT_CONN then
  214. -- 连接成功事件
  215. log.info("BLE_EVENT", "设备已连接", "地址:", param.addr and param.addr:toHex() or "未知")
  216. upgrade_state.ble_connected = true
  217. elseif event == ble.EVENT_DISCONN then
  218. -- 连接断开事件
  219. log.info("BLE_EVENT", "设备已断开连接", "原因:", param.reason or "未知")
  220. upgrade_state.ble_connected = false
  221. -- 如果正在升级,连接断开则终止升级
  222. if upgrade_state.is_upgrading then
  223. log.error("BLE_EVENT", "升级过程中连接断开,终止升级")
  224. upgrade_state.is_upgrading = false
  225. -- 删除临时文件
  226. if upgrade_state.upgrade_file then
  227. os.remove(upgrade_state.upgrade_file)
  228. end
  229. -- 结束FOTA流程
  230. fota.finish(false)
  231. end
  232. elseif event == ble.EVENT_WRITE then
  233. -- 写入事件 - 这是关键事件!
  234. log.info("BLE_EVENT", "处理写入事件")
  235. -- 检查参数是否完整
  236. if not param or not param.uuid_service or not param.uuid_characteristic or not param.data then
  237. log.error("BLE_EVENT", "写入事件参数不完整")
  238. return
  239. end
  240. -- 获取服务UUID和特征值UUID
  241. local service_uuid = param.uuid_service:toHex()
  242. local char_uuid = param.uuid_characteristic:toHex()
  243. local data = param.data
  244. log.info("BLE_EVENT", "服务UUID:", service_uuid)
  245. log.info("BLE_EVENT", "特征值UUID:", char_uuid)
  246. log.info("BLE_EVENT", "数据长度:", #data, "字节")
  247. -- 简化的UUID匹配逻辑:检查UUID是否包含我们的短UUID
  248. local is_service_match = string.find(service_uuid:lower(), "f000")
  249. local is_cmd_match = string.find(char_uuid:lower(), "f001")
  250. local is_data_match = string.find(char_uuid:lower(), "f002")
  251. log.info("BLE_EVENT", "UUID匹配结果:",
  252. "服务匹配:", is_service_match,
  253. "命令匹配:", is_cmd_match,
  254. "数据匹配:", is_data_match)
  255. if is_service_match then
  256. if is_cmd_match then
  257. -- 命令特征值:处理FOTA命令
  258. log.info("BLE_EVENT", "命令特征值匹配,处理命令")
  259. handle_command(data)
  260. elseif is_data_match then
  261. -- 数据特征值:处理FOTA数据
  262. log.info("BLE_EVENT", "数据特征值匹配,处理数据")
  263. handle_data(data)
  264. else
  265. log.warn("BLE_EVENT", "未知的特征值UUID:", char_uuid)
  266. end
  267. else
  268. log.warn("BLE_EVENT", "未知的服务UUID:", service_uuid)
  269. end
  270. elseif event == ble.EVENT_READ then
  271. -- 读取事件 - 外围设备收到主设备读请求
  272. log.info("BLE_EVENT", "处理读取事件")
  273. elseif event == ble.EVENT_READ_VALUE then
  274. -- 读取操作完成事件 - 中心设备读取特征值完成
  275. log.info("BLE_EVENT", "读取操作完成", "数据:", param.data and param.data:toHex() or "无数据")
  276. elseif event == ble.EVENT_SCAN_REPORT then
  277. -- 扫描报告事件 - 中心设备扫描到其他BLE设备
  278. log.info("BLE_EVENT", "扫描报告", "RSSI:", param.rssi, "地址:", param.adv_addr and param.adv_addr:toHex() or "未知")
  279. elseif event == ble.EVENT_SCAN_STOP then
  280. -- 扫描停止事件
  281. log.info("BLE_EVENT", "扫描停止")
  282. else
  283. -- 其他事件
  284. log.info("BLE_EVENT", "其他事件类型:", event)
  285. if param then
  286. -- 尝试打印参数的基本信息,避免直接打印table导致错误
  287. if type(param) == "table" then
  288. log.info("BLE_EVENT", "事件参数为table,包含字段:", #param)
  289. for k, v in pairs(param) do
  290. if type(v) == "string" then
  291. log.info("BLE_EVENT", "参数字段:", k, "值:", v:toHex())
  292. else
  293. log.info("BLE_EVENT", "参数字段:", k, "类型:", type(v))
  294. end
  295. end
  296. else
  297. log.info("BLE_EVENT", "事件参数类型:", type(param))
  298. end
  299. end
  300. end
  301. end
  302. -- 初始化BLE功能
  303. -- @return boolean 初始化是否成功
  304. local function init_ble()
  305. log.info("BLE_INIT", "开始初始化BLE...")
  306. -- 初始化蓝牙核心
  307. local bt_dev = bluetooth.init()
  308. if not bt_dev then
  309. log.error("BLE_INIT", "蓝牙核心初始化失败")
  310. return false
  311. end
  312. log.info("BLE_INIT", "蓝牙核心初始化成功")
  313. -- 初始化BLE功能
  314. local ble_dev = bt_dev:ble(ble_event_cb)
  315. if not ble_dev then
  316. log.error("BLE_INIT", "BLE功能初始化失败")
  317. return false
  318. end
  319. log.info("BLE_INIT", "BLE功能初始化成功")
  320. -- 创建GATT服务
  321. local gatt_result = ble_dev:gatt_create(att_db)
  322. if not gatt_result then
  323. log.error("BLE_INIT", "GATT服务创建失败")
  324. return false
  325. end
  326. log.info("BLE_INIT", "GATT服务创建成功")
  327. -- 配置广播数据
  328. log.info("BLE_INIT", "配置广播数据...")
  329. local adv_result = ble_dev:adv_create({
  330. addr_mode = ble.PUBLIC,
  331. channel_map = ble.CHNLS_ALL,
  332. intv_min = 120,
  333. intv_max = 120,
  334. adv_data = {
  335. { ble.FLAGS, string.char(0x06) }, -- BLE标志
  336. { ble.COMPLETE_LOCAL_NAME, config.device_name }, -- 设备名称
  337. }
  338. })
  339. if not adv_result then
  340. log.error("BLE_INIT", "广播配置失败")
  341. return false
  342. end
  343. log.info("BLE_INIT", "广播配置成功")
  344. -- 开始广播
  345. ble_dev:adv_start()
  346. log.info("BLE_INIT", " BLE广播已启动,设备名称:", config.device_name)
  347. return true
  348. end
  349. -- 主任务函数
  350. sys.taskInit(function()
  351. -- 主循环
  352. while true do
  353. log.info("MAIN", "尝试初始化BLE...")
  354. -- 重置连接状态
  355. upgrade_state.ble_connected = false
  356. -- 初始化BLE
  357. if init_ble() then
  358. log.info("MAIN", "BLE初始化成功,进入主循环")
  359. -- BLE运行状态维护循环
  360. while true do
  361. -- 等待5秒
  362. sys.wait(5000)
  363. if upgrade_state.is_upgrading then
  364. local progress = math.floor((upgrade_state.received_size / upgrade_state.total_size) * 100)
  365. log.info("MAIN", "升级状态: 进行中",
  366. progress, "%",
  367. "(", upgrade_state.received_size, "/", upgrade_state.total_size, ")")
  368. end
  369. end
  370. else
  371. log.error("MAIN", "BLE初始化失败,5秒后重试...")
  372. sys.wait(5000) -- 等待5秒后重试
  373. end
  374. end
  375. end)