├── .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 | --------------------------------------------------------------------------------