├── .gitignore
├── README.md
├── app.js
├── app.json
├── app.wxss
├── images
└── bluetooth.png
├── pages
├── device
│ ├── device.js
│ ├── device.json
│ ├── device.wxml
│ └── device.wxss
└── search
│ ├── search.js
│ ├── search.json
│ ├── search.wxml
│ └── search.wxss
└── utils
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | project.config.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 原帖地址
2 | https://www.cnblogs.com/HintLee/p/9499423.html
3 | ## 说明
4 | 鸽了很久终于来完善一下,此源码适用于微信小程序。之前研究小程序的蓝牙踩了不少坑,重新提取了相关内容作了一个 Demo,有搜索设备、连接设备、写入内容、notify 读取内容等功能,在 BLE 串口透传模块上测试通过。
5 | ## 注意
6 | search 页面基本不需改动可直接使用,device 页面中固定了 services 和 characteristics,发送和接收都是字符串,根据需求修改即可。
7 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | buf2hex: function (buffer) {
4 | return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('')
5 | },
6 | buf2string: function (buffer) {
7 | var arr = Array.prototype.map.call(new Uint8Array(buffer), x => x)
8 | var str = ''
9 | for (var i = 0; i < arr.length; i++) {
10 | str += String.fromCharCode(arr[i])
11 | }
12 | return str
13 | },
14 | onLaunch: function () {
15 | this.globalData.SystemInfo = wx.getSystemInfoSync()
16 | //console.log(this.globalData.SystemInfo)
17 | },
18 | globalData: {
19 | SystemInfo: {}
20 | }
21 | })
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/search/search",
4 | "pages/device/device"
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#f8f8f8",
9 | "navigationBarTitleText": "BLE Demo",
10 | "navigationBarTextStyle": "black",
11 | "backgroundColor": "#f8f8f8"
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 |
--------------------------------------------------------------------------------
/images/bluetooth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lixiantai/BLE_MiniProgram/e2c3411a2b6ae93fe0b4bd7dc20437403777d489/images/bluetooth.png
--------------------------------------------------------------------------------
/pages/device/device.js:
--------------------------------------------------------------------------------
1 | const app = getApp()
2 | Page({
3 | data: {
4 | inputText: 'Hello World!',
5 | receiveText: '',
6 | name: '',
7 | connectedDeviceId: '',
8 | services: {},
9 | characteristics: {},
10 | connected: true
11 | },
12 | bindInput: function (e) {
13 | this.setData({
14 | inputText: e.detail.value
15 | })
16 | console.log(e.detail.value)
17 | },
18 | Send: function () {
19 | var that = this
20 | if (that.data.connected) {
21 | var buffer = new ArrayBuffer(that.data.inputText.length)
22 | var dataView = new Uint8Array(buffer)
23 | for (var i = 0; i < that.data.inputText.length; i++) {
24 | dataView[i] = that.data.inputText.charCodeAt(i)
25 | }
26 |
27 | wx.writeBLECharacteristicValue({
28 | deviceId: that.data.connectedDeviceId,
29 | serviceId: that.data.services[0].uuid,
30 | characteristicId: that.data.characteristics[0].uuid,
31 | value: buffer,
32 | success: function (res) {
33 | console.log('发送成功')
34 | }
35 | })
36 | }
37 | else {
38 | wx.showModal({
39 | title: '提示',
40 | content: '蓝牙已断开',
41 | showCancel: false,
42 | success: function (res) {
43 | that.setData({
44 | searching: false
45 | })
46 | }
47 | })
48 | }
49 | },
50 | onLoad: function (options) {
51 | var that = this
52 | console.log(options)
53 | that.setData({
54 | name: options.name,
55 | connectedDeviceId: options.connectedDeviceId
56 | })
57 | wx.getBLEDeviceServices({
58 | deviceId: that.data.connectedDeviceId,
59 | success: function (res) {
60 | console.log(res.services)
61 | that.setData({
62 | services: res.services
63 | })
64 | wx.getBLEDeviceCharacteristics({
65 | deviceId: options.connectedDeviceId,
66 | serviceId: res.services[0].uuid,
67 | success: function (res) {
68 | console.log(res.characteristics)
69 | that.setData({
70 | characteristics: res.characteristics
71 | })
72 | wx.notifyBLECharacteristicValueChange({
73 | state: true,
74 | deviceId: options.connectedDeviceId,
75 | serviceId: that.data.services[0].uuid,
76 | characteristicId: that.data.characteristics[0].uuid,
77 | success: function (res) {
78 | console.log('启用notify成功')
79 | }
80 | })
81 | }
82 | })
83 | }
84 | })
85 | wx.onBLEConnectionStateChange(function (res) {
86 | console.log(res.connected)
87 | that.setData({
88 | connected: res.connected
89 | })
90 | })
91 | wx.onBLECharacteristicValueChange(function (res) {
92 | var receiveText = app.buf2string(res.value)
93 | console.log('接收到数据:' + receiveText)
94 | that.setData({
95 | receiveText: receiveText
96 | })
97 | })
98 | },
99 | onReady: function () {
100 |
101 | },
102 | onShow: function () {
103 |
104 | },
105 | onHide: function () {
106 |
107 | }
108 | })
--------------------------------------------------------------------------------
/pages/device/device.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pages/device/device.wxml:
--------------------------------------------------------------------------------
1 |
2 | 设备名称:{{name}}
3 | 设备ID:{{connectedDeviceId}}
4 | 状态:{{connected?"已连接":"已断开"}}
5 | 发送内容:
6 |
7 | 接收内容:
8 |
9 |
10 |
--------------------------------------------------------------------------------
/pages/device/device.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | background-color: #f8f8f8;
3 | }
4 | .container {
5 | padding: 30rpx;
6 | align-items: left;
7 | }
8 | .input {
9 | margin-top:3px;
10 | width: 100%;
11 | border: 1px solid lightgray;
12 | border-radius: 6px;
13 | }
14 | .button {
15 | position: fixed;
16 | width: 690rpx;
17 | bottom: 30rpx;
18 | }
--------------------------------------------------------------------------------
/pages/search/search.js:
--------------------------------------------------------------------------------
1 | const app = getApp()
2 | Page({
3 | data: {
4 | searching: false,
5 | devicesList: []
6 | },
7 | Search: function () {
8 | var that = this
9 | if (!that.data.searching) {
10 | wx.closeBluetoothAdapter({
11 | complete: function (res) {
12 | console.log(res)
13 | wx.openBluetoothAdapter({
14 | success: function (res) {
15 | console.log(res)
16 | wx.getBluetoothAdapterState({
17 | success: function (res) {
18 | console.log(res)
19 | }
20 | })
21 | wx.startBluetoothDevicesDiscovery({
22 | allowDuplicatesKey: false,
23 | success: function (res) {
24 | console.log(res)
25 | that.setData({
26 | searching: true,
27 | devicesList: []
28 | })
29 | }
30 | })
31 | },
32 | fail: function (res) {
33 | console.log(res)
34 | wx.showModal({
35 | title: '提示',
36 | content: '请检查手机蓝牙是否打开',
37 | showCancel: false,
38 | success: function (res) {
39 | that.setData({
40 | searching: false
41 | })
42 | }
43 | })
44 | }
45 | })
46 | }
47 | })
48 | }
49 | else {
50 | wx.stopBluetoothDevicesDiscovery({
51 | success: function (res) {
52 | console.log(res)
53 | that.setData({
54 | searching: false
55 | })
56 | }
57 | })
58 | }
59 | },
60 | Connect: function (e) {
61 | var that = this
62 | var advertisData, name
63 | console.log(e.currentTarget.id)
64 | for (var i = 0; i < that.data.devicesList.length; i++) {
65 | if (e.currentTarget.id == that.data.devicesList[i].deviceId) {
66 | name = that.data.devicesList[i].name
67 | advertisData = that.data.devicesList[i].advertisData
68 | }
69 | }
70 | wx.stopBluetoothDevicesDiscovery({
71 | success: function (res) {
72 | console.log(res)
73 | that.setData({
74 | searching: false
75 | })
76 | }
77 | })
78 | wx.showLoading({
79 | title: '连接蓝牙设备中...',
80 | })
81 | wx.createBLEConnection({
82 | deviceId: e.currentTarget.id,
83 | success: function (res) {
84 | console.log(res)
85 | wx.hideLoading()
86 | wx.showToast({
87 | title: '连接成功',
88 | icon: 'success',
89 | duration: 1000
90 | })
91 | wx.navigateTo({
92 | url: '../device/device?connectedDeviceId=' + e.currentTarget.id + '&name=' + name
93 | })
94 | },
95 | fail: function (res) {
96 | console.log(res)
97 | wx.hideLoading()
98 | wx.showModal({
99 | title: '提示',
100 | content: '连接失败',
101 | showCancel: false
102 | })
103 | }
104 | })
105 | },
106 | onLoad: function (options) {
107 | var that = this
108 | var list_height = ((app.globalData.SystemInfo.windowHeight - 50) * (750 / app.globalData.SystemInfo.windowWidth)) - 60
109 | that.setData({
110 | list_height: list_height
111 | })
112 | wx.onBluetoothAdapterStateChange(function (res) {
113 | console.log(res)
114 | that.setData({
115 | searching: res.discovering
116 | })
117 | if (!res.available) {
118 | that.setData({
119 | searching: false
120 | })
121 | }
122 | })
123 | wx.onBluetoothDeviceFound(function (devices) {
124 | //剔除重复设备,兼容不同设备API的不同返回值
125 | var isnotexist = true
126 | if (devices.deviceId) {
127 | if (devices.advertisData)
128 | {
129 | devices.advertisData = app.buf2hex(devices.advertisData)
130 | }
131 | else
132 | {
133 | devices.advertisData = ''
134 | }
135 | console.log(devices)
136 | for (var i = 0; i < that.data.devicesList.length; i++) {
137 | if (devices.deviceId == that.data.devicesList[i].deviceId) {
138 | isnotexist = false
139 | }
140 | }
141 | if (isnotexist) {
142 | that.data.devicesList.push(devices)
143 | }
144 | }
145 | else if (devices.devices) {
146 | if (devices.devices[0].advertisData)
147 | {
148 | devices.devices[0].advertisData = app.buf2hex(devices.devices[0].advertisData)
149 | }
150 | else
151 | {
152 | devices.devices[0].advertisData = ''
153 | }
154 | console.log(devices.devices[0])
155 | for (var i = 0; i < that.data.devicesList.length; i++) {
156 | if (devices.devices[0].deviceId == that.data.devicesList[i].deviceId) {
157 | isnotexist = false
158 | }
159 | }
160 | if (isnotexist) {
161 | that.data.devicesList.push(devices.devices[0])
162 | }
163 | }
164 | else if (devices[0]) {
165 | if (devices[0].advertisData)
166 | {
167 | devices[0].advertisData = app.buf2hex(devices[0].advertisData)
168 | }
169 | else
170 | {
171 | devices[0].advertisData = ''
172 | }
173 | console.log(devices[0])
174 | for (var i = 0; i < devices_list.length; i++) {
175 | if (devices[0].deviceId == that.data.devicesList[i].deviceId) {
176 | isnotexist = false
177 | }
178 | }
179 | if (isnotexist) {
180 | that.data.devicesList.push(devices[0])
181 | }
182 | }
183 | that.setData({
184 | devicesList: that.data.devicesList
185 | })
186 | })
187 | },
188 | onReady: function () {
189 |
190 | },
191 | onShow: function () {
192 |
193 | },
194 | onHide: function () {
195 | var that = this
196 | that.setData({
197 | devicesList: []
198 | })
199 | if (this.data.searching) {
200 | wx.stopBluetoothDevicesDiscovery({
201 | success: function (res) {
202 | console.log(res)
203 | that.setData({
204 | searching: false
205 | })
206 | }
207 | })
208 | }
209 | }
210 | })
--------------------------------------------------------------------------------
/pages/search/search.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/pages/search/search.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 设备名称: {{item.name}}
7 | 设备ID: {{item.deviceId}}
8 | 信号强度RSSI: {{item.RSSI}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/pages/search/search.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | background-color: #f8f8f8;
3 | }
4 | .container {
5 | padding: 0 30rpx 0 30rpx;
6 | align-items: center;
7 | }
8 | .list-item {
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: space-between;
12 | align-items: center;
13 | width: 100%;
14 | padding: 10px 0 10px 0;
15 | box-sizing: border-box;
16 | border: 1px solid #000;
17 | border-style: none none solid none;
18 | border-bottom-color: lightgray;
19 | }
20 | .list-item:last-child {
21 | border-style: none;
22 | }
23 | .button {
24 | position: fixed;
25 | width: 690rpx;
26 | bottom: 30rpx;
27 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------