北斗卫星导航拓荒记

最近接触了一个项目,主要的使用场景是没有互联网的,所以需要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。
代码在此

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343