上次说到总体的需求,按理来说应该由上至下分析一遍,把程序功能理清楚,再决定使用什么软件结构来完成程序的设计。为了说明软件结构时不显得太空,先从具体实现开始写吧。
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实现。