美文网首页
北斗卫星导航拓荒记

北斗卫星导航拓荒记

作者: cyq7on | 来源:发表于2020-02-29 12:41 被阅读0次

    最近接触了一个项目,主要的使用场景是没有互联网的,所以需要App与北斗卫星进行通信,包括获取地理信息,上报信息,解析后台通过卫星下发的信息。北斗海聊官方只提供了PC版的测试软件,我不知使用了什么方法去查看了他们的源码,没有发现对底层通信协议单独做的封装。网上能查到的都没法用,所以,只能自己从0开始了。

    硬件设备

    image.png

    如图,就是这样一个灯罩状的设备,里面插了一张北斗SIM卡。

    PC端测试软件

    image.png

    通过USB口连接设备,重新选择端口和波特率,点击端口号旁边的连接,正常情况下就ok了:


    image.png

    此时就可以发一些指令来测试软件和硬件是否正常工作了。


    image.png
    我发送了两条指令,分别是IC读取和定位申请,即上图中的红框1,2。
    红框3是发送的指令,红框4是收到的指令,具体的协议我们先跳过,后面再研究。

    USB转串口通信

    手机可以用无线和有线两种方式与设备进行通信,我们选择的有线的方式,所以使用USB转串口通信。这部分推荐Android usb及串口通信,我也是使用博主的工具进行调试的,调通之后再开始接入自己的项目,进行后续开发。我针对北斗海聊做的一些特殊的优化在这个项目中

    北斗协议

    image.png
    开发文档我先后拿到过三份,都不尽相同。推荐新手从开发快速入门手册开始看,能够比较快的上手。我最开始拿到的是页数最多的那份,当时心里真是...ε=(´ο`*)))。
    image.png
    标准:
    $IDsss,d1,d2,……,dn*hh<CR><LF>
    一些典型的指令:
    $CCICA,0,00*7B\r\n
    $CCRMO,GGA,2,60*09\r\n
    $BDFKI,DWA,Y,Y,0,0000*0C\n
    • $
      一句指令的开始。
    • ID
      这里只是一个标识符,不要给后面出现的用户ID混淆。发送给设备的指令为CC,收到设备返回的指令为BD。
    • sss
      这是具体指令的名字。
    • ...
      一直到*号之前,这就是具体的指令内容了。
    • *号
      分割符,前面是具体指令内容,后面两位就异或校验。
    • <CR><LF>
      回车换行符,不同平台有所区别,Android平台是\r\n。这是一条指令的终止符,很重要!!!

    Talk is cheap,show me the code

    • IC读取
        /**
         * 读取卡号
         *
         * @return 读取卡号命令
         */
        public static String getICCmd() {
            return "$CCICA,0,00*7B\r\n";
        }
    
    • 获取地理信息
         /**
         * 获取位置信息,北斗一代
         *
         * @return 获取位置信息命令
         */
        public static String getLocationCmdV1() {
            return "$CCDWA,0000000,V,1,L,,0,,,0*65\r\n";
        }
    
    

    一代的定位精度低一些,现在一般都不用了。

         /**
         * 获取位置信息,北斗二代,更加精确,频度60s
         *
         * @return 获取位置信息命令
         */
        public static String getLocationCmd() {
            return "$CCRMO,GGA,2,60*09\r\n";
        }
    
        public static String getLocationCmd(int freq) {
            String s = "CCRMO,GGA,2," + freq;
            String check = SerialPortUtil.getBCC(s.getBytes());
            return "$" + s + "*" + check + "\r\n";
        }
    
    

    北斗二代获取地理信息有频度限制,最高60s/次。

    • 停止输出
         /**
         * 停止输出所有指令
         *
         * @return
         */
        public static String stopOutputCmd() {
            return "$CCRMO,,3,*4F" + "\r\n";
        }
    

    比如60s/次开始定位后,想通过不断电的方式让设备停止定位,则可以发送此指令。

    • 发送短报文
        /**
         * 发送短报文
         *
         * @param id      收信方用户id,必须为7位,eg:0967760
         * @param content 短报文内容
         * @return 发送短报文命令,eg:
         * $CCTXA,0967760,1,2,A43132335F414243BABAD7D6*77,其内容为”123_ABC汉字“
         */
        public static String getMsgCmd(String id, String content) {
            String contentFlag = "A4";
            String start = "CCTXA";
            //分别表示通信类别和传输方式,这里选择了普通通信、混合传输
            String middle = "1,2";
            String result = null;
            try {
                String charsetName = "gb2312";
                byte[] contentBytes = content.getBytes(charsetName);
                StringBuilder sb = new StringBuilder(start)
                        .append(",").append(id)
                        .append(",").append(middle)
                        .append(",").append(contentFlag);
                String hexString = SerialPortUtil.encodeHexString(contentBytes);
                sb.append(hexString);
                String s = sb.toString();
                String check = SerialPortUtil.getBCC(s.getBytes(charsetName));
                result = "$" + s + "*" + check + "\r\n";
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return result;
        }
    
    • 解析反馈信息
        /**
         * 解析反馈信息
         *
         * @param response 反馈字符串,eg:$BDFKI,TXA,Y,Y,0,0000*13,
         *                 $BDFKI,DWA,N,Y,0,0058*16
         * @return
         */
        public static BeidouBean.Response parseResponse(String response) {
            String[] split = response.split(",");
            BeidouBean.Response res = new BeidouBean.Response();
            res.cmdName = split[1];
            res.success = "Y".equals(split[2]);
            res.freqSetting = "Y".equals(split[3]);
            res.limitStatus = Integer.parseInt(split[4]);
            String hourSecond = split[5];
            String hour = hourSecond.substring(0, 2);
            String second = hourSecond.substring(2, 4);
            res.waitSecond = Integer.parseInt(hour) * 60 +
                    Integer.parseInt(second);
            return res;
        }
        //发出指令后的反馈信息
        public static class Response{
            public String cmdName;
            //指令是否执行成功
            public boolean success;
            public boolean freqSetting;
            //0-发射抑制解除,大于0则不正常
            public int limitStatus;
            //当用户设备发送入站申请时,若距离上一次入站申请
            //的时间间隔小于服务频度时,给出等待时间提示,格式为hhss
            public int waitSecond;
        }
    
    • 解析地理信息
        /**
         * 解析位置信息,用于北斗一代
         *
         * @param response 回传字符串,eg:
         *                 $BDDWR,1,0242407,084936.50,2302.2434,N,11323.6667,E,14,M,-6,M,1,V,V,L*1F
         * @return
         */
        public static BeidouBean.Location parseLocationV1(String response) {
            String[] split = response.split(",");
            BeidouBean.Location location = new BeidouBean.Location();
            location.customStr = response;
            location.userId = split[2];
            location.time = split[3];
            location.lat = split[4];
            location.latDirection = split[5];
            location.lon = split[6];
            location.lonDirection = split[7];
            location.altitude = split[8];
            return location;
        }
    
        /**
         * 解析位置信息
         *
         * @param response 回传字符串,eg:
         *                 $GNGGA,063846.00,2914.96875,N,10444.57129,E,1,12,1.07,316.47,M,0,M,,,2.58*6A
         * @return
         */
        public static BeidouBean.Location parseLocation(String response) {
            String[] split = response.split(",");
            BeidouBean.Location location = new BeidouBean.Location();
            location.customStr = response;
            location.time = split[1];
            location.lat = split[2];
            location.latDirection = split[3];
    
            location.lon = split[4];
            location.lonDirection = split[5];
            location.altitude = split[9];
            return location;
        }
    

    我封装的数据模型里并没有把所有信息都加进去,大家使用的使用可以自己拓展。另外,以上方法中没有对回传的指令进行异或校验,上生产时应该加上。

    • 接收指令
      这里有一个坑,一条指令可能会分为2次甚至3次传送回来,所以必须自己做处理。我的解决方案是创建一个buf数组,每次接收到的指令都往里面放,直到读到终止符\r\n。
    DeviceMeasureController.INSTANCE.measure(usbSerialPort,
                    new UsbMeasureParameter(UsbPortDeviceType.USB_OTHERS,
                            19200, 8, 1, 0), new UsbMeasureListener() {
    
                        private byte[] buf = new byte[256];
                        private int index = 0;
    
                        @Override
                        public void measuring(@NotNull UsbSerialPort usbSerialPort, @NotNull byte[] data) {
                            XLog.d(Arrays.toString(data));
                            System.arraycopy(data, 0, buf, index, data.length);             
                                // 换行符
                                if (data[data.length - 1] == (byte) 10) {
                                    String response = new String(buf, 0, index + data.length);
                                    XLog.d(response);
                                    XLog.d(response.length());
    
                                    String info;
                                    if (response.startsWith("$BDFKI")) {
                                        BeidouBean.Response bResponse = BeidouUtil.parseResponse(response);
    
                                        if ("DWA".equals(bResponse.cmdName)) {
                                            if (!bResponse.success) {
                                                //todo
                                            }
                                        } else if ("TXA".equals(bResponse.cmdName)) {
                                            if (bResponse.success) {
                                                //todo
                                            } else {
                                                if (bResponse.waitSecond == 0) {
                                                   //todo
                                                } else {
                                                   //todo
                                                    } 
                                                    XLog.w(String.format("还需等待%ss", bResponse.waitSecond));
                                                }
                                            }
                                        }
    
                                        info = bResponse.toString();
                                    } else if (response.startsWith("$GNGGA")) {
                                        String location = BeidouUtil.customLocation(response);
                                        //todo
                                        info = location;
                                    }else if(response.startsWith("$BDTXR")){
                                        //下发
                                        BeidouBean.pushMsg pushMsg = BeidouUtil.parsePushMsg(response);
                                        receiveMsg(pushMsg);
                                        info = pushMsg.toString();
                                    } else {
                                        info = response;
                                    }
                                    XLog.d(info);
                                    XLog.d(Arrays.toString(buf));
                                    buf = new byte[256];
                                    index = 0;
                                } else {
                                    index += data.length;
                                }
    
                            });
    
                        }
    
                        @Override
                        public void write(@NotNull UsbSerialPort usbSerialPort) {
                            //允许持续性写入数据
                            try {
                                usbSerialPort.write(new byte[]{(byte) 0xff, (byte) 0xff}, 1000);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
    
                        @Override
                        public void measureError(@NotNull String message) {
                            XLog.e(message);
                        }
                    });
    
           
    

    以上代码看起来很长,因为我把解析不同指令的代码放里面了,简化版本的核心逻辑就这样:

    DeviceMeasureController.INSTANCE.measure(usbSerialPort,
                    new UsbMeasureParameter(UsbPortDeviceType.USB_OTHERS,
                            19200, 8, 1, 0), new UsbMeasureListener() {
    
                        private byte[] buf = new byte[256];
                        private int index = 0;
    
                        @Override
                        public void measuring(@NotNull UsbSerialPort usbSerialPort, @NotNull byte[] data) {
                            XLog.d(Arrays.toString(data));
                            System.arraycopy(data, 0, buf, index, data.length);             
                                // 换行符
                                if (data[data.length - 1] == (byte) 10) {
                                    String response = new String(buf, 0, index + data.length);
                                    //todo
                                    buf = new byte[256];
                                    index = 0;
                                } else {
                                    index += data.length;
                                }
    
                            });
    
                        }
    
                        @Override
                        public void write(@NotNull UsbSerialPort usbSerialPort) {
                            //允许持续性写入数据
                            try {
                                usbSerialPort.write(new byte[]{(byte) 0xff, (byte) 0xff}, 1000);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
    
                        @Override
                        public void measureError(@NotNull String message) {
                            XLog.e(message);
                        }
                    });
    
           
    
    image.png
    以上是我这次使用北斗短报文实现的一个伪IM。
    代码在此

    相关文章

      网友评论

          本文标题:北斗卫星导航拓荒记

          本文链接:https://www.haomeiwen.com/subject/odmuhhtx.html