├── 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 | * 
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------