• 微信蓝牙设备开发之初步接入微信硬件jsapi

    文章来源:http://www.vxzsk.com/145.html 


    微信app和蓝牙设备接入通信,通信交互通道有两种,如下图通道路径结构图


    黑色箭头 公众号html界面发送apdu命令到设备,设备最终返回数据到厂商服务器,厂商服务器也就是咱们开发者自己工程项目的服务器后端(数据返回到服务器配置url的 servelet的post方法中了)。这也是导致初学者开发者在开发h5界面发送指令后,无论如何在h5前端界面也得不到蓝牙设备返回数据包的原因,这是个坑,大家注意。

    红色箭头 也就是我们接下来介绍的通道路径,公众号html界面发送apdu指令到设备,设备最终返回数据到发送html命令的界面,意思就是H5界面发送指令到设备,设备返回响应数据包到H5界面,而不是服务器端。

    点击公众号菜单或链接跳转到H5界面的java方法(案例用的springMVC),在此方法中获取timestamp,nonceStr,signature三个参数。

    1、spring的controller方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //V型知识库 www.vxzsk.com 
    @RequestMapping(value="/goReadCardAnniu")
        public ModelAndView goReadCardAnniu2(HttpServletRequest request,HttpServletResponse response){
             
                 String appId="";//应用id
                 String appsecret="";//应用秘钥
                //1,获取access_token
                AccessToken accessToken = WeixinUtil.getAccessToken(appId, appsecret);
                String access_token=accessToken.getToken();
                //2,获取调用微信jsapi的凭证
                String ticket = WeixinUtil.getJsapiTicket(access_token);
                Map<String,String> map = WeixinUtil.sign(ticket, AppConst.SITE_DOMAIN+"lanya/card/goReadCardAnniu.do");
             
            request.setAttribute("timestamp", map.get("timestamp"));
            request.setAttribute("nonceStr", map.get("nonceStr"));
            request.setAttribute("signature", map.get("signature"));
            request.setAttribute("appId", appId);
             
            return new ModelAndView("/weixin/device/b_chat_s_anniu");
        }

    注意第12行的WeixinUtil.sign方法,第二个参数路径一定是跳转H5界面的路径,否则在H5界面初始化微信硬件jsapi会报错。

    2、获取access_token请参考http://www.vxzsk.com/28.html

    3、获取getJsapiTicket方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static String getJsapiTicket(String access_token){
             String getticket_url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=";//接口凭据
            String jsonData=HttpUtil.sendGet(getticket_url+access_token+"&type=jsapi""utf-8"30000);
            JSONObject jsonObj = JSONObject.fromObject(jsonData);
            String errcode = jsonObj.getString("errcode");
            String ticket = null;
            if(errcode.equals("0")){
                ticket = jsonObj.getString("ticket");
            }
            return ticket;
        }

    HttpUtil.sendGet方法 请参考http://www.vxzsk.com/doc/25.html

    4、WeixinUtil.sign方法获取timestamp,nonceStr,signature三个参数方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    /***
         * 获取界面调用jsapi的所需参数
         * @param jsapi_ticket 凭据
         * @param url 界面请求地址
         * @return
         V型知识库 www.vxzsk.com
         */
        public static Map<String, String> sign(String jsapi_ticket, String url) {
            Map<String, String> ret = new HashMap<String, String>();
            String nonce_str = create_nonce_str();
            String timestamp = create_timestamp();
            String string1;
            String signature = "";
     
            //注意这里参数名必须全部小写,且必须有序
            string1 = "jsapi_ticket=" + jsapi_ticket +
                      "&noncestr=" + nonce_str +
                      "&timestamp=" + timestamp +
                      "&url=" + url;
            System.out.println(string1);
     
            try
            {
                MessageDigest crypt = MessageDigest.getInstance("SHA-1");
                crypt.reset();
                crypt.update(string1.getBytes("UTF-8"));
                signature = byteToHex(crypt.digest());
            }
            catch (NoSuchAlgorithmException e)
            {
                e.printStackTrace();
            }
            catch (UnsupportedEncodingException e)
            {
                e.printStackTrace();
            }
     
            ret.put("url", url);
            ret.put("jsapi_ticket", jsapi_ticket);
            ret.put("nonceStr", nonce_str);
            ret.put("timestamp", timestamp);
            ret.put("signature", signature);
     
            return ret;
        }
         
        private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash)
            {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
     
        private static String create_nonce_str() {
            return UUID.randomUUID().toString();
        }
     
        private static String create_timestamp() {
            return Long.toString(System.currentTimeMillis() / 1000);
        }
        

    以上便是跳转H5界面的controller方法,跳转到b_chat_s_anniu.jsp界面的代码如下,先来个分割线


    1、jsp界面引入微信硬件jsapi的js库,jquery库

    1
    2
      <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script> 
      <script src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"> </script>

    2、<body></body>之间的html代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    <!--标题行-->
    <h2 style="color: white;background-color: green;text-align: center;background-position: center;">蓝牙设备</h2>
      <div class="page">
        <div class="bd spacing">
            <div class="weui_cells weui_cells_form">
                    
                <div class="weui_cell">
                    <div class="weui_cell_hd"><label class="weui_label" style="width: auto;">当前设备:&nbsp</label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                       <label id="lbdeviceid" class="weui_label" style="width: auto;"></label>
                    </div>
                </div>
                <div class="weui_cell">
                    <div class="weui_cell_hd"><label class="weui_label" style="width: auto;">状态信息:&nbsp</label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                        <label id="lbInfo" class="weui_label" style="width: auto;"></label>
                    </div>
                </div
                <div class="weui_cell" >
                    <div class="weui_cell_hd"><label class="weui_label">日志:  </label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                        <textarea id="logtext" class="weui_textarea" placeholder="日志" rows="5"></textarea>
                    </div>
                </div>
      
            </div>
     
            <div class="weui_btn_area weui">
                 
                <button class="weui_btn weui_btn weui_btn_warn" id="CallGetWXrefresh">获取设备</button><br>
      
            </div>
      
        </div>
     
        <div class="weui_dialog_alert" id="Mydialog" style="display: none;">
        <div class="weui_mask"></div>
        <div class="weui_dialog">
            <div class="weui_dialog_hd" id="dialogTitle"><strong class="weui_dialog_title">着急啦</strong></div>
            <div class="weui_dialog_bd" id="dialogContent">亲,使用本功能,请先打开手机蓝牙!</div>
            <div class="weui_dialog_ft">
                <a href="#" class="weui_btn_dialog primary">确定</a>
            </div>
        </div>
        </div>
         
         
        <!--BEGIN toast-->
        <div id="toast" style="display: none;">
            <div class="weui_mask_transparent"></div>
            <div class="weui_toast">
                <i class="weui_icon_toast"></i>
                <p class="weui_toast_content" id="toast_msg">已完成</p>
            </div>
        </div>
        <!--end toast-->
     
        <!-- loading toast -->
        <div id="loadingToast" class="weui_loading_toast" style="display:none;">
            <div class="weui_mask_transparent"></div>
            <div class="weui_toast">
                <div class="weui_loading">
                    <div class="weui_loading_leaf weui_loading_leaf_0"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_1"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_2"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_3"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_4"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_5"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_6"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_7"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_8"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_9"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_10"></div>
                    <div class="weui_loading_leaf weui_loading_leaf_11"></div>
                </div>
                <p class="weui_toast_content" id="loading_toast_msg">数据加载中</p>
            </div>
        </div>
        <!-- End loading toast -->
         
        <!--BEGIN dialog1-->
        <div class="weui_dialog_confirm" id="dialog1" style="display: none;">
            <div class="weui_mask"></div>
            <div class="weui_dialog">
                <div class="weui_dialog_hd"><strong class="weui_dialog_title">弹窗标题</strong></div>
                <div class="weui_dialog_bd">自定义弹窗内容,居左对齐显示,告知需要确认的信息等</div>
                <div class="weui_dialog_ft">
                    <a href="javascript:;" class="weui_btn_dialog default" id="qxBtn">取消</a>
                    <a href="javascript:;" class="weui_btn_dialog primary" id="okBtn">确定</a>
                </div>
            </div>
        </div>
        <!--END dialog1-->
        <!--BEGIN dialog2-->
        <div class="weui_dialog_alert" id="dialog2" style="display: none;">
            <div class="weui_mask"></div>
            <div class="weui_dialog">
                <div class="weui_dialog_hd"><strong class="weui_dialog_title">弹窗标题</strong></div>
                <div class="weui_dialog_bd">弹窗内容,告知当前页面信息等</div>
                <div class="weui_dialog_ft">
                    <a href="javascript:;" class="weui_btn_dialog primary">确定</a>
                </div>
            </div>
        </div>
        <!--END dialog2-->
    </div>
     
    <div id="myparams" style="display: none">
     <span id="timestamp">${timestamp }</span>
     <span id="nonceStr">${nonceStr }</span>
     <span id="signature">${signature }</span>
     <span id="appId">${appId }</span>
      
    </div>

    上述html最后四行代码就是我们在controller中存储在request对象中的四个参数

    3、重点来了,初始化微信硬件jsapi库,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    jQuery(document).ready(function(){
      //初始化库 
     loadXMLDoc();
     //初始化库结束
     //点击获取设备按钮的函数 开始
     $("#CallGetWXrefresh").on("click",function(e){  
        
         //1. 打开微信设备 
         my_openWXDeviceLib();
         //2. 获取设备信息
         my_getWXDeviceInfos();
     });
     //点击获取设备按钮的函数 结束 
       
     });

    loadXMLDoc();方法是初始化微信硬件jsapi库,后面方法是给"获取设备"按钮绑定一个点击事件

    1)、loadXMLDoc();方法代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //初始化 微信硬件jsapi库 V型知识库 www.vxzsk.com
    function loadXMLDoc()
    {
        var appId =jQuery("#appId").text();
        var timestamp=jQuery("#timestamp").text();
        var nonceStr =jQuery("#nonceStr").text();
        var signature=jQuery("#signature").text();
        wx.config({
                 beta: true,
                  debug: true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                  appId: appId, 
                  timestamp: timestamp,
                  nonceStr: nonceStr,
                  signature: signature,
                  jsApiList: [
                    'openWXDeviceLib',
                    'closeWXDeviceLib',
                    'getWXDeviceInfos',
                    'getWXDeviceBindTicket',
                    'getWXDeviceUnbindTicket',
                    'startScanWXDevice',
                    'stopScanWXDevice',
                    'connectWXDevice',
                    'disconnectWXDevice',
                    'sendDataToWXDevice',
                    'onWXDeviceBindStateChange',
                    'onWXDeviceStateChange',
                    'onScanWXDeviceResult',
                    'onReceiveDataFromWXDevice',
                    'onWXDeviceBluetoothStateChange',
                  ]
              });
                 alert("初始化库结束");
    }

    此方法需要四个参数,我们已经在controller中获取并存放到request对象中了。

    2)、打开设备方法my_openWXDeviceLib()代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    function my_openWXDeviceLib(){
       var x=0; 
       WeixinJSBridge.invoke('openWXDeviceLib', {}, 
       function(res){
           mlog("打开设备返回:"+res.err_msg);
          if(res.err_msg=='openWXDeviceLib:ok')
            {
              if(res.bluetoothState=='off')
                {    
                  showdialog("太着急啦","亲,使用前请先打开手机蓝牙!");  
                  $("#lbInfo").innerHTML="1.请打开手机蓝牙";
                  $("#lbInfo").css({color:"red"});
                  x=1;
                  isOver();
                };
              if(res.bluetoothState=='unauthorized')
                {
                  showdialog("出错啦","亲,请授权微信蓝牙功能并打开蓝牙!");    
                  $("#lbInfo").html("1.请授权蓝牙功能");
                  $("#lbInfo").css({color:"red"});
                  x=1;
                  isOver();
                }; 
              if(res.bluetoothState=='on')
                {
                  //showdialog("太着急啦","亲,请查看您的设备是否打开!");   
                  $("#lbInfo").html("1.蓝牙已打开,未找到设备");
                  $("#lbInfo").css({color:"red"});
                  //$("#lbInfo").attr(("style", "background-color:#000");
                  x=0;
                  //isOver();
                };      
            }
          else
            {
              $("#lbInfo").html("1.微信蓝牙打开失败");
              x=1; 
              showdialog("微信蓝牙状态","亲,请授权微信蓝牙功能并打开蓝牙!");   
            }
        });
       return x;  //0表示成功 1表示失败
    }

    3)、获取设备信息方法my_getWXDeviceInfos代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function my_getWXDeviceInfos(){
        
        WeixinJSBridge.invoke('getWXDeviceInfos', {}, function(res){
            var len=res.deviceInfos.length;  //绑定设备总数量
            for(i=0; i<=len-1;i++)
             {
               //alert(i + ' ' + res.deviceInfos[i].deviceId + ' ' +res.deviceInfos[i].state); 
               if(res.deviceInfos[i].state==="connected")
                {
                  $("#lbdeviceid").html(res.deviceInfos[i].deviceId); 
                  C_DEVICEID = res.deviceInfos[i].deviceId;
                  $("#lbInfo").html("2.设备已成功连接");
                  $("#lbInfo").css({color:"green"});
                  
                  break;   
                }  
             }
                
        }); 
      return;    
    }

    4)、日志输出方法

    1
    2
    3
    4
    5
    6
    7
    //打印日志
    function mlog(m){
        var log=$('#logtext').val();
        //log=log+m;
        log = m;
        $('#logtext').val(log);
    }

    好了,至此代码都已经写玩了,咱们来看看效果吧。

    第一、打开手机蓝牙并登录微信,扫描设备的二维码,具体如何生成设备二维码和授权设备请参考左上角菜单中的其它章节。

    第二,绑定设备-进入公众号,然后我们可以看到公众号头部已经有"已连接1个设备或未连接"

    第三、点击我们上面所说的controller方法,进入H5界面

    第四、点击获取设备按钮

    如图所示,当前设备的deviceid已经被输出来了,并且打开设备的日志也被打印输出。

    第五,完整的jsp页面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    session.setAttribute("basePath",basePath);
    %>
    <!DOCTYPE html>
    <html class="ie"><!--<![endif]-->
    <head>
    <meta charset="utf-8">
      <title>微信蓝牙设备</title>
      <meta name="viewport" content="width=320.1,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
      
      <link rel="stylesheet" href="<%=basePath %>/resource/device/weui.min.css?what=0">
    <script type="text/javascript" src="<%=basePath %>/resource/weixin/jquery_mobile/js/jquery-2.0.3.min.js"></script
      <script src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"> </script>
     
      
    </head>
    <body ontouchstart>
    <!--标题行-->
    <h2 style="color: white;background-color: green;text-align: center;background-position: center;">蓝牙设备</h2>
      <div class="page">
        <div class="bd spacing">
            <div class="weui_cells weui_cells_form">
                    
                <div class="weui_cell">
                    <div class="weui_cell_hd"><label class="weui_label" style="width: auto;">当前设备:&nbsp</label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                       <label id="lbdeviceid" class="weui_label" style="width: auto;"></label>
                    </div>
                </div>
                <div class="weui_cell">
                    <div class="weui_cell_hd"><label class="weui_label" style="width: auto;">状态信息:&nbsp</label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                        <label id="lbInfo" class="weui_label" style="width: auto;"></label>
                    </div>
                </div
                <div class="weui_cell" >
                    <div class="weui_cell_hd"><label class="weui_label">日志:  </label></div>
                    <div class="weui_cell_bd weui_cell_primary">
                        <textarea id="logtext" class="weui_textarea" placeholder="日志" rows="5"></textarea>
                    </div>
                </div>
      
            </div>
     
            <div class="weui_btn_area weui">
                 
                <button class="weui_btn weui_btn weui_btn_warn" id="CallGetWXrefresh">获取设备</button><br>
      
            </div>
      
        </div>
     
        <div class="weui_dialog_alert" id="Mydialog" style="display: none;">
        <div class="weui_mask"></div>
        <div class="weui_dialog">
            <div class="weui_dialog_hd" id="dialogTitle"><strong class