├── README.md ├── app.js ├── app.json ├── app.wxss ├── images └── about.png ├── pages ├── about │ ├── about.js │ ├── about.json │ ├── about.wxml │ └── about.wxss ├── functionPage │ ├── functionPage.js │ ├── functionPage.json │ ├── functionPage.wxml │ └── functionPage.wxss └── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json ├── screenshots ├── 1.jpg ├── 2.jpg ├── myQrcode.jpg └── wcode.jpg ├── sitemap.json └── utils ├── data.js └── util.js /README.md: -------------------------------------------------------------------------------- 1 | ### 一、微信小程序开发相关资料: 2 | 3 | * [微信公众平台:](https://mp.weixin.qq.com/) 4 | * 开发小程序或公众号需要先到这里注册 5 | * [小程序官方文档:](https://mp.weixin.qq.com/debug/wxadoc/introduction/index.html?t=2018313) 6 | * 微信小程序开发离不了官方文档。 7 | * [小程序官方开发文档:](https://mp.weixin.qq.com/debug/wxadoc/dev/index.html?t=20171117) 8 | * 微信小程序开发离不了官方开发文档。 9 | 10 | 11 | 12 | 13 | 14 | ### 二、主要效果图 15 | 16 | 17 | 18 | ### 三、简述 19 | #### 1.蓝牙BLE调试工具终于上线了! 20 | 这段时间研究了小程序蓝牙API的使用方法,从扫描到连接,从读写数据到监听接收数据,总算调通了整个开发流程!为了方便后续项目的调试,于是乎才有了这个小程序————蓝牙BLE调试工具。 21 | 22 | #### 2.关于蓝牙 23 | 蓝牙有传统蓝牙(3.0以下)和低功耗蓝牙(BLE,又称蓝牙4.0)之分,下面简述就下传统蓝牙和低功耗蓝牙区别: 24 | * BLE蓝牙较传统蓝牙, 传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优点 25 | * 传统蓝牙与BLE蓝牙通信方式也有所不同,传统的一般通过socket方式,而BLE蓝牙是通过Gatt协议来实现 26 | * 传统蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输等; 27 | * 低功耗蓝牙应用于实时性要求比较高,但是数据速率比较低的产品,如遥控类的,如鼠标、键盘、血压计、温度传感器等。 28 | 29 | #### 3.兼容版本 30 | 安卓手机:Android4.3以上、微信APP客户端6.5.7以上; 31 | 苹果手机:iPhone4s以上并且系统要求ios6以上、微信APP客户端6.5.6以上; 32 | 33 | #### 4.使用 34 | 整个开发流程为: 35 | * 判断系统是否支持蓝牙BLE 36 | * 初始化蓝牙适配器 37 | * 扫描 38 | * 连接 39 | * 获取服务UUID、读写和监听的UUID 40 | * 进行读或写操作,监听接收数据 41 | * 断开连接 42 | ##### 关于具体使用方法,请查看官方开发文档! 43 | 44 | #### 5.使用小程序蓝牙API需要注意什么? 45 | * 手机系统和微信版本是否支持蓝牙BLE 46 | * 有些Android6.0以上手机需要开启定位才能搜索蓝牙 47 | * 发送和接收数据是否超过20字节 48 | * ios和Android扫描蓝牙获取到的deviceId不一样 49 | * 等等等 50 | 51 | ### 四、欢迎体验 52 | * 目前小程序已经上线,整个开发流程也算是大概了解和尝试过了。最后贴出小程序码,欢迎体验! 53 | * 54 | * 最近建了公众号,欢迎大家关注,一起学习Android、小程序、跨平台开发~ 55 | * ![](./screenshots/myQrcode.jpg) -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function() { 4 | // 登录 5 | wx.login({ 6 | success: res => { 7 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 8 | } 9 | }) 10 | // 获取用户信息 11 | wx.getSetting({ 12 | success: res => { 13 | // console.log("获取用户的当前设置",res); 14 | if (res.authSetting['scope.userInfo']) { 15 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 16 | wx.getUserInfo({ 17 | success: res => { 18 | // 可以将 res 发送给后台解码出 unionId 19 | this.globalData.userInfo = res.userInfo 20 | 21 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 22 | // 所以此处加入 callback 以防止这种情况 23 | if (this.userInfoReadyCallback) { 24 | this.userInfoReadyCallback(res) 25 | } 26 | } 27 | }) 28 | } 29 | }, 30 | fail: function(res) { 31 | console.log("获取信息失败", res) 32 | } 33 | }) 34 | 35 | this.globalData.sysinfo = wx.getSystemInfoSync(); 36 | }, 37 | 38 | getModel: function() { //获取手机型号 39 | return this.globalData.sysinfo["model"] 40 | }, 41 | getWxVersion: function() { //获取微信版本号 42 | return this.globalData.sysinfo["version"] 43 | }, 44 | getSystem: function() { //获取操作系统版本 45 | return this.globalData.sysinfo["system"] 46 | }, 47 | getPlatform: function() { //获取客户端平台 48 | return this.globalData.sysinfo["platform"] 49 | }, 50 | getSDKVersion: function() { //获取客户端基础库版本 51 | return this.globalData.sysinfo["SDKVersion"] 52 | }, 53 | 54 | //toast提示 55 | toastTips: function(txt) { 56 | wx.showToast({ 57 | title: txt 58 | }) 59 | }, 60 | //toast提示,可以设置等待时长 61 | toastTips1: function(txt, time) { 62 | wx.showToast({ 63 | title: txt, 64 | duration: time 65 | }) 66 | }, 67 | toastTips2: function(txt) { 68 | wx.showToast({ 69 | title: txt, 70 | icon: "loading" 71 | }) 72 | }, 73 | 74 | //弹窗提示 75 | showModal: function(txt) { 76 | wx.showModal({ 77 | title: '提示', 78 | content: txt, 79 | showCancel: false, 80 | }) 81 | }, 82 | //弹窗提示,有确认按钮 83 | showModal1: function(txt) { 84 | wx.showModal({ 85 | title: "提示", 86 | content: txt, 87 | showCancel: false, 88 | confirmText: "确定" 89 | }) 90 | }, 91 | //loading 92 | showLoading: function(txt) { 93 | wx.showLoading({ 94 | title: txt, 95 | mask: true 96 | }); 97 | }, 98 | 99 | isBlank: function(str) { 100 | if (Object.prototype.toString.call(str) === '[object Undefined]') { //空 101 | return true 102 | } else if ( 103 | Object.prototype.toString.call(str) === '[object String]' || 104 | Object.prototype.toString.call(str) === '[object Array]') { //字条串或数组 105 | return str.length == 0 ? true : false 106 | } else if (Object.prototype.toString.call(str) === '[object Object]') { 107 | return JSON.stringify(str) == '{}' ? true : false 108 | } else { 109 | return true 110 | } 111 | 112 | }, 113 | 114 | globalData: { 115 | userInfo: null, 116 | sysinfo: null 117 | } 118 | 119 | 120 | }) -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/functionPage/functionPage", 5 | "pages/about/about" 6 | ], 7 | "window": { 8 | "backgroundTextStyle": "light", 9 | "navigationBarBackgroundColor": "#179B16", 10 | "navigationBarTitleText": "小程序蓝牙调试工具", 11 | "navigationBarTextStyle": "white", 12 | "backgroundColor": "#F8F8F8" 13 | }, 14 | "sitemapLocation": "sitemap.json" 15 | } -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | page { 3 | background-color: #F8F8F8; 4 | height: 100%; 5 | font-size: 32rpx; 6 | line-height: 1.6; 7 | } 8 | .page-body { 9 | width: 100%; 10 | min-height: 100%; 11 | display:flex; 12 | flex-direction:column;/*垂直显示,正如一个列一样,row就是水平显示*/ 13 | justify-content: center;/* center水平居中 align-items:center;垂直居中*/ 14 | font-size: 32rpx; 15 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 16 | } 17 | 18 | .container { 19 | display: flex; 20 | flex-direction: column; 21 | min-height: 100%; 22 | justify-content: space-between; 23 | font-size: 32rpx; 24 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 25 | } 26 | .text-box{ 27 | margin-bottom: 10rpx; 28 | margin-top: 10rpx; 29 | padding: 10rpx 0; 30 | display: flex; 31 | min-height: 50rpx; 32 | background-color: #ddf; 33 | justify-content: center; 34 | align-items: center; 35 | text-align: center; 36 | font-size: 30rpx; 37 | color: #353535; 38 | line-height: 2em; 39 | } 40 | .about-img { 41 | position: fixed; 42 | width: 100rpx; 43 | height: 100rpx; 44 | bottom: 30rpx; 45 | right: 30rpx; 46 | } -------------------------------------------------------------------------------- /images/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessLuo/BlueToothTool/56df4ad518ec83e392cf449dabea2947253e7870/images/about.png -------------------------------------------------------------------------------- /pages/about/about.js: -------------------------------------------------------------------------------- 1 | // pages/about/about.js 2 | // 在页面中定义插屏广告 3 | let interstitialAd = null 4 | 5 | Page({ 6 | 7 | /** 8 | * 页面的初始数据 9 | */ 10 | data: { 11 | 12 | }, 13 | 14 | /** 15 | * 生命周期函数--监听页面加载 16 | */ 17 | onLoad: function (options) { 18 | // 在页面onLoad回调事件中创建插屏广告实例 19 | if (wx.createInterstitialAd) { 20 | interstitialAd = wx.createInterstitialAd({ 21 | adUnitId: 'adunit-acd1c5fefdcea629' 22 | }) 23 | interstitialAd.onLoad(() => { }) 24 | interstitialAd.onError((err) => { }) 25 | interstitialAd.onClose(() => { }) 26 | } 27 | }, 28 | 29 | /** 30 | * 生命周期函数--监听页面初次渲染完成 31 | */ 32 | onReady: function () { 33 | 34 | }, 35 | 36 | /** 37 | * 生命周期函数--监听页面显示 38 | */ 39 | onShow: function () { 40 | // 在适合的场景显示插屏广告 41 | if (interstitialAd) { 42 | interstitialAd.show().catch((err) => { 43 | console.error(err) 44 | }) 45 | } 46 | }, 47 | 48 | /** 49 | * 生命周期函数--监听页面隐藏 50 | */ 51 | onHide: function () { 52 | 53 | }, 54 | 55 | /** 56 | * 生命周期函数--监听页面卸载 57 | */ 58 | onUnload: function () { 59 | 60 | }, 61 | 62 | /** 63 | * 页面相关事件处理函数--监听用户下拉动作 64 | */ 65 | onPullDownRefresh: function () { 66 | 67 | }, 68 | 69 | /** 70 | * 页面上拉触底事件的处理函数 71 | */ 72 | onReachBottom: function () { 73 | 74 | }, 75 | 76 | /** 77 | * 用户点击右上角分享 78 | */ 79 | onShareAppMessage: function () { 80 | 81 | } 82 | }) -------------------------------------------------------------------------------- /pages/about/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "关于" 3 | } -------------------------------------------------------------------------------- /pages/about/about.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 蓝牙BLE调试工具,是一款方便做蓝牙开发的程序猿用来调试蓝牙的工具,目前仅支持蓝牙4.0以上,关于传统蓝牙和低功耗蓝牙在此就不一一赘述了!\r\n 6 | 我是一名默默无闻的Android开发程序猿,我最近在学习小程序开发,目前这项目就是利用业余时间边学边做的,主要是尝试去学习小程序的视图排版和逻辑js,这段时间以来,我对前端知识的认知更深一层了。\r\n 7 | 小程序是腾讯微信官方推出的一款轻量级的产品,其可在Android手机和ios手机微信运行,从2016年底宣布,到2017年正式上线;目前来到了2018年底了,时间过得好快,转眼又是两年了!\r\n 8 | 我也以我的观点总结一下吧,个人认为小程序是无法替代Android的地位的,可以从以下方面思考: 9 | 1.开发角度:小程序是微信开发的一种类似前端框架的产品,对系统原生方法调用还有很多局限性; 10 | 2.运营角度:小程序是基于微信客户端开发的;开发出来的小程序受到微信产品的限制,就类似淘宝链接不能在微信打开一样等; 11 | 3.用户体验:必须要先安装微信客户端,页面卡顿,没有原生的流畅。\r\n 12 | 所以说起替代Android,目前是不可能的!但小程序方案确实不错,毕竟微信流量那么大,完全可以开发一些小项目,达到给自家的APP引流或运营等方案的目的。作为Android开发者,没必要给自己太多限制,业余时间完全可以学习小程序,我前端基础不咋滴,但现在已经上手小程序开发了。 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /pages/about/about.wxss: -------------------------------------------------------------------------------- 1 | /* pages/about/about.wxss */ 2 | .about{ 3 | padding: 15rpx; 4 | background-color: #fff; 5 | } 6 | .scroll-view{ 7 | border-radius: 10rpx; 8 | position: absolute; 9 | width: 94%; 10 | top: 3%; 11 | left: 3%; 12 | right: 3%; 13 | bottom: 200rpx; 14 | box-shadow: 3rpx 4rpx 8rpx rgba(0, 0, 0, 0.3); 15 | } 16 | /* 程序猿在廣東 */ 17 | .gzh-view { 18 | position: fixed; 19 | bottom: 1%; 20 | left: 5%; 21 | width: 90%; 22 | box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3); 23 | } -------------------------------------------------------------------------------- /pages/functionPage/functionPage.js: -------------------------------------------------------------------------------- 1 | // pages/funtionPage/funtionPage.js 2 | var app = getApp(); 3 | var utils = require("../../utils/util.js"); 4 | Page({ 5 | 6 | /** 7 | * 页面的初始数据 8 | */ 9 | data: { 10 | textLog:"", 11 | deviceId: "", 12 | name: "", 13 | allRes:"", 14 | serviceId:"", 15 | readCharacteristicId:"", 16 | writeCharacteristicId: "", 17 | notifyCharacteristicId: "", 18 | connected: true, 19 | canWrite: false 20 | }, 21 | 22 | /** 23 | * 生命周期函数--监听页面加载 24 | */ 25 | onLoad: function (options) { 26 | var that = this; 27 | var devid = decodeURIComponent(options.deviceId); 28 | var devname = decodeURIComponent(options.name); 29 | var devserviceid = decodeURIComponent(options.serviceId); 30 | var log = that.data.textLog + "设备名=" + devname +"\n设备UUID="+devid+"\n服务UUID="+devserviceid+ "\n"; 31 | this.setData({ 32 | textLog: log, 33 | deviceId: devid, 34 | name: devname, 35 | serviceId: devserviceid 36 | }); 37 | //获取特征值 38 | that.getBLEDeviceCharacteristics(); 39 | }, 40 | 41 | /** 42 | * 生命周期函数--监听页面显示 43 | */ 44 | onShow: function () { 45 | if (wx.setKeepScreenOn) { 46 | wx.setKeepScreenOn({ 47 | keepScreenOn: true, 48 | success: function (res) { 49 | //console.log('保持屏幕常亮') 50 | } 51 | }) 52 | } 53 | }, 54 | 55 | /** 56 | * 生命周期函数--监听页面隐藏 57 | */ 58 | onHide: function () { 59 | 60 | }, 61 | 62 | //清空log日志 63 | startClear: function () { 64 | var that = this; 65 | that.setData({ 66 | textLog: "" 67 | }); 68 | }, 69 | 70 | //点击设置最大MTU 71 | setMTUClick:function(){ 72 | var that = this; 73 | that.setMaxMTU(); 74 | }, 75 | 76 | //返回蓝牙是否正处于链接状态 77 | onBLEConnectionStateChange:function (onFailCallback) { 78 | wx.onBLEConnectionStateChange(function (res) { 79 | // 该方法回调中可以用于处理连接意外断开等异常情况 80 | console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`); 81 | return res.connected; 82 | }); 83 | }, 84 | //断开与低功耗蓝牙设备的连接 85 | closeBLEConnection: function () { 86 | var that = this; 87 | wx.closeBLEConnection({ 88 | deviceId: that.data.deviceId 89 | }) 90 | that.setData({ 91 | connected: false, 92 | 93 | }); 94 | wx.showToast({ 95 | title: '连接已断开', 96 | icon: 'success' 97 | }); 98 | setTimeout(function () { 99 | wx.navigateBack(); 100 | }, 2000) 101 | }, 102 | //获取蓝牙设备某个服务中的所有 characteristic(特征值) 103 | getBLEDeviceCharacteristics: function (order){ 104 | var that = this; 105 | wx.getBLEDeviceCharacteristics({ 106 | deviceId: that.data.deviceId, 107 | serviceId: that.data.serviceId, 108 | success: function (res) { 109 | for (let i = 0; i < res.characteristics.length; i++) { 110 | let item = res.characteristics[i] 111 | if (item.properties.read) {//该特征值是否支持 read 操作 112 | var log = that.data.textLog + "该特征值支持 read 操作:" + item.uuid + "\n"; 113 | that.setData({ 114 | textLog: log, 115 | readCharacteristicId: item.uuid 116 | }); 117 | } 118 | if (item.properties.write) {//该特征值是否支持 write 操作 119 | var log = that.data.textLog + "该特征值支持 write 操作:" + item.uuid + "\n"; 120 | that.setData({ 121 | textLog: log, 122 | writeCharacteristicId: item.uuid, 123 | canWrite:true 124 | }); 125 | 126 | } 127 | if (item.properties.notify || item.properties.indicate) {//该特征值是否支持 notify或indicate 操作 128 | var log = that.data.textLog + "该特征值支持 notify 操作:" + item.uuid + "\n"; 129 | that.setData({ 130 | textLog: log, 131 | notifyCharacteristicId: item.uuid, 132 | }); 133 | that.notifyBLECharacteristicValueChange(); 134 | } 135 | 136 | } 137 | 138 | } 139 | }) 140 | // that.onBLECharacteristicValueChange(); //监听特征值变化 141 | }, 142 | //启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。 143 | //注意:必须设备的特征值支持notify或者indicate才可以成功调用,具体参照 characteristic 的 properties 属性 144 | notifyBLECharacteristicValueChange: function (){ 145 | var that = this; 146 | wx.notifyBLECharacteristicValueChange({ 147 | state: true, // 启用 notify 功能 148 | deviceId: that.data.deviceId, 149 | serviceId: that.data.serviceId, 150 | characteristicId: that.data.notifyCharacteristicId, 151 | success: function (res) { 152 | var log = that.data.textLog + "notify启动成功" + res.errMsg+"\n"; 153 | that.setData({ 154 | textLog: log, 155 | }); 156 | that.onBLECharacteristicValueChange(); //监听特征值变化 157 | }, 158 | fail: function (res) { 159 | wx.showToast({ 160 | title: 'notify启动失败', 161 | mask: true 162 | }); 163 | setTimeout(function () { 164 | wx.hideToast(); 165 | }, 2000) 166 | } 167 | 168 | }) 169 | 170 | }, 171 | //监听低功耗蓝牙设备的特征值变化。必须先启用notify接口才能接收到设备推送的notification。 172 | onBLECharacteristicValueChange:function(){ 173 | var that = this; 174 | wx.onBLECharacteristicValueChange(function (res) { 175 | var resValue = utils.ab2hext(res.value); //16进制字符串 176 | var resValueStr = utils.hexToString(resValue); 177 | 178 | var log0 = that.data.textLog + "成功获取:" + resValueStr + "\n"; 179 | that.setData({ 180 | textLog: log0, 181 | }); 182 | 183 | }); 184 | }, 185 | //orderInput 186 | orderInput:function(e){ 187 | this.setData({ 188 | orderInputStr: e.detail.value 189 | }) 190 | }, 191 | 192 | //发送指令 193 | sentOrder:function(){ 194 | var that = this; 195 | var orderStr = that.data.orderInputStr;//指令 196 | let order = utils.stringToBytes(orderStr); 197 | that.writeBLECharacteristicValue(order); 198 | }, 199 | 200 | //向低功耗蓝牙设备特征值中写入二进制数据。 201 | //注意:必须设备的特征值支持write才可以成功调用,具体参照 characteristic 的 properties 属性 202 | writeBLECharacteristicValue: function (order){ 203 | var that = this; 204 | let byteLength = order.byteLength; 205 | var log = that.data.textLog + "当前执行指令的字节长度:" + byteLength + "\n"; 206 | that.setData({ 207 | textLog: log, 208 | }); 209 | 210 | wx.writeBLECharacteristicValue({ 211 | deviceId: that.data.deviceId, 212 | serviceId: that.data.serviceId, 213 | characteristicId: that.data.writeCharacteristicId, 214 | // 这里的value是ArrayBuffer类型 215 | // value: order.slice(0, 20), 216 | value: order, 217 | success: function (res) { 218 | // if (byteLength > 20) { 219 | // setTimeout(function(){ 220 | // // that.writeBLECharacteristicValue(order.slice(20, byteLength)); 221 | // },150); 222 | // } 223 | var log = that.data.textLog + "写入成功:" + res.errMsg + "\n"; 224 | that.setData({ 225 | textLog: log, 226 | }); 227 | }, 228 | 229 | fail: function (res) { 230 | var log = that.data.textLog + "写入失败" + res.errMsg+"\n"; 231 | that.setData({ 232 | textLog: log, 233 | }); 234 | } 235 | 236 | }) 237 | }, 238 | 239 | //设置最大MTU 240 | //设置蓝牙最大传输单元。需在 wx.createBLEConnection调用成功后调用,mtu 设置范围 (22,512)。安卓5.1以上有效。 241 | setMaxMTU:function(){ 242 | var that = this; 243 | wx.setBLEMTU({ 244 | deviceId: that.data.deviceId, 245 | mtu: 512, 246 | success:(res)=>{ 247 | console.log("setBLEMTU success>>", res) 248 | var log = that.data.textLog + "设置最大MTU成功:res=" +JSON.stringify(res)+"\n"; 249 | that.setData({ 250 | textLog: log, 251 | }); 252 | 253 | }, 254 | fail:(res)=>{ 255 | console.log("setBLEMTU fail>>", res) 256 | var log = that.data.textLog + "设置最大MTU失败:res=" +JSON.stringify(res)+"\n"; 257 | that.setData({ 258 | textLog: log, 259 | }); 260 | } 261 | }) 262 | } 263 | 264 | }) -------------------------------------------------------------------------------- /pages/functionPage/functionPage.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/functionPage/functionPage.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 当前连接的蓝牙设备是: 5 | 设备名:{{name}} 6 | 设备ID:{{deviceId}} 7 | 8 | 9 | 10 | 展示log日志(可滑动查看): 11 | 12 | 13 | {{textLog}} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /pages/functionPage/functionPage.wxss: -------------------------------------------------------------------------------- 1 | /* pages/funtionPage/functionPage.wxss */ 2 | 3 | .bluetooth-detail { 4 | width: 95%; 5 | margin: 0 auto; 6 | padding: 10px 20px; 7 | box-sizing: border-box; 8 | border-bottom: 1px solid #ddd; 9 | } 10 | .card { 11 | border-radius: 10rpx; 12 | background-color: #fff; 13 | margin: 20rpx; 14 | padding: 20rpx; 15 | box-shadow: 0 2rpx 4rpx rgba(0,0,0,.3); 16 | } 17 | .list { 18 | max-height: 45vh; 19 | } 20 | 21 | .function-input{ 22 | width: 80%; 23 | position: relative; 24 | font-size: 16rpx; 25 | display: flex; 26 | flex-direction: row; 27 | align-items: center; 28 | justify-content: space-between; 29 | margin: 0rpx 10%; 30 | } 31 | .input{ 32 | border: 1rpx solid #666; 33 | width: 400rpx; 34 | padding: 20rpx 10rpx; 35 | font-size: 32rpx; 36 | } 37 | .function-button-div { 38 | width: 100%; 39 | position: absolute; 40 | bottom: 6%; 41 | font-size: 16rpx; 42 | } 43 | .function-button-div1 { 44 | width: 100%; 45 | position: relative; 46 | } 47 | .function-button-div2 { 48 | position: relative; 49 | top: 20px; 50 | font-size: 12rpx; 51 | 52 | width: 90%; 53 | margin-left: 5%; 54 | display: flex; 55 | flex-direction: row; 56 | justify-content: space-between 57 | } -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | // pages/index/index.js 2 | var app = getApp(); 3 | var utils = require("../../utils/util.js"); 4 | 5 | function inArray(arr, key, val) { 6 | for (let i = 0; i < arr.length; i++) { 7 | if (arr[i][key] === val) { 8 | return i; 9 | } 10 | } 11 | return -1; 12 | } 13 | 14 | 15 | // 在页面中定义插屏广告 16 | let interstitialAd = null 17 | 18 | 19 | Page({ 20 | /** 21 | * 页面的初始数据 22 | */ 23 | data: { 24 | textLog: "", 25 | isopen: false, //蓝牙适配器是否已打开 26 | devices: [], 27 | connected: false, 28 | chs: [] 29 | }, 30 | 31 | /** 32 | * 生命周期函数--监听页面加载 33 | */ 34 | onLoad: function(options) { 35 | var that = this; 36 | console.log("用户信息", app.globalData.userInfo); 37 | 38 | // 在页面onLoad回调事件中创建插屏广告实例 39 | if (wx.createInterstitialAd) { 40 | interstitialAd = wx.createInterstitialAd({ 41 | adUnitId: 'adunit-0eadc609afe6d24d' 42 | }) 43 | interstitialAd.onLoad(() => { }) 44 | interstitialAd.onError((err) => { }) 45 | interstitialAd.onClose(() => { }) 46 | } 47 | 48 | 49 | var log = "获取微信版本号:" + app.getWxVersion() + "\n" + 50 | "获取客户端系统:" + app.getPlatform() + "\n" + 51 | "系统版本:" + app.getSystem() + "\n"; 52 | that.setData({ 53 | textLog: log 54 | }); 55 | //获取当前设备平台以及微信版本 56 | if (app.getPlatform() == 'android' && utils.versionCompare('6.5.7', app.getWxVersion())) { 57 | wx.showModal({ 58 | title: '提示', 59 | content: '当前微信版本过低,请更新至最新版本', 60 | showCancel: false, 61 | success: function(res) { 62 | if (res.confirm) { 63 | that.backPage(); 64 | } 65 | } 66 | }) 67 | 68 | } else if (app.getPlatform() == 'android' && utils.versionCompare('4.3.0', app.getSystem())) { 69 | wx.showModal({ 70 | title: '提示', 71 | content: '当前系统版本过低,请更新至Android4.3以上版本', 72 | showCancel: false, 73 | success: function(res) { 74 | if (res.confirm) { 75 | that.backPage(); 76 | } 77 | } 78 | }) 79 | } else if (app.getPlatform() == 'ios' && utils.versionCompare('6.5.6', app.getWxVersion())) { 80 | wx.showModal({ 81 | title: '提示', 82 | content: '当前微信版本过低,请更新至最新版本', 83 | showCancel: false, 84 | success: function(res) { 85 | if (res.confirm) { 86 | that.backPage(); 87 | } 88 | } 89 | }) 90 | } 91 | 92 | }, 93 | 94 | onShow:function(){ 95 | // 在适合的场景显示插屏广告 96 | if (interstitialAd) { 97 | interstitialAd.show().catch((err) => { 98 | console.error(err) 99 | }) 100 | } 101 | }, 102 | 103 | /** 104 | * 生命周期函数--监听页面初次渲染完成 105 | */ 106 | onReady: function () { 107 | 108 | }, 109 | 110 | /** 111 | * 生命周期函数--监听页面卸载 112 | */ 113 | onUnload: function() { 114 | console.log("生命周期函数--监听页面卸载"); 115 | this.closeBluetoothAdapter(); //关闭蓝牙模块,使其进入未初始化状态。 116 | }, 117 | 118 | 119 | //退出页面 120 | backPage: function() { 121 | // wx.navigateBack({ 122 | // delta: -1 123 | // }) 124 | }, 125 | 126 | //清空log日志 127 | startClear: function() { 128 | var that = this; 129 | that.setData({ 130 | textLog: "" 131 | }); 132 | }, 133 | 134 | /** 135 | * 流程: 136 | * 1.先初始化蓝牙适配器, 137 | * 2.获取本机蓝牙适配器的状态, 138 | * 3.开始搜索,当停止搜索以后在开始搜索,就会触发蓝牙是配置状态变化的事件, 139 | * 4.搜索完成以后获取所有已经发现的蓝牙设备,就可以将devices中的设备Array取出来, 140 | * 5.然后就可以得到所有已经连接的设备了 141 | */ 142 | startScan: function() { 143 | var that = this; 144 | that._discoveryStarted = false; 145 | if (that.data.isopen) { //如果已初始化小程序蓝牙模块,则直接执行扫描 146 | that.getBluetoothAdapterState(); 147 | } else { 148 | that.openBluetoothAdapter(); 149 | } 150 | }, 151 | //初始化小程序蓝牙模块 152 | openBluetoothAdapter: function() { 153 | var that = this; 154 | wx.openBluetoothAdapter({ 155 | success: function(res) { 156 | var log = that.data.textLog + "打开蓝牙适配器成功!\n"; 157 | that.setData({ 158 | textLog: log, 159 | isopen: true 160 | }); 161 | that.getBluetoothAdapterState(); 162 | }, 163 | fail: function(err) { 164 | isopen: true; 165 | app.showModal1("蓝牙开关未开启"); 166 | var log = that.data.textLog + "蓝牙开关未开启 \n"; 167 | that.setData({ 168 | textLog: log 169 | }); 170 | } 171 | }) 172 | //监听蓝牙适配器状态变化事件 173 | wx.onBluetoothAdapterStateChange(function(res) { 174 | console.log('onBluetoothAdapterStateChange', res) 175 | var isDvailable = res.available; //蓝牙适配器是否可用 176 | if (isDvailable) { 177 | that.getBluetoothAdapterState(); 178 | } else { 179 | that.stopBluetoothDevicesDiscovery(); //停止搜索 180 | that.setData({ 181 | devices: [] 182 | }); 183 | app.showModal1("蓝牙开关未开启"); 184 | } 185 | }) 186 | }, 187 | //关闭蓝牙模块,使其进入未初始化状态。 188 | closeBluetoothAdapter: function() { 189 | wx.closeBluetoothAdapter() 190 | this._discoveryStarted = false 191 | }, 192 | //获取本机蓝牙适配器状态 193 | getBluetoothAdapterState: function() { 194 | var that = this; 195 | wx.getBluetoothAdapterState({ 196 | success: function(res) { 197 | var isDiscov = res.discovering; //是否正在搜索设备 198 | var isDvailable = res.available; //蓝牙适配器是否可用 199 | if (isDvailable) { 200 | var log = that.data.textLog + "本机蓝牙适配器状态:可用 \n"; 201 | that.setData({ 202 | textLog: log 203 | }); 204 | if (!isDiscov) { 205 | that.startBluetoothDevicesDiscovery(); 206 | } else { 207 | var log = that.data.textLog + "已在搜索设备 \n"; 208 | that.setData({ 209 | textLog: log 210 | }); 211 | } 212 | } 213 | } 214 | }) 215 | }, 216 | //开始扫描附近的蓝牙外围设备。 217 | //注意,该操作比较耗费系统资源,请在搜索并连接到设备后调用 stop 方法停止搜索。 218 | startBluetoothDevicesDiscovery: function() { 219 | var that = this; 220 | if (that._discoveryStarted) { 221 | return 222 | } 223 | that._discoveryStarted = true; 224 | app.showLoading("正在扫描.."); 225 | var log = that.data.textLog + "正在扫描..\n"; 226 | that.setData({ 227 | textLog: log 228 | }); 229 | setTimeout(function() { 230 | wx.hideLoading(); //隐藏loading 231 | }, 3000); 232 | wx.startBluetoothDevicesDiscovery({ 233 | services: [], 234 | allowDuplicatesKey: true, //是否允许重复上报同一设备, 如果允许重复上报,则onDeviceFound 方法会多次上报同一设备,但是 RSSI(信号) 值会有不同 235 | success: function(res) { 236 | var log = that.data.textLog + "扫描附近的蓝牙外围设备成功,准备监听寻找新设备:" + res + "\n"; 237 | that.setData({ 238 | textLog: log 239 | }); 240 | that.onBluetoothDeviceFound(); //监听寻找到新设备的事件 241 | } 242 | }); 243 | 244 | }, 245 | 246 | 247 | //停止搜寻附近的蓝牙外围设备。若已经找到需要的蓝牙设备并不需要继续搜索时,建议调用该接口停止蓝牙搜索。 248 | stopBluetoothDevicesDiscovery: function() { 249 | var that = this; 250 | var log = that.data.textLog + "停止搜寻附近的蓝牙外围设备 \n"; 251 | that.setData({ 252 | textLog: log 253 | }); 254 | wx.stopBluetoothDevicesDiscovery() 255 | }, 256 | //监听寻找到新设备的事件 257 | onBluetoothDeviceFound: function() { 258 | var that = this; 259 | wx.onBluetoothDeviceFound(function(res) { 260 | res.devices.forEach(function(device) { 261 | if (!device.name && !device.localName) { 262 | return 263 | } 264 | const foundDevices = that.data.devices; 265 | const idx = inArray(foundDevices, 'deviceId', device.deviceId); 266 | const data = {}; 267 | if (idx === -1) { 268 | data[`devices[${foundDevices.length}]`] = device 269 | } else { 270 | data[`devices[${idx}]`] = device 271 | } 272 | that.setData(data) 273 | }) 274 | }) 275 | }, 276 | 277 | //连接低功耗蓝牙设备。 278 | createBLEConnection: function(e) { 279 | var that = this; 280 | const ds = e.currentTarget.dataset; 281 | const devId = ds.deviceId; //设备UUID 282 | const name = ds.name; //设备名 283 | // that.stopConnectDevices(); //配对之前先断开已连接设备 284 | // app.showLoading("正在连接,请稍后"); 285 | var log = that.data.textLog + "正在连接,请稍后..\n"; 286 | that.setData({ 287 | textLog: log 288 | }); 289 | app.showLoading("连接中.."); 290 | wx.createBLEConnection({ 291 | deviceId: devId, 292 | success: function(res) { 293 | wx.hideLoading(); //隐藏loading 294 | var log = that.data.textLog + "配对成功,获取服务..\n"; 295 | that.setData({ 296 | textLog: log, 297 | connected: true, 298 | name, 299 | devId, 300 | }); 301 | that.getBLEDeviceServices(devId) 302 | }, 303 | 304 | fail: function(err) { 305 | wx.hideLoading(); //隐藏loading 306 | var log = that.data.textLog + "连接失败,错误码:" + err.errCode + "\n"; 307 | that.setData({ 308 | textLog: log 309 | }); 310 | 311 | if (err.errCode === 10012) { 312 | app.showModal1("连接超时,请重试!"); 313 | } else if (err.errCode === 10013) { 314 | app.showModal1("连接失败,蓝牙地址无效!"); 315 | } else { 316 | app.showModal1("连接失败,请重试!"); // + err.errCode10003原因多种:蓝牙设备未开启或异常导致无法连接;蓝牙设备被占用或者上次蓝牙连接未断开导致无法连接 317 | } 318 | 319 | that.closeBLEConnection() 320 | }, 321 | 322 | }); 323 | that.stopBluetoothDevicesDiscovery(); //停止搜索 324 | }, 325 | //断开与低功耗蓝牙设备的连接 326 | closeBLEConnection: function() { 327 | wx.closeBLEConnection({ 328 | deviceId: this.data.devId 329 | }) 330 | this.setData({ 331 | connected: false, 332 | chs: [], 333 | canWrite: false, 334 | }) 335 | }, 336 | //获取蓝牙设备所有 service(服务) 337 | getBLEDeviceServices: function(devId) { 338 | var that = this; 339 | wx.getBLEDeviceServices({ 340 | deviceId: devId, 341 | success: function(res) { 342 | for (let i = 0; i < res.services.length; i++) { 343 | if (res.services[i].isPrimary) { //该服务是否为主服务 344 | var s = res.services[i].uuid; 345 | var log = that.data.textLog + "该服务是为主服务:" + res.services[i].uuid + "\n"; 346 | that.setData({ 347 | textLog: log 348 | }); 349 | wx.navigateTo({ 350 | url: '/pages/functionPage/functionPage?name=' + encodeURIComponent(that.data.name) + '&deviceId=' + encodeURIComponent(devId) + '&serviceId=' + encodeURIComponent(res.services[i].uuid) 351 | }); 352 | return 353 | } 354 | } 355 | } 356 | }) 357 | }, 358 | 359 | //关于 360 | aboutClick: function() { 361 | wx.navigateTo({ 362 | url: '/pages/about/about' 363 | }); 364 | } 365 | 366 | 367 | }) -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports.max = function(n1, n2) { 4 | return Math.max(n1, n2) 5 | } 6 | module.exports.len = function(arr) { 7 | arr = arr || [] 8 | return arr.length 9 | } 10 | 11 | 12 | 13 | 14 | 展示log日志(可滑动查看): 15 | 16 | 17 | {{textLog}} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 已发现 {{devices.length}} 个外围设备: 27 | 28 | 29 | {{item.name}} 30 | 信号强度: {{item.RSSI}}dBm ({{utils.max(0, item.RSSI + 100)}}%) 31 | UUID: {{item.deviceId}} 32 | Service数量: {{utils.len(item.advertisServiceUUIDs)}} 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /* pages/index/index.wxss */ 2 | .log { 3 | width: 90%; 4 | left: 5%; 5 | position: relative; 6 | margin-top: 10rpx; 7 | } 8 | .scroll-list { 9 | max-height: 300rpx; 10 | } 11 | 12 | .scan-view { 13 | width: 90%; 14 | margin-left: 5%; 15 | margin-top: 10rpx; 16 | display: flex; 17 | flex-direction: row; 18 | justify-content: space-between 19 | } 20 | .scan-view button { 21 | position:relative; 22 | display:block; 23 | margin-left:auto; 24 | margin-right:auto; 25 | padding-left:14px; 26 | padding-right:14px; 27 | box-sizing:border-box; 28 | font-size:18px; 29 | width: 48%; 30 | } 31 | 32 | .devices_summary { 33 | margin-top: 30px; 34 | padding: 10px 5%; 35 | font-size: 16px; 36 | } 37 | .device_list { 38 | height: 400rpx; 39 | margin: 50px 5%; 40 | margin-top: 0; 41 | border: 1px solid #EEE; 42 | border-radius: 5px; 43 | width: auto; 44 | } 45 | .device_item { 46 | border-bottom: 1px solid #EEE; 47 | padding: 10px; 48 | color: #666; 49 | } 50 | .device_item_hover { 51 | background-color: rgba(0, 0, 0, .1); 52 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "enhance": false, 10 | "postcss": true, 11 | "preloadBackgroundData": false, 12 | "minified": true, 13 | "newFeature": true, 14 | "coverView": true, 15 | "nodeModules": false, 16 | "autoAudits": false, 17 | "showShadowRootInWxmlPanel": true, 18 | "scopeDataCheck": false, 19 | "uglifyFileName": false, 20 | "checkInvalidKey": true, 21 | "checkSiteMap": true, 22 | "uploadWithSourceMap": true, 23 | "compileHotReLoad": false, 24 | "useMultiFrameRuntime": true, 25 | "useApiHook": true, 26 | "useApiHostProcess": false, 27 | "babelSetting": { 28 | "ignore": [], 29 | "disablePlugins": [], 30 | "outputPath": "" 31 | }, 32 | "enableEngineNative": false, 33 | "bundle": false, 34 | "useIsolateContext": true, 35 | "useCompilerModule": true, 36 | "userConfirmedUseCompilerModuleSwitch": false, 37 | "userConfirmedBundleSwitch": false, 38 | "packNpmManually": false, 39 | "packNpmRelationList": [], 40 | "minifyWXSS": true 41 | }, 42 | "compileType": "miniprogram", 43 | "libVersion": "2.3.0", 44 | "appid": "wx54d497a8dd210ed9", 45 | "projectname": "BlueToothDemo", 46 | "isGameTourist": false, 47 | "simulatorType": "wechat", 48 | "simulatorPluginLibVersion": {}, 49 | "condition": { 50 | "plugin": { 51 | "list": [] 52 | }, 53 | "game": { 54 | "list": [] 55 | }, 56 | "gamePlugin": { 57 | "list": [] 58 | }, 59 | "miniprogram": { 60 | "list": [ 61 | { 62 | "name": "pages/functionPage/functionPage", 63 | "pathName": "pages/functionPage/functionPage", 64 | "scene": null 65 | } 66 | ] 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessLuo/BlueToothTool/56df4ad518ec83e392cf449dabea2947253e7870/screenshots/1.jpg -------------------------------------------------------------------------------- /screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessLuo/BlueToothTool/56df4ad518ec83e392cf449dabea2947253e7870/screenshots/2.jpg -------------------------------------------------------------------------------- /screenshots/myQrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessLuo/BlueToothTool/56df4ad518ec83e392cf449dabea2947253e7870/screenshots/myQrcode.jpg -------------------------------------------------------------------------------- /screenshots/wcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChessLuo/BlueToothTool/56df4ad518ec83e392cf449dabea2947253e7870/screenshots/wcode.jpg -------------------------------------------------------------------------------- /sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /utils/data.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var mobApi = { 4 | baseUrl: "https://apicloud.mob.com/v1/postcode/", 5 | myKey:"2820369744a80", 6 | codeToQuery : function(code){//邮编查询地址 7 | return this.baseUrl +"query?key="+this.myKey+"&code="+code; 8 | }, 9 | getAllCity : function(){//获取全国城市数据 10 | return this.baseUrl +"pcd?key="+this.myKey; 11 | }, 12 | queryCityCode:function(){ 13 | return this.baseUrl +"search?key=2820369744a80&pid=40&cid=4001&word=安康"; 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | // 字符串转byte 2 | function stringToBytes(str) { 3 | var strArray = new Uint8Array(str.length); 4 | for (var i = 0; i < str.length; i++) { 5 | strArray[i] = str.charCodeAt(i); 6 | } 7 | const array = new Uint8Array(strArray.length) 8 | strArray.forEach((item, index) => array[index] = item) 9 | return array.buffer; 10 | } 11 | 12 | // ArrayBuffer转16进制字符串示例 13 | function ab2hext(buffer) { 14 | var hexArr = Array.prototype.map.call( 15 | new Uint8Array(buffer), 16 | function (bit) { 17 | return ('00' + bit.toString(16)).slice(-2) 18 | } 19 | ) 20 | return hexArr.join(''); 21 | } 22 | 23 | //16进制转字符串 24 | function hexToString(str) { 25 | var trimedStr = str.trim(); 26 | var rawStr = 27 | trimedStr.substr(0, 2).toLowerCase() === "0x" ? 28 | trimedStr.substr(2) : 29 | trimedStr; 30 | var len = rawStr.length; 31 | if (len % 2 !== 0) { 32 | // alert("Illegal Format ASCII Code!"); 33 | return ""; 34 | } 35 | var curCharCode; 36 | var resultStr = []; 37 | for (var i = 0; i < len; i = i + 2) { 38 | curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value 39 | resultStr.push(String.fromCharCode(curCharCode)); 40 | } 41 | return resultStr.join(""); 42 | } 43 | 44 | /*微信app版本比较*/ 45 | function versionCompare(ver1, ver2) { 46 | var version1pre = parseFloat(ver1) 47 | var version2pre = parseFloat(ver2) 48 | var version1next = parseInt(ver1.replace(version1pre + ".", "")) 49 | var version2next = parseInt(ver2.replace(version2pre + ".", "")) 50 | if (version1pre > version2pre) 51 | return true 52 | else if (version1pre < version2pre) 53 | return false 54 | else { 55 | if (version1next > version2next) 56 | return true 57 | else 58 | return false 59 | } 60 | } 61 | 62 | module.exports = { 63 | stringToBytes: stringToBytes, 64 | ab2hext: ab2hext, 65 | hexToString: hexToString, 66 | versionCompare: versionCompare 67 | } 68 | --------------------------------------------------------------------------------