bluetooth.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <template>
  2. <view class="wrap">
  3. <button type="primary" :disabled="scanning" @tap="scan">{{scanning?'扫描中':'1. 扫描'}}</button>
  4. <radio-group v-if="list.length" @change="e=>deviceId=e.detail.value">
  5. <label v-for="d in list" :key="d.deviceId">
  6. <radio :value="d.deviceId" />{{d.name||d.localName||'未知设备'}}
  7. </label>
  8. </radio-group>
  9. <button type="warn" :disabled="!deviceId||connecting" @tap="connect">2. 连接</button>
  10. <view class="log">
  11. <text v-for="(l,i) in logs" :key="i">{{l}}</text>
  12. </view>
  13. </view>
  14. </template>
  15. <script>
  16. // Promise 化
  17. const $p = (api, opts = {}) => new Promise((res, rej) => {
  18. opts.success = res;
  19. opts.fail = rej;
  20. uni[api](opts)
  21. })
  22. export default {
  23. data() {
  24. return {
  25. scanning: false,
  26. list: [], // 扫描列表
  27. deviceId: '', // 选中设备
  28. connecting: false,
  29. logs: []
  30. }
  31. },
  32. onUnload() {
  33. this.close()
  34. },
  35. methods: {
  36. log(msg) {
  37. this.logs.unshift(`${new Date().toLocaleTimeString()} ${msg}`)
  38. },
  39. /* 1. 扫描 */
  40. async scan() {
  41. this.log('打开蓝牙...')
  42. try {
  43. await $p('openBluetoothAdapter')
  44. await $p('startBluetoothDevicesDiscovery', {
  45. allowDuplicatesKey: false
  46. })
  47. this.scanning = true
  48. this.log('扫描中...')
  49. uni.onBluetoothDeviceFound(res => this.list = [...this.list, ...res.devices])
  50. setTimeout(() => this.stopScan(), 8000)
  51. } catch (e) {
  52. this.log('扫描失败:' + e.errMsg)
  53. }
  54. },
  55. async stopScan() {
  56. this.scanning = false
  57. await $p('stopBluetoothDevicesDiscovery')
  58. this.log('扫描已停止')
  59. },
  60. /* 2. 连接并发现服务/特征 */
  61. async connect() {
  62. if (!this.deviceId) return
  63. this.connecting = true
  64. this.log('正在连接...')
  65. try {
  66. await $p('createBLEConnection', {
  67. deviceId: this.deviceId
  68. })
  69. // 部分机型必须延时
  70. await new Promise(r => setTimeout(r, 800))
  71. const {
  72. services
  73. } = await $p('getBLEDeviceServices', {
  74. deviceId: this.deviceId
  75. })
  76. if (!services.length) throw new Error('无服务')
  77. const service = services[0]
  78. const {
  79. characteristics
  80. } = await $p('getBLEDeviceCharacteristics', {
  81. deviceId: this.deviceId,
  82. serviceId: service.uuid
  83. })
  84. const char = characteristics.find(c => c.properties.notify || c.properties.indicate)
  85. if (char) {
  86. await $p('notifyBLECharacteristicValueChange', {
  87. deviceId: this.deviceId,
  88. serviceId: service.uuid,
  89. characteristicId: char.uuid,
  90. state: true
  91. })
  92. // 监听数据
  93. uni.onBLECharacteristicValueChange(res => {
  94. const hex = Array.prototype.map.call(new Uint8Array(res.value), b => ('0' + b
  95. .toString(16)).slice(-2)).join('')
  96. this.log(`收到 notify:${hex}`)
  97. })
  98. }
  99. this.log('连接成功,已启用 notify!')
  100. } catch (e) {
  101. this.log('连接失败:' + e.errMsg || e)
  102. }
  103. this.connecting = false
  104. },
  105. /* 3. 断开 & 释放 */
  106. async close() {
  107. if (this.deviceId) {
  108. await $p('closeBLEConnection', {
  109. deviceId: this.deviceId
  110. }).catch(() => {})
  111. }
  112. await $p('closeBluetoothAdapter').catch(() => {})
  113. }
  114. }
  115. }
  116. </script>
  117. <style scoped>
  118. .wrap {
  119. padding: 30rpx
  120. }
  121. .log {
  122. margin-top: 30rpx;
  123. font-size: 24rpx;
  124. color: #555;
  125. background: #f5f5f5;
  126. padding: 20rpx;
  127. max-height: 400rpx;
  128. overflow-y: auto;
  129. }
  130. </style>