从硬件到软件,从串口到云端,一套完整的物联网Android解决方案
写在前面
半年前,朋友找到我,说他们公司有一批商用咖啡机需要做智能化改造。原来只能通过按键操作的机器,现在要用平板来控制,还要支持远程管理、扫码点单、自动烧水、标签打印……听完需求,我的第一反应是:这不就是一个典型的物联网Android项目吗?
真正上手之后,发现坑比想象中多得多。串口通信不稳定、MQTT重连机制要自己撸、离线缓存策略得设计好、蓝牙打印机各种兼容性问题……好在,熬了无数个夜之后,总算稳定上线了。
今天就把这套系统的完整架构分享出来,希望能给做物联网Android开发的朋友一些参考。
先看看整体效果
系统跑在一台12寸的Android平板上,安装在咖啡机正面。主要功能包括:
- 点单下单(支持扫码器扫码点单)
- 设备控制(开关机、温度设置、萃取控制)
- 自动烧水(定时任务调度)
- 标签打印(连接蓝牙打印机)
- 设备状态实时监控
- 远程运维(云端控制台统一管理)
用户界面用的是Material Design组件,配了一些图表展示数据趋势。整体响应速度还不错,毕竟硬件直连,延迟都在毫秒级。
核心架构:四层分离,各司其职
架构这块我花的时间最多。物联网项目和普通App最大的区别在于——硬件通信的不确定性。串口发出去的指令,设备可能没收到,可能收到了但没执行,可能执行了但返回的数据丢了。
所以从一开始我就决定把硬件层彻底隔离出来,这样即使底层协议变更,业务层也不用大改。
最终确定的架构是这样的:
┌─────────────────────────────────────┐ │ 表现层(UI) │ │ Activity / Fragment / Adapter │ ├─────────────────────────────────────┤ │ 业务层(Business) │ │ Manager / Service / Receiver │ │ / Schedule / Cache Proxy │ ├─────────────────────────────────────┤ │ 数据层(Data) │ │ API / Room / MMKV / MQTT │ ├─────────────────────────────────────┤ │ 硬件通信层(Hardware) │ │ SerialPort / Modbus / GPIO / 打印机 │ └─────────────────────────────────────┘
每一层都干了什么?
表现层:就是用户看到的界面,点一点、滑一滑。用了DataBinding做双向绑定,页面状态变化直接反映到UI上,代码量少了不少。
业务层:这一层是最复杂的。Manager类管理各类业务逻辑,比如设备状态管理、订单管理;Service负责后台常驻任务;Receiver监听系统广播(开机自启就是在这实现的);Schedule模块处理定时任务,比如每天早上6点自动烧水。
数据层:统一的数据存取接口。API定义云端接口,Room存结构化业务数据,MMKV存配置项。三层数据源可以灵活组合,优先使用本地缓存,网络恢复后再同步。
硬件通信层:所有硬件操作都在这一层封装。串口用的是一个开源库改造的,Modbus RTU协议自己撸了一套解析逻辑,GPIO控制直接操作/sys/class/gpio节点。上层业务完全不关心底层是串口还是USB,只调用统一接口就行。
硬件接入的那些事儿
串口通信
咖啡机主控板用的是RS232串口,波特率9600。Android设备通过USB转串口线连接,用的是一个SerialPort库。
踩过的坑:
- 有些廉价转接线驱动不支持,换了FTDI芯片的才稳定
- 串口读写必须放在子线程,否则UI卡顿
- 数据粘包问题要自己做分包处理
蓝牙打印机
用的是市面常见的蓝牙热敏标签打印机,支持ESC/POS指令集。 封装了一个打印服务,把标签模板解析成打印指令。支持打印商品标签、取餐码、价格标签。打印队列用了RxJava的异步处理,不会因为一张卡住导致全部阻塞。
扫码器
接入的是USB即插即用的扫码器,模拟键盘输入。在输入框获得焦点的状态下,扫码器会把识别的条码内容自动输入。 配合一个自定义EditText,扫码完成后自动触发查询或下单逻辑,体验很流畅。
LED灯光和声音
LED用的GPIO控制,高低电平切换开关。声音用MediaPlayer播放本地资源,语音提示用的TTS引擎。
任务管理:让设备自己跑起来
这套系统最让我满意的是无人值守能力。设备通电后自动启动,初始化完成后自动连接MQTT、上报状态。
设备状态上报
每隔30秒采集一次设备状态(水温、压力、工作模式、故障码),通过HTTP上报云端。如果网络断了,就存到本地Room数据库,等网络恢复后批量补报。
自动烧水服务
咖啡机有独立的热水系统,可以根据营业时间预设烧水计划。比如早上7点开始加热,8点前达到92℃。 用的是Android的AlarmManager + JobScheduler组合,即使App被杀死也能定时唤醒。
日志上传
所有的操作日志、设备日志都存了本地文件,每天凌晨自动打包上传。线上问题排查基本靠这个,不用去现场翻日志了。
开早锁鲜
这个功能是咖啡师强烈要求的。每天早上开机后,系统自动执行一次预热 → 冲洗 → 萃取测试,确保第一杯咖啡的品质。 如果测试失败,会重试3次,仍然失败的话通过MQTT推送告警到店长手机。
网络通信:HTTP + MQTT 双通道
HTTP通道
用的EasyHttp + OkHttp组合,封装了一个统一的网络请求层。 支持的功能:
- 自动添加Token
- 请求重试(最多3次)
- 超时控制(连接10s,读写30s)
- 响应统一解析
MQTT通道
物联网设备必备!云端可以通过MQTT实时下发指令,比HTTP轮询省电多了。 用的Eclipse Paho库,自己封装了一个MQTT管理器,支持:
- 自动重连(断线后指数退避重试)
- 消息订阅/取消订阅
- 消息持久化(离线期间的消息重新上线后收到)
数据存储:MMKV + Room 双缓存
这一点我觉得做得很扎实。 MMKV:存配置项,比如服务器地址、设备编号、用户偏好设置。毫秒级读写,比SharedPreferences强太多。
Room:存业务数据,比如订单记录、设备历史状态、打印记录。支持复杂的查询统计,页面展示图表全靠它。
策略是:优先读缓存,缓存没有才请求网络。写数据先写本地,再异步同步到云端。
多版本构建:开发/测试/生产环境分离
这个用Product Flavors实现的,三个flavor:
- dev:开发环境,API地址指向本地测试服务器,日志全开,集成LeakCanary
- staging:预发布环境,基本和生产一致,但日志保留完整
- prod:生产环境,API地址是正式域名,日志只保留Error级别
切换环境需要在Build Variants里选一下,构建出来的包名、图标、配置都自动切换,非常方便。
开机自启与广播处理
商用设备要求通电自启,所以BootReceiver是必须的。 收到开机广播后,延迟5秒启动主Activity(等系统完全初始化),然后自动连接硬件、连接MQTT、开始状态上报。
其他广播处理:
- 购物车数量变化:全局广播,多个页面同步刷新
- MQTT消息广播:收到云端指令后通过LocalBroadcast分发到各个模块
- 网络变化广播:网络恢复后触发数据补报
所有Action都定义在一个常量类里,方便管理。
关于稳定性的那些优化
上线一个月,崩溃率控制在0.3%以下。说几个关键优化:
- 串口超时处理:每次读写设置超时时间,超时后自动重试,不卡死UI线程
- 内存泄漏监控:LeakCanary + 友盟U-APM双重监控,及时发现泄漏
- 崩溃自动恢复:关键服务崩溃后自动重启,不影响用户使用
- 日志分级存储:不同的日志级别存不同文件,避免文件过大
- 电量优化:MQTT的KeepAlive设置为60秒,减少网络唤醒
技术栈一览
| 分类 | 技术 | 用途 |
|---|---|---|
| 网络 | EasyHttp + OkHttp | HTTP请求 |
| 网络 | Retrofit | REST API |
| 缓存 | MMKV | 高性能KV存储 |
| 数据库 | Room | 结构化数据存储 |
| 响应式 | RxJava2 | 异步任务编排 |
| MQTT | Eclipse Paho | 消息推送 |
| 图片 | Glide | 图片加载 |
| UI | Material Design | 组件库 |
| 导航 | Navigation Component | 页面导航 |
| 图表 | MPAndroidChart | 数据可视化 |
| 弹窗 | XPopup | 弹窗组件 |
| 监控 | 友盟U-APM | 性能监控 |
| 适配 | AndroidAutoSize | 屏幕适配 |
最后说几句
这个项目从立项到上线差不多半年时间,中间踩了无数坑。最大的感悟是:物联网开发,80%的精力都在处理各种边界情况。设备断连了怎么办?数据没发出去怎么办?硬件返回的数据格式不对怎么办?
这些看起来琐碎的问题,恰恰决定了产品能否真正落地。
如果你也在做类似的物联网Android项目,希望这篇文章能帮你少走一些弯路。有什么问题欢迎评论区交流,看到都会回。
对了,如果觉得有用,别忘了点个赞 👍 支持一下