开发一个控制硬件的安卓app(2)——指令生成与状态解析
上次说到总体的需求,按理来说应该由上至下分析一遍,把程序功能理清楚,再决定使用什么软件结构来完成程序的设计。为了说明软件结构时不显得太空,先从具体实现开始写吧。
1 通讯协议介绍
如何用安卓app来控制设备呢?首先就要提及通讯协议这个概念了。我所使用的这套硬件设备有着一套自己的”密码本”,只要我将指令以符合”密码本”的格式发送给设备,设备就会按照给出的指令行动。这里的”密码本”就是上面提到的通讯协议。不过不同设备的具体指令信息或多或少有所不同,大家参考一下就好了。下面给出这一套设备的通讯协议格式,请注意指令使用16进制描述。
表1 通信协议基本格式
具体指令信息(共15位) | |||||
D0 ~ D8 | 0x2E | T1 | T2 | T3 | 0x0D 0x0A |
位置 | 说明 | ||||
D0 ~ D8 | 为信息位 | ||||
0x2E | 小数点,起分隔作用 | ||||
T1 | 发送方地址值,发送数据的站点地址,即源地址,指明由谁发出的数据 | ||||
T2 | 接收方地址值,接受数据的站点地址,即目的地址,指明数据是发给谁 | ||||
T3 | 为校验位,简单的数据校验,计算方法如下:D0...D8,T1,T2共11个数字的和,取其个位数,十六进制范围在0x30~0x39之间(数字0~9) | ||||
0x0D 0x0A | 为结束标志标识位,用回车换行表示命令结束 |
为了识别指令是从哪发到哪的,指令中使用T1与T2来标记发送方和接收方,地址定义如下:
表2 通讯协议中的地址
主站 | 工控机站点 | 0X30 |
从站 | 堆垛机 | 0X31 |
输送线 | 0X32 | |
AGV小车 | 0X33 | |
分拣线 | 0X34 | |
电子标签 | 0X35 | |
加工 | 0X36 | |
机器人1 | 0X37 | |
机器人2 | 0X38 | |
AGV小车2 | 0X39 |
比如说你想控制小车的动作,你的指令中T1为0X30,T2为0X33。而小车给你返回的状态信息中的T1为0X33,T2为0X30。
2 具体指令介绍
虽然看着上面的命令很复杂,但是自动分拣系统的指令非常简单,只有4个控制指令和4个返回状态,下面给出自动分拣系统的通讯协议,讲一讲怎么看懂这个通讯协议。
主站-->分拣线命令
T1=0x30,T2=0x34,D0、D1定义如下(其它位为0):
表3 分拣线命令
序 | D0取值 | 功能操作 | 说明 D1~D8取值 |
1 | 0x31 | 启动 | D1={0,1,2},其中: 0表示不动作 1表示分拣口1 2表示分拣口2 |
2 | 0x36 | 停止 | D1~D8全为0 |
D0:按表3的说法D0取0X31(16进制),也就是"1"(ascii码);
D1:按表3的说法D0取"0"(ascii码),也就是0X30(16进制);
D2-D8:看表3前面一句话,D0、D1定义如下(其它位为0),所以这几位都是0X30(16进制),也就是"0"(ascii码);
T0:按表1描述,固定为0X2E(16进制),对应为"."(ascii码);
T1:按表1和2,指令从上位机发送,所以该位为0x30(16进制),对应"0"(ascii码);
T2:和上面相同,值为0x34(16进制),对应"4"(ascii码);
T3:这是校验位,把前面除了T0的相加取个位,简单算一下就是1+0+0+0+0+0+0+0+0+0+4=5,那么就是"5"(ascii码),也就是0X35(16进制);
最后两位:按表1描述这是固定值0x0D 0x0A(16进制),也就是"\r\n"(ascii码),就是回车换行啦。很好,这样一条指令就出来了。
16进制表示0X31 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X2E 0X30 0X34 0X35 0X0D 0X0A
字符串表示"100000000.045\r\n"
对于本设备来说,两种表示都可以,不过ascii码不是所有16进制数都能表示,所以编程时最好使用16进制来操作指令。
大家可以用其它指令来练习一下,是不是看懂了就很简单呢?
具体实现起来就是这样的:
表4 具体分拣线指令
操作 | 指令 |
启动 | 0X31 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X2E 0X30 0X34 0X35 0X0D 0X0A 或”100000000.045\r\n” |
向分拣口1运送 | 0X31 0X31 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X2E 0X30 0X34 0X36 0X0D 0X0A 或”110000000.046\r\n” |
向分拣口2运送 | 0X31 0X32 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X2E 0X30 0X34 0X37 0X0D 0X0A 或”120000000.047\r\n” |
停止 | 0X36 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X30 0X2E 0X30 0X34 0X30 0X0D 0X0A 或”600000000.040\r\n” |
T1=0x34,T2=0x30,D0定义如下(其它位为0):
序 | D0取值 | 状态说明 |
1 | 0x31 | 货物分向分拣口1 |
2 | 0x32 | 货物分向分拣口2 |
3 | 0x36 | 分拣系统已停止运行 |
4 | 0x38 | 分拣系统空闲,完成一次分拣自动变为空闲 |
这边和上面指令的分析差不多,只是地址交换了一下。状态反馈就是下位机把当前自己的状态返回给上位机,便于上位机进行协调。
3 指令生成与状态解析的Java实现
下面在eclipse中实现一下这个指令的生成与状态的解析
public class AutoSortLine { // 用于存放15位指令 protected byte[] cmd = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x30, 0x34, 0x00, 0x0D, 0x0A }; protected boolean isBusy = false; // 构造方法初始化 public AutoSortLine() { for (int i = 0; i < 9; i++) { cmd[i] = 0x30; } } // 启动 public byte[] start() { cmd[0] = 0x31; cmd[1] = 0x30; countCheck(); return cmd; } // 停止 public byte[] stop() { cmd[0] = 0x36; cmd[1] = 0x30; countCheck(); return cmd; } // 1口出 public byte[] out1() { cmd[0] = 0x31; cmd[1] = 0x31; countCheck(); return cmd; } // 2口出 public byte[] out2() { cmd[0] = 0x31; cmd[1] = 0x32; countCheck(); return cmd; } /** * 翻译通信协议 * * @param msg * 收到的状态信息 * @return 返回应显示的状态信息 */ public String receive(String msg) { switch (msg.replaceAll(" ", "")) { case "100000000.405\r\n": // 注意\r\n isBusy = true; return "货物分向分拣口1"; case "200000000.406\r\n": return "货物分向分拣口2"; case "600000000.400\r\n": return "分拣系统已停止运行"; case "800000000.402\r\n": isBusy = false; return "分拣系统空闲"; default: isBusy = false; break; } return ""; } // 查询设备是否繁忙 public boolean isBusy() { return isBusy; } // 计算校验位 public void countCheck() { byte check = 0; for (int i = 0; i < 12; i++) { if (i != 9) { check = (byte) (check + cmd[i] - 0x30); } } cmd[12] = (byte) ((check % 10) + 0x30); } // 用来测试指令生成和状态解析的效果 public static void main(String[] args) { AutoSortLine asl = new AutoSortLine(); System.out.println("指令生成,请注意每个指令结束有额外的换行"); System.out.println("strat code is " + new String(asl.start()).equals("100000000.045\r\n")); System.out.println("start:" + new String(asl.start())); System.out.println("stop code is " + new String(asl.stop()).equals("600000000.040\r\n")); System.out.println("stop:" + new String(asl.stop())); System.out.println("out1 code is " + new String(asl.out1()).equals("110000000.046\r\n")); System.out.println("out1:" + new String(asl.out1())); System.out.println("out2 code is " + new String(asl.out2()).equals("120000000.047\r\n")); System.out.println("out2:" + new String(asl.out2())); System.out.println("状态解析,请注意每个状态结束有额外的换行"); System.out.println(asl.receive("100000000.405\r\n")); System.out.println(asl.receive("200000000.406\r\n")); System.out.println(asl.receive("600000000.400\r\n")); System.out.println(asl.receive("800000000.402\r\n")); } }
运行后输出
指令生成,请注意每个指令结束有额外的换行 strat code is true start:100000000.045 stop code is true stop:600000000.040 out1 code is true out1:110000000.046 out2 code is true out2:120000000.047 状态解析,请注意每个状态结束有额外的换行 货物分向分拣口1 货物分向分拣口2 分拣系统已停止运行 分拣系统空闲这就完成了指令生成与状态解析的Java实现。