blue-device-services.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /**
  2. * 蓝牙相关封装,面向 uni-app 的 API。
  3. *
  4. * 说明:
  5. * - 仅支持蓝牙低功耗(BLE),适用于打印机等外设。
  6. * - 组件内必须先调用 initBluetoothAdapter 之后才可开始搜索/连接。
  7. */
  8. export async function checkBluetoothPermission(): Promise<void> {
  9. return new Promise((resolve, reject) => {
  10. uni.getSetting({
  11. success: (res) => {
  12. const hasLocation = res.authSetting?.['scope.userLocation'];
  13. if (hasLocation) {
  14. return resolve();
  15. }
  16. uni.authorize({
  17. scope: 'scope.userLocation',
  18. success: () => resolve(),
  19. fail: (err) => {
  20. uni.showModal({
  21. title: '权限提示',
  22. content: '需要定位权限才能扫描蓝牙设备,请前往设置开启定位权限。',
  23. showCancel: false,
  24. confirmText: '去设置',
  25. success: () => {
  26. uni.openSetting({});
  27. },
  28. });
  29. reject(err);
  30. },
  31. });
  32. },
  33. fail: reject,
  34. });
  35. });
  36. }
  37. export async function initBluetoothAdapter(): Promise<void> {
  38. type BluetoothMode = 'central' | 'peripheral';
  39. const closeAdapter = () =>
  40. new Promise<void>((resolve) => {
  41. uni.closeBluetoothAdapter({
  42. success: () => resolve(),
  43. fail: () => resolve(),
  44. });
  45. });
  46. const openAdapter = (mode?: BluetoothMode) =>
  47. new Promise<void>((resolve, reject) => {
  48. const options: any = {
  49. success: () => resolve(),
  50. fail: (err) => {
  51. console.log(err);
  52. reject(new Error(err.errMsg || `openBluetoothAdapter 失败`));
  53. },
  54. };
  55. if (mode) {
  56. options.mode = mode;
  57. }
  58. uni.openBluetoothAdapter(options);
  59. });
  60. // 先尝试关闭现有 adapter,避免重复初始化导致的异常
  61. await closeAdapter();
  62. // iOS 需要分别以主机/从机模式初始化一次,才能兼容全部场景。
  63. const platform = uni.getSystemInfoSync?.()?.platform || '';
  64. console.log(platform, 'initBluetoothAdapter platform');
  65. if (platform === 'ios') {
  66. // iOS 在 central/peripheral 两种模式都初始化一次,且需显式指定 mode。
  67. await closeAdapter();
  68. await openAdapter('central');
  69. await closeAdapter();
  70. await openAdapter('peripheral');
  71. // 最终回到 central,保证后续扫描/连接 BLE 外设稳定可用。
  72. await closeAdapter();
  73. await openAdapter('central');
  74. return;
  75. }
  76. // Android 等其他平台保持默认初始化流程
  77. await openAdapter();
  78. }
  79. export async function getBluetoothAdapterState(): Promise<any> {
  80. return new Promise((resolve, reject) => {
  81. uni.getBluetoothAdapterState({
  82. success: resolve,
  83. fail: reject,
  84. });
  85. });
  86. }
  87. export async function startBluetoothDevicesDiscovery(onDeviceFound: (device: any) => void): Promise<() => void> {
  88. return new Promise((resolve, reject) => {
  89. let listener: any | null = null;
  90. uni.startBluetoothDevicesDiscovery({
  91. allowDuplicatesKey: false,
  92. success: () => {
  93. listener = (res: any) => {
  94. if (!res || !res.devices) return;
  95. res.devices.forEach((device: any) => {
  96. if (device) onDeviceFound(device);
  97. });
  98. };
  99. uni.onBluetoothDeviceFound(listener);
  100. resolve(() => {
  101. if (listener) {
  102. uni.offBluetoothDeviceFound();
  103. listener = null;
  104. }
  105. });
  106. },
  107. fail: reject,
  108. });
  109. });
  110. }
  111. export async function stopBluetoothDevicesDiscovery(): Promise<void> {
  112. return new Promise((resolve, reject) => {
  113. uni.stopBluetoothDevicesDiscovery({
  114. success: () => resolve(),
  115. fail: reject,
  116. });
  117. });
  118. }
  119. export async function connectBLEDevice(deviceId: string): Promise<void> {
  120. return new Promise((resolve, reject) => {
  121. uni.createBLEConnection({
  122. deviceId,
  123. success: resolve,
  124. fail: reject,
  125. });
  126. });
  127. }
  128. export async function closeBLEConnection(deviceId: string): Promise<void> {
  129. return new Promise((resolve, reject) => {
  130. uni.closeBLEConnection({
  131. deviceId,
  132. success: resolve,
  133. fail: reject,
  134. });
  135. });
  136. }
  137. export function onBLEConnectionStateChange(onChange: (res: any) => void): () => void {
  138. const listener = (res: any) => {
  139. onChange(res);
  140. };
  141. uni.onBLEConnectionStateChange(listener);
  142. return () => {
  143. try {
  144. uni.offBLEConnectionStateChange(listener);
  145. } catch {
  146. try {
  147. uni.offBLEConnectionStateChange();
  148. } catch {
  149. // ignore
  150. }
  151. }
  152. };
  153. }
  154. export async function getBluetoothDevices(): Promise<any[]> {
  155. return new Promise((resolve, reject) => {
  156. uni.getBluetoothDevices({
  157. success: (res: any) => resolve(res.devices || []),
  158. fail: reject,
  159. });
  160. });
  161. }
  162. export async function getBLEDeviceServicesAndCharacteristics(deviceId: string): Promise<{ services: any[]; characteristicsMap: Record<string, any[]> }> {
  163. const services = await new Promise<any[]>((resolve, reject) => {
  164. uni.getBLEDeviceServices({
  165. deviceId,
  166. success: (res: any) => resolve(res.services || []),
  167. fail: reject,
  168. });
  169. });
  170. const characteristicsMap: Record<string, any[]> = {};
  171. await Promise.all(
  172. services.map(async (service) => {
  173. try {
  174. const characteristics = await new Promise<any[]>((resolve, reject) => {
  175. uni.getBLEDeviceCharacteristics({
  176. deviceId,
  177. serviceId: service.uuid,
  178. success: (res: any) => resolve(res.characteristics || []),
  179. fail: reject,
  180. });
  181. });
  182. characteristicsMap[service.uuid] = characteristics;
  183. } catch (err) {
  184. // 忽略某些服务获取特征值失败
  185. console.warn('getBLEDeviceCharacteristics fail', service.uuid, err);
  186. }
  187. }),
  188. );
  189. return { services, characteristicsMap };
  190. }
  191. // 打印数据 - 重构版
  192. export const writeBLECharacteristicSend = async (
  193. {
  194. deviceId,
  195. serviceId,
  196. characteristicId,
  197. uint8Array,
  198. oneTimeData = 20 // 单次发送最大字节数 (MTU)
  199. }: {
  200. deviceId: string;
  201. serviceId: string;
  202. characteristicId: string;
  203. uint8Array: Uint8Array | number[];
  204. oneTimeData?: number;
  205. }
  206. ): Promise<boolean> => {
  207. console.log(uint8Array, '----');
  208. // 1. 统一数据格式为 Uint8Array
  209. const u8 = uint8Array instanceof Uint8Array ? uint8Array : new Uint8Array(uint8Array);
  210. const totalLength = u8.length;
  211. if (totalLength === 0) {
  212. console.warn('writeBLECharacteristicSend: 发送数据为空');
  213. return false;
  214. }
  215. try {
  216. // 2. 循环分包发送 (替代原递归逻辑)
  217. for (let offset = 0; offset < totalLength; offset += oneTimeData) {
  218. // 计算当前包的实际长度 (处理最后一包不足 oneTimeData 的情况)
  219. const chunkLength = Math.min(oneTimeData, totalLength - offset);
  220. // 3. 构造 ArrayBuffer (参考原代码逻辑)
  221. const buffer = new ArrayBuffer(chunkLength);
  222. const dataView = new DataView(buffer);
  223. // 填充数据
  224. for (let i = 0; i < chunkLength; i++) {
  225. dataView.setUint8(i, u8[offset + i]);
  226. }
  227. // 4. 发送数据
  228. await new Promise<void>((resolve, reject) => {
  229. uni.writeBLECharacteristicValue({
  230. deviceId,
  231. serviceId,
  232. characteristicId,
  233. value: buffer, // 必须传入 ArrayBuffer
  234. success: () => resolve(),
  235. fail: (err: any) => {
  236. console.error('蓝牙写入失败', err);
  237. reject(err);
  238. },
  239. });
  240. });
  241. // 5. 写入间隔 (防止部分设备缓冲区溢出,参考之前代码保留 50ms)
  242. if (offset + chunkLength < totalLength) {
  243. await new Promise((r) => setTimeout(r, 50));
  244. }
  245. }
  246. return true;
  247. } catch (err) {
  248. console.error('writeBLECharacteristicSend fail', err);
  249. // 这里可以抛出错误让调用者处理,或者统一返回 false
  250. return false;
  251. }
  252. };