安卓開發硬件開發之-大華條碼秤開發1

最近公司做零售項目,接了電子秤,條碼秤,打印機等一堆的硬件設備,其它設備網上都能找到相應資料這裏就不在重述,這篇文章主要講的是條碼秤連接以及向秤發送命令與接收回應。
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位條碼設置、標籤發送等。

碼字不易,希望能幫到你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章