ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序硬件交互实战:蓝牙称重与云函数协同方案

微信小程序硬件交互实战:蓝牙称重与云函数协同方案 简介本资源是2022年中国高校计算机大赛微信小程序赛道国家级三等奖获奖作品——全自动称重分鱼系统面向计算机专业本科生、毕业设计学生及小程序开发者解决渔业场景中人工称重效率低、分类粗放等实际问题。压缩包共85个文件含15个JS逻辑文件实现蓝牙通信与业务流程、11个WXML页面结构、12个WXSS样式文件、23张PNG界面截图与示意图、17个JSON配置与数据模拟文件整体仅370KB轻量但结构完整涵盖小程序前端、云函数对接、本地模拟数据及基础权限管理模块。目前已有173人学习下载。读者可直接运行调试获取完整的赛题实现方案包括称重数据实时采集逻辑、鱼类图像识别的前端调用封装、后台数据库字段设计参考、ECharts报表集成示例以及项目目录规范含cloudfunctions云函数、utils工具库、pages多页面路由等典型结构是理解小程序工程化落地的优质实践样本。1. 这不是一条鱼的称重而是一套可落地的微信小程序硬件协同方案“全自动称重分鱼系统”听起来像水产厂里的工业设备但它的载体是微信小程序——2022年中国高校计算机大赛微信小程序赛道国奖三等奖作品。它解决的不是“怎么把鱼放上秤”而是在无App安装、低用户门槛前提下让渔民/分拣员用手机扫一下、拍一下、点一下就完成活鱼个体称重、品种识别、等级划分、数据归档与报表生成。核心不在“称重传感器精度”而在“如何让微信小程序稳定驱动USB串口设备如HX711模块、实时解析AD值、校准零点漂移、规避微信端JS单线程阻塞导致的采样丢帧”以及最关键的——如何绕过小程序无法直连硬件的限制用云函数桥接本地蓝牙/WiFi模组实现「前端扫码触发→云函数调度边缘网关→返回结构化称重结果」的闭环。适合正在做毕业设计graduation design、需要硬件交互能力又受限于小程序安全沙箱的学生团队也适用于社区水产合作社、小型渔港分拣站等轻量级数字化场景。它不依赖uniapp或HBuilderX所有逻辑扎根于原生小程序框架与云开发能力app.json配置、cloudfunctions目录组织、wx.getConnectedBluetoothDevices调用时机全是真实踩坑后沉淀的最小可行路径。2. 从 app.json 到云函数小程序端硬件交互的三层架构设计微信小程序本身无法直接访问串口、GPIO 或 USB 设备因此“全自动称重分鱼”的实现必须拆解为前端展示层、通信中继层、硬件控制层。这三层不是理论模型而是app.json配置、云函数代码、蓝牙设备固件三者强耦合的结果。常见错误是把所有逻辑堆在onLoad里反复调用wx.startBluetoothDevicesDiscovery最终触发[app.json 文件内容错误]或invalid state报错——根本原因在于未按微信小程序蓝牙通信的状态机约束组织代码。2.1 app.json 的权限声明与页面路由必须严格匹配硬件流程app.json不仅是页面注册表更是小程序能力的“宪法性文件”。针对称重场景必须显式声明以下权限与页面结构{ pages: [ pages/home/home, pages/scan/scan, pages/weight/weight, pages/report/report ], permission: { scope.bluetooth: { desc: 用于连接电子秤蓝牙模块获取实时重量数据 }, scope.record: { desc: 用于录制分鱼过程语音备注可选 } }, requiredPrivateInfos: [getLocation, getPhoneNumber], usingComponents: true, sitemapLocation: sitemap.json }注意scope.record在最新基础库3.8.10中若未实际调用wx.startRecord会导致审核失败而scope.bluetooth声明后必须在用户首次进入pages/scan/scan页面时主动调用wx.openBluetoothAdapter并监听onBluetoothAdapterStateChange否则后续wx.getConnectedBluetoothDevices永远返回空数组。很多毕业设计项目卡在“找不到设备”根源就是app.json写了权限但前端没触发适配器初始化。2.2 cloudfunctions 目录结构决定硬件指令分发效率云函数不是万能胶而是硬件指令的“交通调度中心”。本项目cloudfunctions目录采用三级分层cloudfunctions/ ├── weight-control/ // 主控函数接收小程序请求 → 查询设备在线状态 → 下发校准/读数指令 ├── device-sync/ // 设备同步函数定时拉取蓝牙网关上报的原始AD值存入云数据库 └── report-gen/ // 报表生成函数聚合当日称重记录生成PDF并推送到用户消息其中weight-control函数的核心逻辑是判断设备连接状态并透传指令// cloudfunctions/weight-control/index.js const cloud require(wx-server-sdk) cloud.init() exports.main async (event, context) { const { action, deviceId, calibrationValue } event const db cloud.database() // 1. 查询该设备是否已绑定且在线通过 device-sync 函数写入的 lastHeartbeat 字段 const device await db.collection(bluetooth_devices).where({ _id: deviceId, lastHeartbeat: db.command.gt(Date.now() - 30000) // 30秒内心跳有效 }).get() if (device.data.length 0) { return { code: 400, msg: 设备离线请检查蓝牙网关供电 } } // 2. 根据 action 类型构造指令包JSON格式经base64编码后下发给网关 let payload {} switch (action) { case tare: payload { cmd: TARE, ts: Date.now() } break case read: payload { cmd: READ, ts: Date.now(), timeout: 2000 } break case calibrate: payload { cmd: CALIBRATE, value: calibrationValue } break } // 3. 调用云调用向网关设备推送指令此处使用云调用 invoke 接口 try { const result await cloud.callFunction({ name: device-sync, data: { type: send-command, deviceId, payload: Buffer.from(JSON.stringify(payload)).toString(base64) } }) return { code: 200, data: result.result } } catch (e) { return { code: 500, msg: 指令下发失败, error: e.message } } }提示device-sync函数需部署在支持 WebSocket 的云开发环境如腾讯云云函数 SCF并维持与蓝牙网关的长连接。若使用 HTTP 轮询称重响应延迟将超过 800ms无法满足“实时分鱼”需求。学生项目常误用wx.request直连网关IP既违反小程序安全策略又因HTTPS证书问题导致连接失败。2.3 小程序端蓝牙通信必须遵循微信状态机生命周期微信小程序蓝牙 API 是典型的状态驱动模型任何跳过openAdapter → startDiscovery → getConnectedDevices → createBLEConnection步骤的操作都会失败。以下是pages/scan/scan.js中可靠连接流程Page({ data: { devices: [], connectedDeviceId: }, onLoad() { this.openBluetoothAdapter() }, openBluetoothAdapter() { wx.openBluetoothAdapter({ success: () { console.log(蓝牙适配器开启成功) this.startBluetoothDevicesDiscovery() }, fail: (err) { wx.showModal({ title: 蓝牙不可用, content: 请检查手机蓝牙是否开启或重启小程序, showCancel: false }) } }) }, startBluetoothDevicesDiscovery() { wx.startBluetoothDevicesDiscovery({ success: () { // 必须在 success 回调内监听设备发现事件 wx.onBluetoothDeviceFound((devices) { const fishScaleDevices devices.filter(d d.name d.name.includes(FishScale) // 设备广播名约定 ) this.setData({ devices: fishScaleDevices }) }) } }) }, connectToDevice(e) { const deviceId e.currentTarget.dataset.deviceid wx.createBLEConnection({ deviceId, success: () { this.setData({ connectedDeviceId: deviceId }) wx.showToast({ title: 连接成功, icon: success }) // 连接成功后立即启用 notify等待重量数据推送 this.enableNotify(deviceId) } }) }, enableNotify(deviceId) { // 注意notify 必须在 connection established 后调用 wx.notifyBLECharacteristicValueChange({ state: true, deviceId, serviceId: 0000ffe0-0000-1000-8000-00805f9b34fb, // 自定义服务UUID characteristicId: 0000ffe1-0000-1000-8000-00805f9b34fb // 重量数据特征值UUID }) wx.onBLECharacteristicValueChange((res) { const buffer res.value const view new DataView(buffer) // HX711 输出为 24bit 有符号整数需补位转为32位 const rawValue (view.getUint8(0) 16) | (view.getUint8(1) 8) | view.getUint8(2) const weightGram (rawValue 0x800000) ? rawValue - 0x1000000 : rawValue // 校准系数 k0.0023实测得出单位克 const weightKg (weightGram * 0.0023).toFixed(3) this.updateWeightDisplay(weightKg) }) } })关键参数说明serviceId和characteristicId必须与蓝牙网关固件中定义的 UUID 严格一致notifyBLECharacteristicValueChange的state: true表示启用通知而非读取——因为重量数据是网关主动推送的流式数据非查询式响应。若误用wx.readBLECharacteristicValue将导致每次称重需手动触发彻底失去“自动”特性。3. 称重数据校准与分鱼逻辑从原始AD值到可销售标签称重不是读一个数字而是建立“传感器AD值 → 实际重量kg→ 鱼类等级 → 分拣动作”的映射链。HX711 模块输出的是24位ADC原始值其线性度受温度、供电电压、传感器形变影响极大。学生项目常直接用map(0, 16777215, 0, 10)粗暴换算导致同一条鱼三次称重误差超±150g。真正的校准必须包含零点漂移补偿、满量程标定、温度补偿三项。3.1 零点漂移动态补偿用滑动窗口消除环境干扰HX711 的零点并非固定值而是随温度缓慢漂移。本项目采用10秒滑动窗口均值法在无负载时持续更新零点基准// utils/weight-calibrator.js class WeightCalibrator { constructor() { this.zeroPoints [] // 存储最近10次空载AD值 this.windowSize 10 } updateZeroPoint(rawValue) { this.zeroPoints.push(rawValue) if (this.zeroPoints.length this.windowSize) { this.zeroPoints.shift() } } getZeroBaseline() { if (this.zeroPoints.length 0) return 0 const sum this.zeroPoints.reduce((a, b) a b, 0) return Math.round(sum / this.zeroPoints.length) } calculateWeight(rawValue) { const baseline this.getZeroBaseline() const netValue rawValue - baseline // 防止负值传感器噪声 if (netValue 100) return 0 // 单位换算实测k值0.0023 g/bit return (netValue * 0.0023).toFixed(3) } } // 在 weight 页面实例化 const calibrator new WeightCalibrator() // 每次收到新AD值时更新 wx.onBLECharacteristicValueChange((res) { const rawValue parseRawValue(res.value) // 解析函数见2.3节 calibrator.updateZeroPoint(rawValue) const weightKg calibrator.calculateWeight(rawValue) that.setData({ currentWeight: weightKg }) })为什么必须动态更新实验数据显示室温从25℃升至35℃时HX711零点漂移达±8500 AD值相当于±20g误差。静态零点校准仅在开机时有效无法应对分鱼作业中持续数小时的环境变化。3.2 分鱼等级判定规则基于重量区间与品种特征的复合决策“分鱼”不是简单四舍五入而是依据渔业标准如《GB/T 30891-2014 活鱼运输技术规范》设定多维阈值。本系统支持两种模式品种规格等级重量范围kg备注鲤鱼特级≥2.5鳞片完整、体色鲜亮鲤鱼一级1.8–2.49允许轻微擦伤鲤鱼二级1.2–1.79需单独包装草鱼特级≥3.0腹部无胀气草鱼一级2.2–2.99—草鱼二级1.5–2.19—判定逻辑封装在云函数weight-control中// cloudfunctions/weight-control/index.js 中新增判定逻辑 function classifyFish(weightKg, species) { const rules { carp: [ { level: 特级, min: 2.5, max: Infinity }, { level: 一级, min: 1.8, max: 2.49 }, { level: 二级, min: 1.2, max: 1.79 } ], grass-carp: [ { level: 特级, min: 3.0, max: Infinity }, { level: 一级, min: 2.2, max: 2.99 }, { level: 二级, min: 1.5, max: 2.19 } ] } const specRules rules[species] || rules.carp for (const rule of specRules) { if (weightKg rule.min weightKg rule.max) { return rule.level } } return 待复检 // 超出所有区间 } // 调用示例 const level classifyFish(2.35, carp) // 返回 一级业务细节等级判定结果会触发两个动作——① 小程序端播放对应等级提示音wx.playVoice② 自动生成带二维码的分鱼标签调用report-gen函数生成PDF含鱼种、重量、等级、时间戳、操作员ID。3.3 云数据库 schema 设计支撑分鱼溯源与报表统计称重数据必须结构化存储才能支撑后续的“哪条鱼卖给了谁”“今日特级鲤鱼占比”等业务查询。cloudfunctions/report-gen依赖的数据库集合fish_weight_records定义如下字段名类型说明索引_idString云开发自动生成ID—deviceIdString蓝牙设备ID✅speciesString鱼种carp/grass-carp/tilapia✅weightKgNumber净重kg保留3位小数✅levelString等级特级/一级/二级/待复检✅operatorIdString操作员微信openid✅timestampNumber时间戳毫秒✅labelUrlString生成的PDF标签URL—batchIdString批次号按日自动生成如20220815-001✅关键查询示例在report-gen函数中// 查询今日特级鲤鱼销售汇总 const todayStart new Date().setHours(0,0,0,0) const todayEnd new Date().setHours(23,59,59,999) const result await db.collection(fish_weight_records).aggregate() .match({ species: carp, level: 特级, timestamp: db.command.gte(todayStart).and(db.command.lte(todayEnd)) }) .group({ _id: null, totalWeight: db.command.sum($weightKg), totalCount: db.command.sum(1), avgWeight: db.command.avg($weightKg) }) .end()性能提示batchId字段用于快速定位某日全部数据避免全表扫描timestamp字段必须建索引否则万级数据下报表生成耗时超15秒。学生项目常忽略索引导致“导出报表卡死”。4. app.json 错误排查与云函数调试从报错信息反推配置缺陷当小程序启动报[app.json 文件内容错误]app.json:时90% 情况并非 JSON 语法错误而是app.json中声明的能力与实际代码调用不匹配。这类错误在微信开发者工具env: windows,mp,1.06.2209190; lib: 3.8.10中尤为高频需结合错误堆栈与配置项逐项验证。4.1 三类高频 app.json 错误及其修复路径错误现象根本原因修复操作验证方式app.json: requiredPrivateInfos is not arrayrequiredPrivateInfos字段值为字符串或对象非数组检查app.json中该字段是否为[getLocation, getPhoneNumber]格式不能写成getLocation删除miniprogram/app.json中该字段重新添加标准数组app.json: no page is definedpages数组为空或路径错误如pages/home/index但实际文件是pages/home/home.js确认pages数组中每个路径都存在对应.js/.wxml/.wxss/.json四件套在开发者工具中右键页面路径 → “在资源管理器中显示”app.json: permission scope.xxx is invalid声明了未在代码中使用的权限如写了scope.record但从未调用wx.startRecord删除app.json中未调用的权限声明或在对应页面onLoad中补全调用逻辑使用微信开发者工具“详情 → 项目设置 → 权限声明检测”特别注意env: windows,mp,1.06.2209190表示运行环境为 Windows 版开发者工具版本号 1.06.2209190。该版本对app.json的校验比真机更严格务必在提交前用真机扫码测试避免“工具报错但真机正常”的假象。4.2 云函数本地调试绕过网络延迟精准定位硬件交互瓶颈云函数部署后调试困难尤其涉及蓝牙指令下发时console.log输出滞后且无法查看中间状态。推荐使用cloud.callFunction的config参数强制走本地调试通道// 在小程序端临时修改调用方式上线前删除 wx.cloud.callFunction({ name: weight-control, data: { action: read, deviceId: dev_001 }, config: { env: local, // 强制本地调试 region: ap-guangzhou } }).then(res { console.log(本地调试结果:, res) }).catch(err { console.error(本地调试失败:, err) })此时需在本地启动云函数调试服务# 在 cloudfunctions/weight-control 目录执行 npm install npm run dev -- --port 8080然后在微信开发者工具中设置“云函数本地调试端口”为8080。这样所有weight-control调用将直连本地 Node.js 服务console.log实时输出且可加断点调试payload构造、数据库查询、指令编码全过程。4.3 蓝牙连接失败的终极排查清单当wx.getConnectedBluetoothDevices返回空数组按此顺序逐项确认手机系统层Android 12 需开启“附近设备”权限Settings → Apps → 小程序 → Permissions → Near Field CommunicationiOS 需开启“蓝牙”开关且未被其他App独占小程序代码层确认wx.openBluetoothAdapter成功回调后必须等待至少500ms再调用wx.startBluetoothDevicesDiscovery否则部分安卓机型返回空设备列表硬件层用 nRF Connect App 扫描同一设备确认其广播名localName是否为FishScale-XXXX且服务UUID0000ffe0-...可见云函数层检查device-sync函数日志确认网关是否成功上报心跳lastHeartbeat字段是否更新网络层若网关通过 WiFi 连接确认其 IP 是否在小程序request白名单中project.config.json的networkTimeout.request需设为 10000ms。经验技巧在pages/scan/scan.wxml中加入隐藏调试按钮长按10秒触发wx.openBluetoothAdapter强制重试并弹窗显示当前蓝牙适配器状态wx.getConnectedBluetoothDevices结果这是毕业设计答辩时最直观的故障演示方式。本文还有配套的精品资源点击获取
返回列表