前言
一个超市里常用的条码秤对接经历
tcp/ip
该条码秤使用 tcp/ip 协议进行通信,在 java 中,tcp/ip 通信被封装成了 Socket 类,所以使用起来还算简单。
以下是一个简单的 socket 示例
try {
socket = new Socket();
InetSocketAddress socketAddress = new InetSocketAddress("192.168.3.150", 4001);
socket.connect(socketAddress, 10 * 1000);
socket.setSoTimeout(5 * 1000);
if (socket.isConnected()) {
// 向 socket 服务器写入数据
byte[] bytes = {0x21, 0x30, 0x49, 0x41, 0x0d, 0x0a, 0x03};
socket.getOutputStream().write(bytes);
// 接收 socket 服务器返回的数据
InputStreamReader ipr = new InputStreamReader(socket.getInputStream());
bufferedReader = new BufferedReader(ipr);
String str = bufferedReader.readLine();
}
} catch (IOException error) {
error.printStackTrace();
}
建议先实例化 socket 再进行连接,因为这样可以通过 setSoTimeout 函数设置超时时间,避免线程阻塞,同时 socket 的连接不能在主线程中进行,否则会报错。
发送数据到条码秤
大华条码秤 socket 服务器的端口为 4001,需要给大华秤盘设置一个固定的 ip 地址。
设置方式如下:
按下功能键 -> 输入9002 -> 按下确认 -> 开始输入 ip 地址,输入 . 需要按下去皮键 -> 在输入主机地址后按下去皮键完成设置
在大华的文档中标明,每个指令都得以 0x0d, 0x0a, 0x03 为结束符,所以可以封装一个函数,如下:
private String sendCommand(byte[] bytes) {
byte[] suffix = {0x0d, 0x0a, 0x03};
try {
if (socket != null && socket.isConnected() && socket.getOutputStream() != null) {
byte[] command = byteMerger(bytes, suffix);
socket.getOutputStream().write(command);
return "SEND_SUCCESS";
} else {
return "NOT_CONNECT";
}
} catch (IOException e) {
e.printStackTrace();
return "SEND_FAIL";
}
}
private static byte[] byteMerger(byte[] bt1, byte[] bt2){
byte[] bt3 = new byte[bt1.length + bt2.length];
System.arraycopy(bt1, 0, bt3, 0, bt1.length);
System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
return bt3;
}
首先发送清除 plu 和报表指令到条码秤来初始化条码秤。
public boolean clearData() {
if (socket != null && socket.isConnected()) {
try {
byte[] bytes = {0x21, 0x30, 0x49, 0x41};
sendCommand(bytes)
byte[] bytes2 = {0x21, 0x30, 0x48, 0x41};
sendCommand(bytes2)
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
return false;
}
}
这里有一点需要注意,因为初始化报表会耗费一定的时间,所以不能在清除报表后立马同步商品数据,否则会导致同步失败。
关于商品数据传入文档描述如下:
这里针对几个关键信息作出说明:
- plu 条码秤中货品的助记码,可在条码秤使用快捷键或录入 plu 码进行搜索
- code 商品代码,长度7位,用于条码秤打印条码标签后在系统数据库中进行匹配,第一位固定为店名号最后一位,意味着再怎样进行设置打印后都为店名号
- price 商品价格,单位为分,长度6位,不足需补位0
- goodsName 商品名,需要转换成区位码转换函数如下
private static String bytes2HexString(byte b) {
return bytes2HexString(new byte[] { b });
}
private static String bytes2HexString(byte[] b) {
String ret = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
ret += hex.toUpperCase();
}
return ret;
}
private String StringToAreaByteCode(String str) {
String result = "";
for (int i = 0; i < str.length(); i++) {
result += charToAreaByteCode(str.charAt(i));
}
return result;
}
private String charToAreaByteCode(char str) {
byte[] bs = new byte[0];
String singleStr = String.valueOf(str);
try {
bs = singleStr.getBytes("GB2312");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (bs.length == 2) { // 正常字符
String s = "";
String t = "";
for (int i = 0; i < bs.length; i++) {
int a = Integer.parseInt(bytes2HexString(bs[i]), 16);
t = (a - 0x80 - 0x20) + "";
if(t.length() == 1){
t = 0 + t;
}
s += t;
}
return s;
} else if (bs.length == 1) { // ascii 码
String qw = Integer.parseInt(bytes2HexString(bs[0]), 16) - 0x21 + 301 + "";
while (qw.length() < 4) {
qw = "0" + qw;
}
return qw;
} else {
return "";
}
}
发送商品数据代码如下:
public boolean setGoods(BarCodeScaleGoods goods) {
String plu = goods.pluCode;
String code = goods.code;
String price = String.valueOf(new Double(goods.price * 100).intValue());
String goodsName = StringToAreaByteCode(goods.goodsName);
String unit = goods.unit;
while (plu.length() < 4) {
plu = '0' + plu;
}
while (price.length() < 6) {
price = '0' + price;
}
String goodsInfo = String.format("!0V%sA%s%s%s00000099901010000000000000000000000000000000000000000000000B%sCDE", plu, code, price, unit, goodsName);
byte[] bytes = goodsInfo.getBytes();
String result = sendCommand(bytes);
if (result.equals("SEND_SUCCESS")) {
try {
InputStreamReader ipr = new InputStreamReader(socket.getInputStream());
bufferedReader = new BufferedReader(ipr);
String str = bufferedReader.readLine();
if (str.length() > 0) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
return false;
}
}
因为发送商品数据的指令每个字段没有明确的标识位,大华在解析指令时是以下标来获取信息的,所以得保证每个字段的信息字节数是固定的,所以得对动态的信息进行补位,代码如下:
while (plu.length() < 4) {
plu = '0' + plu;
}
while (price.length() < 6) {
price = '0' + price;
}
需在空字节上填充0。
在完成发送后,判断条码秤是否成功录入商品的条件是条码秤有没有返回信息,所以就有以下代码来判断是否录入成功。
if (result.equals("SEND_SUCCESS")) {
try {
InputStreamReader ipr = new InputStreamReader(socket.getInputStream());
bufferedReader = new BufferedReader(ipr);
String str = bufferedReader.readLine();
if (str.length() > 0) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
return false;
}
最后需要同步热键设置,用于快捷键录入商品,文档描述如下:
代码如下:
public void setHotKey() {
String str = "!0L00A";
for (int i = 1; i <= 71; i++) {
if (String.valueOf(i).length() == 1) {
str += "000" + i;
} else if (String.valueOf(i).length() == 2) {
str += "00" + i;
}
}
str += "B";
byte[] bytes = str.getBytes();
sendCommand(bytes);
}