├── README.md
├── app.js
├── app.json
├── app.wxss
├── pages
├── index
│ ├── index.js
│ ├── index.wxml
│ └── index.wxss
├── logs
│ ├── logs.js
│ ├── logs.json
│ ├── logs.wxml
│ └── logs.wxss
└── scan
│ ├── scan.js
│ ├── scan.json
│ ├── scan.wxml
│ └── scan.wxss
├── project.config.json
└── utils
└── util.js
/README.md:
--------------------------------------------------------------------------------
1 | # wechat-ble-demo
2 | 微信小程序蓝牙测试
3 |
4 | 相关说明文章:http://blog.csdn.net/lablenet/article/details/78492824
5 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | // wx.getSetting({
17 | // success: res => {
18 | // if (res.authSetting['scope.userInfo']) {
19 | // // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | // wx.getUserInfo({
21 | // success: res => {
22 | // // 可以将 res 发送给后台解码出 unionId
23 | // this.globalData.userInfo = res.userInfo
24 |
25 | // // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // // 所以此处加入 callback 以防止这种情况
27 | // if (this.userInfoReadyCallback) {
28 | // this.userInfoReadyCallback(res)
29 | // }
30 | // }
31 | // })
32 | // }
33 | // }
34 | // })
35 | },
36 | globalData: {
37 | userInfo: null
38 | }
39 | })
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/scan/scan",
4 | "pages/index/index",
5 | "pages/logs/logs"
6 | ],
7 | "window": {
8 | "backgroundTextStyle": "light",
9 | "navigationBarBackgroundColor": "#fff",
10 | "navigationBarTitleText": "WeChat",
11 | "navigationBarTextStyle": "black"
12 | }
13 | }
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | motto: 'Hello World',
8 | userInfo: {},
9 | hasUserInfo: false,
10 | canIUse: wx.canIUse('button.open-type.getUserInfo')
11 | },
12 | //事件处理函数
13 | bindViewTap: function() {
14 | wx.navigateTo({
15 | url: '../logs/logs'
16 | })
17 | },
18 | onLoad: function () {
19 | if (app.globalData.userInfo) {
20 | this.setData({
21 | userInfo: app.globalData.userInfo,
22 | hasUserInfo: true
23 | })
24 | } else if (this.data.canIUse){
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | app.userInfoReadyCallback = res => {
28 | this.setData({
29 | userInfo: res.userInfo,
30 | hasUserInfo: true
31 | })
32 | }
33 | } else {
34 | // 在没有 open-type=getUserInfo 版本的兼容处理
35 | wx.getUserInfo({
36 | success: res => {
37 | app.globalData.userInfo = res.userInfo
38 | this.setData({
39 | userInfo: res.userInfo,
40 | hasUserInfo: true
41 | })
42 | }
43 | })
44 | }
45 | },
46 | getUserInfo: function(e) {
47 | console.log(e)
48 | app.globalData.userInfo = e.detail.userInfo
49 | this.setData({
50 | userInfo: e.detail.userInfo,
51 | hasUserInfo: true
52 | })
53 | }
54 | })
55 |
--------------------------------------------------------------------------------
/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{userInfo.nickName}}
8 |
9 |
10 |
11 |
12 | {{motto}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | .userinfo {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | }
7 |
8 | .userinfo-avatar {
9 | width: 128rpx;
10 | height: 128rpx;
11 | margin: 20rpx;
12 | border-radius: 50%;
13 | }
14 |
15 | .userinfo-nickname {
16 | color: #aaa;
17 | }
18 |
19 | .usermotto {
20 | margin-top: 200px;
21 | }
--------------------------------------------------------------------------------
/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function () {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return util.formatTime(new Date(log))
12 | })
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志"
3 | }
--------------------------------------------------------------------------------
/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/pages/scan/scan.js:
--------------------------------------------------------------------------------
1 | // pages/scan/scan.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 | bleStatus:"蓝牙未打开",
9 | bleAdapterStatus:"未初始化",
10 | bleChipInfo:{},
11 | bleChips:[],
12 | bleConnSuccess:false,
13 | bleNotifyData:"未读取数据"
14 | },
15 |
16 | /**
17 | * 开始扫描
18 | */
19 | onScanClick:function(event){
20 | console.log('扫描开始')
21 | let self = this
22 | wx.openBluetoothAdapter({
23 | success: function(res) {
24 | // 扫描蓝牙
25 | self.bleDisCovery()
26 | self.setData({
27 | bleAdapterStatus:"初始化成功"
28 | })
29 | },
30 | fail:function(error){
31 | self.setData({
32 | bleAdapterStatus: "初始化失败"
33 | })
34 | wx.showModal({
35 | showCancel: false,
36 | title: '提示',
37 | content: '设备蓝牙未打开,请打开蓝牙功能',
38 | success: function (res) {
39 | if (res.confirm) {
40 | //console.log('用户点击确定')
41 | }
42 | }
43 | });
44 | },
45 | complete:function(){
46 | //console.log('complete')
47 | }
48 | });
49 | },
50 | /**
51 | * 解析数据信息
52 | */
53 | bleFound:function(){
54 | console.log("发现设备信息")
55 | let self =this
56 | wx.onBluetoothDeviceFound(function (res) {
57 | let devices = res.devices
58 | console.log(devices)
59 | let length = self.data.bleChips.length
60 | let devicesLength = devices.length
61 | if (devicesLength > length){
62 | self.data.bleChips = devices
63 | self.setData({
64 | bleChips: devices
65 | });
66 | }
67 | console.log(self.data.bleChips)
68 | });
69 |
70 | },
71 | /**
72 | * 扫描设备
73 | */
74 | bleDisCovery:function(){
75 | console.log("扫描蓝牙")
76 | let self = this
77 | wx.startBluetoothDevicesDiscovery({
78 | interval:1000,
79 | success: function(res){
80 | self.bleFound();
81 | }
82 | });
83 | },
84 | /**
85 | * 初始化蓝牙
86 | */
87 | bleInit:function(){
88 | console.log('初始化蓝牙')
89 | let self = this
90 | wx.openBluetoothAdapter({
91 | success: function(res) {
92 | self.setData({
93 | bleAdapterStatus: "初始化成功"
94 | })
95 | },
96 | fail:function(msg){
97 | self.setData({
98 | bleAdapterStatus: "初始化失败"
99 | })
100 | wx.showModal({
101 | showCancel:false,
102 | title: '提示',
103 | content: '设备蓝牙未打开,请打开蓝牙功能',
104 | success:function(res){
105 | if (res.confirm) {
106 | //console.log('用户点击确定')
107 | // 退出小程序
108 | }
109 | }
110 | });
111 | }
112 | });
113 | },
114 | /**
115 | * 蓝牙设备监听
116 | */
117 | bleStatusListener:function(){
118 | // 监听蓝牙状态
119 | let slef =this
120 | wx.onBluetoothAdapterStateChange(function (res) {
121 | console.log(`adapterState changed, now is`, res)
122 | if (res.available){
123 | // 是否可用
124 | console.log("蓝牙状态以改变!")
125 | slef.setData({
126 | bleStatus: "蓝牙已打开"
127 | });
128 | }else{
129 | slef.setData({
130 | bleStatus: "蓝牙已关闭"
131 | });
132 | // 不可用时
133 | wx.showModal({
134 | showCancel: false,
135 | title: '提示',
136 | content: '设备蓝牙未打开,请打开蓝牙功能',
137 | success: function (res) {
138 | if (res.confirm) {
139 | // console.log('用户点击确定')
140 | // 退出小程序
141 | }
142 | }
143 | });
144 | }
145 | });
146 | },
147 | onConnBle:function(e){
148 | // 停止扫描
149 | wx.stopBluetoothDevicesDiscovery({
150 | success: function(res) {
151 | },
152 | });
153 | // 接收点击事件的参数
154 | let device = e.currentTarget.dataset.item
155 | console.log(`conn ble >> ${device}`)
156 | this.setData({
157 | bleChipInfo: device
158 | })
159 | let deviceId = device.deviceId
160 | let self = this
161 | // 连接设备
162 | console.log("连接设备中...")
163 | wx.createBLEConnection({
164 | deviceId: deviceId,
165 | success: function(res) {
166 | wx.showToast({
167 | title: '连接成功',
168 | });
169 | // 连接成功,打开 notify
170 | setTimeout(function(){
171 | self.bleServices(deviceId)
172 | },1500)
173 |
174 | },
175 | fail:function(errMsg){
176 | wx.showToast({
177 | title: `连接失败:${errMsg}`,
178 | })
179 | }
180 | });
181 | },
182 | bleServices: function (deviceId){
183 | let self = this
184 | wx.getBLEDeviceServices({
185 | deviceId: deviceId,
186 | success: function (res) {
187 | wx.showToast({
188 | title: 'service success',
189 | })
190 | let services = res.services
191 | for(let index in services){
192 | let service= services[index]
193 | console.log(service)
194 | if (service.uuid === '49535343-FE7D-4AE5-8FA9-9FAFD205E455'){
195 | console.log("have service: 49535343-FE7D-4AE5-8FA9-9FAFD205E455")
196 | self.bleServiceChart(deviceId, service.uuid)
197 | }
198 | }
199 | console.log('device services:', res.services)
200 | }
201 | })
202 | },
203 | bleServiceChart: function (deviceId,serviceId){
204 | let self = this;
205 | wx.getBLEDeviceCharacteristics({
206 | // 这里的 deviceId 需要在上面的 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
207 | deviceId: deviceId,
208 | // 这里的 serviceId 需要在上面的 getBLEDeviceServices 接口中获取
209 | serviceId: serviceId,
210 | success: function (res) {
211 | console.log('device getBLEDeviceCharacteristics:', res.characteristics)
212 | let characteristics = res.characteristics
213 | for (let index in characteristics){
214 | let characteristic = characteristics[index]
215 | if (characteristic.uuid === '49535343-1E4D-4BD9-BA61-23C647249616'){
216 | console.log("have characteristic: 49535343-1E4D-4BD9-BA61-23C647249616")
217 | }
218 | console.log(characteristic)
219 | }
220 | self.openNotify(deviceId)
221 | }
222 | })
223 | },
224 | openNotify: function (deviceId) {
225 | this.setData({
226 | bleConnSuccess: true
227 | });
228 | let self = this
229 | wx.notifyBLECharacteristicValueChange({
230 | deviceId: deviceId,
231 | serviceId: '49535343-FE7D-4AE5-8FA9-9FAFD205E455',
232 | characteristicId: '49535343-1E4D-4BD9-BA61-23C647249616',
233 | state: true,
234 | success: function (res) {
235 | console.log('notify success')
236 | self.onNotifyChange()
237 | wx.showToast({
238 | title: 'notify success',
239 | });
240 | },
241 | fail: function (err) {
242 | console.log(err)
243 | wx.showToast({
244 | title: 'notify fail',
245 | });
246 | }
247 | });
248 | },
249 | onNotifyChange:function(){
250 | // 接收数据
251 | let self = this
252 | wx.onBLECharacteristicValueChange(function (res) {
253 | console.log(res.characteristicId)
254 | let byteDatas = Array.from(new Int8Array(res.value))
255 | console.log(byteDatas)
256 | const data = byteDatas.join(',')
257 | self.setData({
258 | bleNotifyData:data
259 | });
260 | console.log(data)
261 | });
262 | },
263 |
264 | /**
265 | * 生命周期函数--监听页面加载
266 | */
267 | onLoad: function (options) {
268 | if (wx.openBluetoothAdapter) {
269 | wx.openBluetoothAdapter()
270 | } else {
271 | wx.showModal({
272 | title: '提示',
273 | content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
274 | })
275 | }
276 | },
277 |
278 |
279 | /**
280 | * 生命周期函数--监听页面初次渲染完成
281 | */
282 | onReady: function () {
283 | // 监听蓝牙
284 | this.bleStatusListener()
285 | },
286 |
287 | /**
288 | * 生命周期函数--监听页面显示
289 | */
290 | onShow: function () {
291 | // 初始化蓝牙
292 | this.bleInit()
293 | },
294 |
295 | /**
296 | * 生命周期函数--监听页面隐藏
297 | */
298 | onHide: function () {
299 |
300 | },
301 |
302 | /**
303 | * 生命周期函数--监听页面卸载
304 | */
305 | onUnload: function () {
306 | wx.closeBluetoothAdapter({
307 | success: function(res) {
308 |
309 | },
310 | });
311 | },
312 |
313 | /**
314 | * 页面相关事件处理函数--监听用户下拉动作
315 | */
316 | onPullDownRefresh: function () {
317 |
318 | },
319 |
320 | /**
321 | * 页面上拉触底事件的处理函数
322 | */
323 | onReachBottom: function () {
324 |
325 | },
326 |
327 | /**
328 | * 用户点击右上角分享
329 | */
330 | onShareAppMessage: function () {
331 |
332 | }
333 | })
--------------------------------------------------------------------------------
/pages/scan/scan.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "蓝牙测试"
3 | }
--------------------------------------------------------------------------------
/pages/scan/scan.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 蓝牙状态监听:{{bleStatus}}
6 | 蓝牙初始化:{{bleAdapterStatus}}
7 | 当前连接的设备:{{bleChipInfo.name}}-{{bleChipInfo.deviceId}}
8 |
9 |
10 |
11 | 扫描结果
12 |
13 |
14 | {{index}}-{{item.name}}-{{item.deviceId}}
15 |
16 |
17 |
18 |
19 | notify data:
20 | {{bleNotifyData}}
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pages/scan/scan.wxss:
--------------------------------------------------------------------------------
1 | /* pages/scan/scan.wxss */
2 | .scan_view{
3 | margin: 16px;
4 | }
5 | .result{
6 | padding: 16px;
7 | direction: flex;
8 | flex-direction: column;
9 | background-color: gainsboro;
10 | box-shadow: 0 3px 3px 0 gainsboro;
11 | height:100px;
12 | width: 90%;
13 | flex-wrap:nowrap;
14 | }
15 |
16 | .result_text{
17 | width: 100%;
18 | height: 20px;
19 | color: black;
20 | font-size: 14px;
21 | display: block;
22 | }
23 |
24 | .btn_scan{
25 | margin-top: 16px
26 | }
27 |
28 | .scan_result_title{
29 | color: black;
30 | font-size: 14px;
31 | display: block;
32 | margin-top: 16px;
33 | }
34 | .scan_result{
35 | display: flex;
36 | flex-direction: row;
37 | padding: 16px;
38 | background-color: gainsboro;
39 | height: 50px;
40 | margin-top: 8px;
41 | }
42 |
43 | .notify_result{
44 | display: flex;
45 | flex-direction: column;
46 | background-color: gainsboro;
47 | box-shadow: 0 3px 3px 0 gainsboro;
48 | margin: 16px;
49 | padding: 16px;
50 | }
51 |
52 | .notify_result_data{
53 | font-size: 12px;
54 | display: block;
55 | }
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "setting": {
4 | "urlCheck": true,
5 | "es6": true,
6 | "postcss": true,
7 | "minified": true,
8 | "newFeature": true
9 | },
10 | "compileType": "miniprogram",
11 | "libVersion": "1.6.4",
12 | "appid": "wx3c5fc86bd5706314",
13 | "projectname": "%E7%9D%BF%E7%95%9C%E4%BF%9DDev",
14 | "condition": {
15 | "search": {
16 | "current": -1,
17 | "list": []
18 | },
19 | "conversation": {
20 | "current": -1,
21 | "list": []
22 | },
23 | "miniprogram": {
24 | "current": -1,
25 | "list": []
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------