安卓工控IO串口/继电器门锁控制:精准开门、硬件状态实时回传方案

📅 2026/7/11 7:34:16
安卓工控IO串口/继电器门锁控制:精准开门、硬件状态实时回传方案
安卓工控IO串口/继电器门锁控制精准开门、硬件状态实时回传方案大家好我是黒漂技术佬。从今天开始进入第四篇章硬件门锁控制。开门是无人售货柜最核心的动作。说起来简单——给个高电平继电器吸合门就开了。但真要做到稳定可靠里面的门道可不少。串口通信异常怎么办继电器粘住了怎么办门开了没开怎么确认今天咱们把门锁控制从底层到上层全讲清楚。一、售货柜门锁的硬件方案先给新手科普一下常见的门锁控制方案。常见的控制方式方案原理优点缺点GPIO直连主控GPIO直接驱动门锁简单成本低电流小只能驱动小型锁继电器控制GPIO控制继电器继电器控制门锁电源能驱动大功率锁隔离安全多一个继电器体积大一点串口门锁门锁自带单片机通过串口发指令控制功能多状态反馈全成本高协议各家不一样485总线多门锁通过485总线挂接适合多门柜子布线少协议复杂需要485转换主流方案继电器控制 门磁反馈性价比最高稳定性也够。典型的门锁硬件结构安卓主控板 ├── GPIO输出 → 继电器 → 电磁锁电源 → 电磁锁 └── GPIO输入 ← 门磁传感器 ← 门的物理状态控制通路APP发指令 → GPIO输出 → 继电器吸合 → 锁通电开锁反馈通路门开/关 → 门磁状态变化 → GPIO输入变化 → APP检测到二、串口通信基础很多门锁/IO扩展板是通过串口控制的先讲串口基础。Android串口操作Android上操作串口一般用 android-serialport-api 这个库publicclassSerialPortManager{privateSerialPortserialPort;privateInputStreaminputStream;privateOutputStreamoutputStream;privateReadThreadreadThread;/** * 打开串口 */publicbooleanopen(Stringdevice,intbaudRate){try{serialPortnewSerialPort(newFile(device),baudRate,0);inputStreamserialPort.getInputStream();outputStreamserialPort.getOutputStream();// 启动读线程readThreadnewReadThread();readThread.start();returntrue;}catch(Exceptione){Log.e(Serial,打开串口失败,e);returnfalse;}}/** * 发送指令 */publicsynchronizedbooleansend(byte[]data){try{outputStream.write(data);outputStream.flush();returntrue;}catch(IOExceptione){Log.e(Serial,发送失败,e);returnfalse;}}/** * 读数据线程 */privateclassReadThreadextendsThread{Overridepublicvoidrun(){byte[]buffernewbyte[1024];while(!isInterrupted()){try{intsizeinputStream.read(buffer);if(size0){byte[]datanewbyte[size];System.arraycopy(buffer,0,data,0,size);onDataReceived(data);}}catch(IOExceptione){break;}}}}}串口协议设计串口协议各家不一样但基本框架差不多帧头(1字节) 地址(1字节) 命令(1字节) 数据长度(1字节) 数据(N字节) 校验(1字节)举个例子某品牌门锁控制板协议开门指令AA 01 01 01 01 XX - AA帧头 - 01设备地址 - 01命令开门 - 01数据长度 - 01门号 - XX校验和 状态查询AA 01 02 00 XX 状态返回AA 01 82 04 [门1状态][门2状态][门3状态][门4状态] XX校验和计算publicstaticbytecalcChecksum(byte[]data,intlen){bytesum0;for(inti0;ilen;i){sumdata[i];}returnsum;}三、GPIO控制继电器如果是直接用GPIO控制继电器更简单。GPIO操作方式安卓工控板的GPIO一般通过 sysfs 操作/sys/class/gpio/ ├── export // 导出GPIO ├── unexport // 取消导出 └── gpioXXX/ ├── direction // in/out输入还是输出 └── value // 0/1电平值GPIO操作工具类publicclassGpioUtils{/** * 导出GPIO */publicstaticbooleanexportGpio(intgpio){returnwriteFile(/sys/class/gpio/export,String.valueOf(gpio));}/** * 设置方向 */publicstaticbooleansetDirection(intgpio,booleanisOutput){Stringpath/sys/class/gpio/gpiogpio/direction;returnwriteFile(path,isOutput?out:in);}/** * 设置输出电平 */publicstaticbooleansetValue(intgpio,intvalue){Stringpath/sys/class/gpio/gpiogpio/value;returnwriteFile(path,String.valueOf(value));}/** * 读取输入电平 */publicstaticintgetValue(intgpio){Stringpath/sys/class/gpio/gpiogpio/value;StringvalreadFile(path);return1.equals(val)?1:0;}privatestaticbooleanwriteFile(Stringpath,Stringcontent){try{FileWriterwriternewFileWriter(path);writer.write(content);writer.close();returntrue;}catch(IOExceptione){returnfalse;}}}注意GPIO操作需要root权限或者系统权限普通APP做不了。四、门锁控制器封装把底层控制封装起来上层业务不用关心是串口还是GPIO。统一接口publicinterfaceDoorController{/** * 打开指定门 * param doorNo 门号从1开始 * param duration 开锁持续时间毫秒 */booleanopenDoor(intdoorNo,intduration);/** * 查询门的状态 * return 0关 1开 -1未知 */intgetDoorStatus(intdoorNo);/** * 注册门状态变化监听 */voidaddDoorStatusListener(DoorStatusListenerlistener);}继电器实现publicclassRelayDoorControllerimplementsDoorController{// 每个门对应的GPIO编号privateint[]relayGpios{/* GPIO1, GPIO2, GPIO3... */};// 每个门对应的门磁GPIOprivateint[]sensorGpios{/* GPIO5, GPIO6, GPIO7... */};OverridepublicbooleanopenDoor(intdoorNo,intduration){intgpiorelayGpios[doorNo-1];// 输出高电平继电器吸合开锁GpioUtils.setValue(gpio,1);// 持续duration毫秒后断电落锁newHandler(Looper.getMainLooper()).postDelayed(()-{GpioUtils.setValue(gpio,0);},duration);returntrue;}OverridepublicintgetDoorStatus(intdoorNo){intgpiosensorGpios[doorNo-1];returnGpioUtils.getValue(gpio);}}串口实现publicclassSerialDoorControllerimplementsDoorController{privateSerialPortManagerserial;OverridepublicbooleanopenDoor(intdoorNo,intduration){// 组装开门指令byte[]cmdbuildOpenDoorCommand(doorNo,duration);returnserial.send(cmd);}OverridepublicintgetDoorStatus(intdoorNo){// 先发查询指令byte[]queryCmdbuildStatusQueryCommand();serial.send(queryCmd);// 等响应这里简化了实际要用回调// ...returndoorStatusCache[doorNo-1];}}五、门状态实时回传光发指令不行得知道门到底开了没、关了没。轮询方式定时查询门状态publicclassDoorStatusPoller{privatestaticfinallongPOLL_INTERVAL1000;// 1秒查一次privateint[]lastStatus;privateHandlerhandler;publicvoidstart(){handlernewHandler(Looper.getMainLooper());lastStatusnewint[doorCount];Arrays.fill(lastStatus,-1);pollRunnable.run();}privateRunnablepollRunnablenewRunnable(){Overridepublicvoidrun(){for(inti1;idoorCount;i){intstatuscontroller.getDoorStatus(i);if(status!lastStatus[i-1]lastStatus[i-1]!-1){// 状态变了通知监听者notifyStatusChanged(i,lastStatus[i-1],status);}lastStatus[i-1]status;}handler.postDelayed(this,POLL_INTERVAL);}};}中断方式GPIO输入中断有些板子支持GPIO中断状态变了主动通知比轮询效率高// 通过sysfs的edge文件配置中断触发方式// none: 不触发// rising: 上升沿触发// falling: 下降沿触发// both: 双边沿触发publicstaticbooleansetEdge(intgpio,Stringedge){Stringpath/sys/class/gpio/gpiogpio/edge;returnwriteFile(path,edge);}然后用epoll或者文件监听的方式等待变化不用轮询。状态变化上报云端门状态变了要及时告诉云端privatevoidnotifyStatusChanged(intdoorNo,intoldStatus,intnewStatus){StringstatusStrnewStatus1?open:closed;JSONObjectpayloadnewJSONObject();payload.put(door_no,doorNo);payload.put(status,statusStr);payload.put(timestamp,System.currentTimeMillis());MqttManager.publish(device/DeviceInfo.getDeviceNo()/door/status,payload.toString(),1// QoS1保证送达);}六、开门确认机制发了开门指令怎么确认门真的开了开门成功的判定开门指令发出去之后在规定时间内检测到门状态从关变开才算成功publicclassOpenDoorResultChecker{privatestaticfinallongOPEN_TIMEOUT5000;// 5秒内没开就算失败/** * 开门并确认结果 */publicOpenResultopenDoorWithConfirm(intdoorNo,intduration){// 1. 记录开门前的状态intbeforeStatuscontroller.getDoorStatus(doorNo);// 2. 发送开门指令booleancmdSentcontroller.openDoor(doorNo,duration);if(!cmdSent){returnOpenResult.FAILED_CMD_SEND;}// 3. 等待门打开longstartTimeSystem.currentTimeMillis();while(System.currentTimeMillis()-startTimeOPEN_TIMEOUT){intcurrentStatuscontroller.getDoorStatus(doorNo);if(currentStatus1beforeStatus0){// 检测到门从关变开了returnOpenResult.SUCCESS;}try{Thread.sleep(100);}catch(InterruptedExceptione){break;}}// 超时了门没开returnOpenResult.FAILED_TIMEOUT;}}开门结果的几种状态状态说明后续处理SUCCESS开门成功正常上报FAILED_CMD_SEND指令发送失败告警重试FAILED_TIMEOUT指令发了但门没开告警可能锁坏了或者被顶住了FAILED_STUCK门开了但关不上严重告警需要人工处理七、多品牌门锁适配实际项目中可能用好几种门锁做个适配层。适配器模式publicinterfaceDoorLockAdapter{booleanopenDoor(intdoorNo,intduration);intgetDoorStatus(intdoorNo);voidinit();}// 品牌A的门锁publicclassBrandALockAdapterimplementsDoorLockAdapter{...}// 品牌B的门锁publicclassBrandBLockAdapterimplementsDoorLockAdapter{...}// 工厂类publicclassDoorLockFactory{publicstaticDoorLockAdaptercreate(Stringbrand){switch(brand){casebrand_a:returnnewBrandALockAdapter();casebrand_b:returnnewBrandBLockAdapter();default:thrownewIllegalArgumentException(不支持的门锁品牌);}}}配置里指定门锁品牌代码自动加载对应的适配器换门锁不用改业务代码。八、踩坑经验坑1继电器粘住门一直开着现象发了关门指令但门还是开的继电器触点粘住了原因继电器质量差或者电流过大触点熔焊解决用好一点的继电器软件上加检测发现长时间开锁自动告警甚至反极性脉冲一下坑2门磁抖动状态频繁跳变现象门明明关着状态一会儿开一会儿关原因门磁接触不良机械振动导致电平跳变解决加防抖连续N次检测到状态一致才算真变了坑3串口数据粘包/分包现象串口收到的数据不对解析失败原因串口是流协议一次read不一定收到完整一帧解决自己做帧缓存找帧头帧尾拼完整帧再解析坑4GPIO权限问题现象APP打开正常操作GPIO失败原因sysfs文件权限不够或者GPIO没导出解决系统启动脚本里提前导出GPIO并改权限或者APP用系统签名九、总结门锁控制的核心控制可靠状态可知。底层控制串口或GPIO根据硬件方案选状态反馈轮询或中断确保知道门的真实状态开门确认发指令状态检测双重确认不能发了就当成功适配层设计多品牌门锁统一接口业务代码不感知底层异常检测继电器粘住、门磁抖动这些常见问题要提前预防门锁是售货柜的核心执行部件直接关系到交易安全和用户体验这块一定要做扎实。我是黒漂技术佬专注无人零售物联网实战。下一篇讲《门锁异常自愈》开门超时、门锁卡死、误触发这些异常情况怎么自动处理。有门锁调试的坑欢迎私信交流大家一起踩坑~