最近公司做零售項目,接了電子秤,條碼秤,打印機等一堆的硬件設備,其它設備網上都能找到相應資料這裏就不在重述,這篇文章主要講的是條碼秤連接以及向秤發送命令與接收回應。
1.條碼秤的連接:
條碼秤通訊採用的是ping ip的方式,我們就用socket通訊來建立連接,我socket通訊偷懶採用的是別人的庫OkSocket 引用地址是:implementation’com.tonystark.android:socket:latest.release’ 因爲條碼秤沒得啥子固定包頭這些,所以是直接將lib下載下來導入進了項目中修改了ReaderImpl文件
import com.xuhao.didi.core.exceptions.ReadException;
import com.xuhao.didi.core.iocore.interfaces.IOAction;
import com.xuhao.didi.core.pojo.OriginalData;
import com.xuhao.didi.core.protocol.IReaderProtocol;
import com.xuhao.didi.core.utils.BytesUtils;
import com.xuhao.didi.core.utils.SLog;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by xuhao on 2017/5/31.
*/
public class ReaderImpl extends AbsReader {
// private ByteBuffer mRemainingBuf;
//
// @Override
// public void read() throws RuntimeException {
// OriginalData originalData = new OriginalData();
// IReaderProtocol headerProtocol = mOkOptions.getReaderProtocol();
// int headerLength = headerProtocol.getHeaderLength();
// ByteBuffer headBuf = ByteBuffer.allocate(headerLength);
// headBuf.order(mOkOptions.getReadByteOrder());
// try {
// if (mRemainingBuf != null) {
// mRemainingBuf.flip();
// int length = Math.min(mRemainingBuf.remaining(), headerLength);
// headBuf.put(mRemainingBuf.array(), 0, length);
// if (length < headerLength) {
// //there are no data left
// mRemainingBuf = null;
// readHeaderFromChannel(headBuf, headerLength - length);
// } else {
// mRemainingBuf.position(headerLength);
// }
// } else {
// readHeaderFromChannel(headBuf, headBuf.capacity());
// }
// originalData.setHeadBytes(headBuf.array());
// if (SLog.isDebug()) {
// SLog.i("read head: " + BytesUtils.toHexStringForLog(headBuf.array()));
// }
// int bodyLength = headerProtocol.getBodyLength(originalData.getHeadBytes(), mOkOptions.getReadByteOrder());
// if (SLog.isDebug()) {
// SLog.i("need read body length: " + bodyLength);
// }
// if (bodyLength > 0) {
// if (bodyLength > mOkOptions.getMaxReadDataMB() * 1024 * 1024) {
// throw new ReadException("Need to follow the transmission protocol.\r\n" +
// "Please check the client/server code.\r\n" +
// "According to the packet header data in the transport protocol, the package length is " + bodyLength + " Bytes.\r\n" +
// "You need check your <ReaderProtocol> definition");
// }
// ByteBuffer byteBuffer = ByteBuffer.allocate(bodyLength);
// byteBuffer.order(mOkOptions.getReadByteOrder());
// if (mRemainingBuf != null) {
// int bodyStartPosition = mRemainingBuf.position();
// int length = Math.min(mRemainingBuf.remaining(), bodyLength);
// byteBuffer.put(mRemainingBuf.array(), bodyStartPosition, length);
// mRemainingBuf.position(bodyStartPosition + length);
// if (length == bodyLength) {
// if (mRemainingBuf.remaining() > 0) {//there are data left
// ByteBuffer temp = ByteBuffer.allocate(mRemainingBuf.remaining());
// temp.order(mOkOptions.getReadByteOrder());
// temp.put(mRemainingBuf.array(), mRemainingBuf.position(), mRemainingBuf.remaining());
// mRemainingBuf = temp;
// } else {//there are no data left
// mRemainingBuf = null;
// }
// //cause this time data from remaining buffer not from channel.
// originalData.setBodyBytes(byteBuffer.array());
// mStateSender.sendBroadcast(IOAction.ACTION_READ_COMPLETE, originalData);
// return;
// } else {//there are no data left in buffer and some data pieces in channel
// mRemainingBuf = null;
// }
// }
// readBodyFromChannel(byteBuffer);
// originalData.setBodyBytes(byteBuffer.array());
// } else if (bodyLength == 0) {
// originalData.setBodyBytes(new byte[0]);
// if (mRemainingBuf != null) {
// //the body is empty so header remaining buf need set null
// if (mRemainingBuf.hasRemaining()) {
// ByteBuffer temp = ByteBuffer.allocate(mRemainingBuf.remaining());
// temp.order(mOkOptions.getReadByteOrder());
// temp.put(mRemainingBuf.array(), mRemainingBuf.position(), mRemainingBuf.remaining());
// mRemainingBuf = temp;
// } else {
// mRemainingBuf = null;
// }
// }
// } else if (bodyLength < 0) {
// throw new ReadException(
// "read body is wrong,this socket input stream is end of file read " + bodyLength + " ,that mean this socket is disconnected by server");
// }
// mStateSender.sendBroadcast(IOAction.ACTION_READ_COMPLETE, originalData);
// } catch (Exception e) {
// ReadException readException = new ReadException(e);
// throw readException;
// }
// }
//
// private void readHeaderFromChannel(ByteBuffer headBuf, int readLength) throws IOException {
// for (int i = 0; i < readLength; i++) {
// byte[] bytes = new byte[1];
// int value = mInputStream.read(bytes);
// if (value == -1) {
// throw new ReadException(
// "read head is wrong, this socket input stream is end of file read " + value + " ,that mean this socket is disconnected by server");
// }
// headBuf.put(bytes);
// }
// }
//
// private void readBodyFromChannel(ByteBuffer byteBuffer) throws IOException {
// while (byteBuffer.hasRemaining()) {
// try {
// byte[] bufArray = new byte[mOkOptions.getReadPackageBytes()];
// int len = mInputStream.read(bufArray);
// if (len == -1) {
// break;
// }
// int remaining = byteBuffer.remaining();
// if (len > remaining) {
// byteBuffer.put(bufArray, 0, remaining);
// mRemainingBuf = ByteBuffer.allocate(len - remaining);
// mRemainingBuf.order(mOkOptions.getReadByteOrder());
// mRemainingBuf.put(bufArray, remaining, len - remaining);
// } else {
// byteBuffer.put(bufArray, 0, len);
// }
// } catch (Exception e) {
// throw e;
// }
// }
// if (SLog.isDebug()) {
// SLog.i("read total bytes: " + BytesUtils.toHexStringForLog(byteBuffer.array()));
// SLog.i("read total length:" + (byteBuffer.capacity() - byteBuffer.remaining()));
// }
// }
//接收byte數組
@Override
public void read() throws RuntimeException {
OriginalData originalData = new OriginalData();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
while (true) {
byte[] buff = new byte[1024 * 5];
int len = mInputStream.read(buff);
bos.write(buff, 0, len);
originalData.setBodyBytes(bos.toByteArray());
mStateSender.sendBroadcast(IOAction.ACTION_READ_COMPLETE, originalData);
bos.reset();
}
} catch (Exception e) {
ReadException readException = new ReadException(e);
throw readException;
}
}
}
我這裏將socket封裝成了單例:
public class SocketUtil {
private static final String TAG = "liluo";
private static class InstanceHolder {
public static SocketUtil sManager = new SocketUtil();
}
public static SocketUtil instance() {
return SocketUtil.InstanceHolder.sManager;
}
private SocketUtil() {
}
public IConnectionManager manager;
public IConnectionManager open(String ipAddress, int port) {
Logger.e("liluo", "開始連接" + ipAddress + "port:" + port);
close();
// final Handler handler = new Handler();
//連接參數設置(IP,端口號),這也是一個連接的唯一標識,不同連接,該參數中的兩個值至少有其一不一樣
ConnectionInfo info = new ConnectionInfo(ipAddress, port);
//調用OkSocket,開啓這次連接的通道,拿到通道Manager
// OkSocketOptions mOkOptions = new OkSocketOptions.Builder()
// .setCallbackThreadModeToken(new OkSocketOptions.ThreadModeToken() {
// @Override
// public void handleCallbackEvent(ActionDispatcher.ActionRunnable runnable) {
// handler.post(runnable);
// }
// })
// .build();
manager = OkSocket.open(info);
//註冊Socket行爲監聽器,SocketActionAdapter是回調的Simple類,其他回調方法請參閱類文檔
manager.registerReceiver(socketActionAdapter);
//調用通道進行連接
manager.connect();
return manager;
}
public void sendData(String content) {
if (manager == null) {
return;
}
if (!manager.isConnect()) {
MyToast.showLong("連接斷開");
} else {
if (TextUtils.isEmpty(content)) {
return;
}
MsgDataBean msgDataBean = new MsgDataBean(content);
manager.send(msgDataBean);
}
}
/**
* 關閉socket
*/
public void close() {
if (manager != null) {
manager.disconnect();
manager.unRegisterReceiver(socketActionAdapter);
manager = null;
}
}
private SocketActionAdapter socketActionAdapter = new SocketActionAdapter() {
@Override
public void onSocketConnectionSuccess(ConnectionInfo info, String action) {
Logger.e("liluo", "連接信息:" + new Gson().toJson(info));
EvenBusBean evenBusBean = new EvenBusBean();
evenBusBean.setTypes(10);
evenBusBean.setTmcState(new TmcState(1));
EventBus.getDefault().post(evenBusBean);
}
@Override
public void onSocketDisconnection(ConnectionInfo info, String action, Exception e) {
if (e != null) {
Logger.e(TAG, "異常斷開(Disconnected with exception):" + e.getMessage());
} else {
Logger.e(TAG, "正常斷開(Disconnect Manually)");
}
EvenBusBean evenBusBean = new EvenBusBean();
evenBusBean.setTypes(10);
evenBusBean.setTmcState(new TmcState(3));
EventBus.getDefault().post(evenBusBean);
}
@Override
public void onSocketConnectionFailed(ConnectionInfo info, String action, Exception e) {
Logger.e(TAG, "連接失敗(Connecting Failed)");
EvenBusBean evenBusBean = new EvenBusBean();
evenBusBean.setTypes(10);
evenBusBean.setTmcState(new TmcState(2));
EventBus.getDefault().post(evenBusBean);
}
@Override
public void onSocketReadResponse(ConnectionInfo info, String action, OriginalData data) {
String str = new String(data.getBodyBytes(), Charset.forName("utf-8"));
Logger.e("liluo", "條碼秤接收數據:" + str);
EvenBusBean evenBusBean = new EvenBusBean();
evenBusBean.setTypes(10);
evenBusBean.setTmcState(new TmcState(4, str));
EventBus.getDefault().post(evenBusBean);
}
@Override
public void onSocketWriteResponse(ConnectionInfo info, String action, ISendable data) {
String str = new String(data.parse(), Charset.forName("utf-8"));
// Logger.e(TAG, "條碼秤發送數據:" + str);
}
@Override
public void onPulseSend(ConnectionInfo info, IPulseSendable data) {
String str = new String(data.parse(), Charset.forName("utf-8"));//心跳
}
};
記得連接條碼秤之前需要對條碼秤設置ip,只有當條碼秤與安卓設備處於同一網絡下才行,至於一些動態分配ip,ip判重等這裏就不多說,根據公司業務自行寫業務邏輯,當然後期有什麼問題也可以留言,繼續:SocketUtil.instance().open(“ip地址”, "端口");
連接條碼秤。
2.發送商品信息到條碼秤
因爲網上沒有條碼秤相關文檔,所以發送命令全靠監聽解碼得出,解碼不易,喜歡的給個贊哈!
以下是監聽廠家軟件發送商品到條碼秤得到的命令:
0000: 21 30 56 30 30 30 31 41 30 30 30 35 30 30 31 31 !0V 0001 A 0005001 1
0010: 30 30 30 31 31 30 30 30 30 30 30 30 30 30 30 30 00011 00000000000
0020: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000
0030: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000
0040: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000
0050: 30 42 33 38 32 37 32 35 39 31 30 33 31 39 43 44 0B382725910319CD
0060: 45 0D 0A
1、解包說明
!0V
0003//plu號四位
A
0000654//商品編碼7位
000125//單價1.25元6位
1//計價模式:0 計重 1計件 2定重
000000
999//質保天數最大999
00//店號
000000000000000(15)
02531//皮重02.531公斤沒有5個0(皮重不能超過10公斤)
28個0
B
382725910319 //逐個字區位碼 內容:蘋果3
CDE
這裏面所有的漢字特殊符號等均需要用區位碼,下面給出漢字轉區位碼工具類:
/**
* 獲取完整的區位碼
*
* @param name 原始字符串
* @return 區位碼
*/
public static String getTotalAreaCode(String name) {
StringBuilder stringBuilder = new StringBuilder();
char[] nameChar = name.toCharArray();
for (char c : nameChar) {
if (isChinese(c))
stringBuilder.append(toAreaCode(String.valueOf(c), true));
else
stringBuilder.append(toAreaCode(String.valueOf(c), false));
}
String code = stringBuilder.toString();
if (code.contains("-")) {
Log.e("liluo", name + "的區位碼存在非法字符:" + code);
return "";
} else {
Log.d("liluo", name + "的區位碼:" + code);
return code;
}
}
/**
* 按照是否是中文字符分類獲取區位碼
*
* @param word 原始值
* @param isChina 是否是中文字符
* @return 區位碼
*/
public static String toAreaCode(String word, boolean isChina) {
StringBuilder areaCode = new StringBuilder();
byte[] bs;
try {
bs = word.getBytes("GB2312");
for (byte b : bs) {
int code = Integer.parseInt(Integer.toHexString(b & 0xFF), 16);
if (isChina) {
int temp = code - 0x80 - 0x20;
if (temp < 10) {
areaCode.append("0").append(temp);
} else {
areaCode.append(temp);
}
} else {
int temp = code - 32;
if (temp < 10) {
areaCode.append("030").append(temp);
} else {
areaCode.append("03").append(temp);
}
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.e("liluo", "區位碼轉碼異常,可能導致亂碼或者空白字符");
}
return areaCode.toString();
}
/**
* 根據Unicode編碼判斷中文漢字和符號
*/
public static boolean isChinese(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION;
}
下面是我這邊封裝的發送商品數據的方法,各位可以參考比對:
public static String getGoodsData(BaseGoodsinfo s) {
String test = "!0V" + s.getGoodsPlu() + "A" + "000" + s.getGoodsPlu() + CommUtil.addZero(CommUtil.SaveTwo(s.getSellPrice()), 6) + "0" + CommUtil.getZreo(6) + CommUtil.getBaozhiqi(s.getExpirationDate()) + "21" + CommUtil.getZreo(15) + "00000" +
CommUtil.getZreo(28) + "B" + CommUtil.getTotalAreaCode(s.getGoodsName()) + "CD";
String content = HexUtil.str2HexStr(test) + "0D0A";
return content;
}
因爲在CD之後還有描述位無法在十進制中表達,所以在轉了之後強行拼接0A0D在最後。這裏將字符串轉16進制方法呈上:
/**
* 字符串轉換成十六進制字符串
*
* @param str 待轉換的ASCII字符串
* @return String 每個Byte之間空格分隔,如: [61 6C 6B]
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
sb.append(' ');
}
return sb.toString().trim();
}
因爲socekt是二進制發送的,所以需要將十六進制轉換成二進制發送:
public static byte[] hexStringToByteArray(String hexString) {
hexString = hexString.replaceAll(" ", "");
int len = hexString.length();
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 兩位一組,表示一個字節,把這樣表示的16進制字符串,還原成一個字節
bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
.digit(hexString.charAt(i + 1), 16));
}
return bytes;
}
發送成功後會返回0v+四位plu號+一串其它的,當我們接收到0v開頭加上你的plu號時即表示商品下發到秤成功。
好了,今天就先講商品的發送,後續還會在將快捷鍵、13位18位條碼設置、標籤發送等。
碼字不易,希望能幫到你。