北斗卫星导航拓荒记

最近接触了一个项目,主要的使用场景是没有互联网的,所以需要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
一些典型的指令:
$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
    这是具体指令的名字。
  • ...
    一直到*号之前,这就是具体的指令内容了。
  • *号
    分割符,前面是具体指令内容,后面两位就异或校验。

  • 回车换行符,不同平台有所区别,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。
代码在此

你可能感兴趣的:(北斗卫星导航拓荒记)