diff --git a/packages/api-proxy/@types/index.d.ts b/packages/api-proxy/@types/index.d.ts index e8e7415da7..392ca0c701 100644 --- a/packages/api-proxy/@types/index.d.ts +++ b/packages/api-proxy/@types/index.d.ts @@ -45,9 +45,36 @@ export const offError: WechatMiniprogram.Wx['offError'] export const createInnerAudioContext: WechatMiniprogram.Wx['createInnerAudioContext'] export const base64ToArrayBuffer: WechatMiniprogram.Wx['base64ToArrayBuffer'] export const arrayBufferToBase64: WechatMiniprogram.Wx['arrayBufferToBase64'] +export const openBluetoothAdapter: WechatMiniprogram.Wx['openBluetoothAdapter'] +export const closeBluetoothAdapter: WechatMiniprogram.Wx['closeBluetoothAdapter'] +export const startBluetoothDevicesDiscovery: WechatMiniprogram.Wx['startBluetoothDevicesDiscovery'] +export const stopBluetoothDevicesDiscovery: WechatMiniprogram.Wx['stopBluetoothDevicesDiscovery'] +export const onBluetoothDeviceFound: WechatMiniprogram.Wx['onBluetoothDeviceFound'] +export const offBluetoothDeviceFound: WechatMiniprogram.Wx['offBluetoothDeviceFound'] +export const getConnectedBluetoothDevices: WechatMiniprogram.Wx['getConnectedBluetoothDevices'] +export const getBluetoothAdapterState: WechatMiniprogram.Wx['getBluetoothAdapterState'] +export const onBluetoothAdapterStateChange: WechatMiniprogram.Wx['onBluetoothAdapterStateChange'] +export const offBluetoothAdapterStateChange: WechatMiniprogram.Wx['offBluetoothAdapterStateChange'] +export const getBluetoothDevices: WechatMiniprogram.Wx['getBluetoothDevices'] +export const writeBLECharacteristicValue: WechatMiniprogram.Wx['writeBLECharacteristicValue'] +export const readBLECharacteristicValue: WechatMiniprogram.Wx['readBLECharacteristicValue'] +export const notifyBLECharacteristicValueChange: WechatMiniprogram.Wx['notifyBLECharacteristicValueChange'] +export const onBLECharacteristicValueChange: WechatMiniprogram.Wx['onBLECharacteristicValueChange'] +export const offBLECharacteristicValueChange: WechatMiniprogram.Wx['offBLECharacteristicValueChange'] +export const setBLEMTU: WechatMiniprogram.Wx['setBLEMTU'] +export const getBLEDeviceRSSI: WechatMiniprogram.Wx['getBLEDeviceRSSI'] +export const getBLEDeviceServices: WechatMiniprogram.Wx['getBLEDeviceServices'] +export const getBLEDeviceCharacteristics: WechatMiniprogram.Wx['getBLEDeviceCharacteristics'] export const closeBLEConnection: WechatMiniprogram.Wx['closeBLEConnection'] export const createBLEConnection: WechatMiniprogram.Wx['createBLEConnection'] export const onBLEConnectionStateChange: WechatMiniprogram.Wx['onBLEConnectionStateChange'] +export const offBLEConnectionStateChange: WechatMiniprogram.Wx['offBLEConnectionStateChange'] +export const startWifi: WechatMiniprogram.Wx['startWifi'] +export const stopWifi: WechatMiniprogram.Wx['stopWifi'] +export const getWifiList: WechatMiniprogram.Wx['getWifiList'] +export const onGetWifiList: WechatMiniprogram.Wx['onGetWifiList'] +export const offGetWifiList: WechatMiniprogram.Wx['offGetWifiList'] +export const getConnectedWifi: WechatMiniprogram.Wx['getConnectedWifi'] export const createCanvasContext: WechatMiniprogram.Wx['createCanvasContext'] export const canvasToTempFilePath: WechatMiniprogram.Wx['canvasToTempFilePath'] export const canvasGetImageData: WechatMiniprogram.Wx['canvasGetImageData'] diff --git a/packages/api-proxy/package.json b/packages/api-proxy/package.json index 5229a3cba2..df7e5fb9db 100644 --- a/packages/api-proxy/package.json +++ b/packages/api-proxy/package.json @@ -46,7 +46,9 @@ "react-native-device-info": "*", "react-native-get-location": "*", "react-native-haptic-feedback": "*", - "react-native-safe-area-context": "*" + "react-native-safe-area-context": "*", + "react-native-ble-manager": "*", + "react-native-wifi-reborn": "*" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -66,6 +68,12 @@ }, "react-native-haptic-feedback": { "optional": true + }, + "react-native-ble-manager": { + "optional": true + }, + "react-native-wifi-reborn": { + "optional": true } } } diff --git a/packages/api-proxy/src/platform/api/base/index.web.js b/packages/api-proxy/src/platform/api/base/index.web.js index 2ac750aa09..7ebc5846eb 100644 --- a/packages/api-proxy/src/platform/api/base/index.web.js +++ b/packages/api-proxy/src/platform/api/base/index.web.js @@ -1,10 +1,7 @@ import { fromByteArray, toByteArray } from './base64' function base64ToArrayBuffer (base64) { - if (__mpx_mode__ === 'web') { - return toByteArray(base64)?.buffer - } - return toByteArray(base64) + return toByteArray(base64)?.buffer // 判断了一下,RN下也是要.buffer才是buffer类型 } function arrayBufferToBase64 (arrayBuffer) { diff --git a/packages/api-proxy/src/platform/api/ble-connection/index.ali.js b/packages/api-proxy/src/platform/api/ble-connection/index.ali.js index 32191cc4a4..236c30c192 100644 --- a/packages/api-proxy/src/platform/api/ble-connection/index.ali.js +++ b/packages/api-proxy/src/platform/api/ble-connection/index.ali.js @@ -1,4 +1,4 @@ -import { ENV_OBJ } from '../../../common/js' +import { ENV_OBJ, envError } from '../../../common/js' function closeBLEConnection (options = {}) { return ENV_OBJ.disconnectBLEDevice(options) @@ -12,8 +12,73 @@ function onBLEConnectionStateChange (callback) { return ENV_OBJ.onBLEConnectionStateChanged(callback) } +function offBLEConnectionStateChange (callback) { + return ENV_OBJ.offBLEConnectionStateChanged(callback) +} + +const openBluetoothAdapter = ENV_OBJ.openBluetoothAdapter || envError('openBluetoothAdapter') + +const closeBluetoothAdapter = ENV_OBJ.closeBluetoothAdapter || envError('closeBluetoothAdapter') + +const startBluetoothDevicesDiscovery = ENV_OBJ.startBluetoothDevicesDiscovery || envError('startBluetoothDevicesDiscovery') + +const stopBluetoothDevicesDiscovery = ENV_OBJ.stopBluetoothDevicesDiscovery || envError('stopBluetoothDevicesDiscovery') + +const onBluetoothDeviceFound = ENV_OBJ.onBluetoothDeviceFound || envError('onBluetoothDeviceFound') + +const offBluetoothDeviceFound = ENV_OBJ.offBluetoothDeviceFound || envError('offBluetoothDeviceFound') + +const getConnectedBluetoothDevices = ENV_OBJ.getConnectedBluetoothDevices || envError('getConnectedBluetoothDevices') + +const getBluetoothAdapterState = ENV_OBJ.getBluetoothAdapterState || envError('getBluetoothAdapterState') + +const onBluetoothAdapterStateChange = ENV_OBJ.onBluetoothAdapterStateChange || envError('onBluetoothAdapterStateChange') + +const offBluetoothAdapterStateChange = ENV_OBJ.offBluetoothAdapterStateChange || envError('offBluetoothAdapterStateChange') + +const getBluetoothDevices = ENV_OBJ.getBluetoothDevices || envError('getBluetoothDevices') + +const writeBLECharacteristicValue = ENV_OBJ.writeBLECharacteristicValue || envError('writeBLECharacteristicValue') + +const readBLECharacteristicValue = ENV_OBJ.readBLECharacteristicValue || envError('readBLECharacteristicValue') + +const notifyBLECharacteristicValueChange = ENV_OBJ.notifyBLECharacteristicValueChange || envError('notifyBLECharacteristicValueChange') + +const onBLECharacteristicValueChange = ENV_OBJ.onBLECharacteristicValueChange || envError('onBLECharacteristicValueChange') + +const offBLECharacteristicValueChange = ENV_OBJ.offBLECharacteristicValueChange || envError('offBLECharacteristicValueChange') + +const setBLEMTU = ENV_OBJ.setBLEMTU || envError('setBLEMTU') + +const getBLEDeviceRSSI = ENV_OBJ.getBLEDeviceRSSI || envError('getBLEDeviceRSSI') + +const getBLEDeviceServices = ENV_OBJ.getBLEDeviceServices || envError('getBLEDeviceServices') + +const getBLEDeviceCharacteristics = ENV_OBJ.getBLEDeviceCharacteristics || envError('getBLEDeviceCharacteristics') + export { closeBLEConnection, createBLEConnection, - onBLEConnectionStateChange + onBLEConnectionStateChange, + offBLEConnectionStateChange, + openBluetoothAdapter, + closeBluetoothAdapter, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, + onBluetoothDeviceFound, + offBluetoothDeviceFound, + getConnectedBluetoothDevices, + getBluetoothAdapterState, + onBluetoothAdapterStateChange, + offBluetoothAdapterStateChange, + getBluetoothDevices, + writeBLECharacteristicValue, + readBLECharacteristicValue, + notifyBLECharacteristicValueChange, + onBLECharacteristicValueChange, + offBLECharacteristicValueChange, + setBLEMTU, + getBLEDeviceRSSI, + getBLEDeviceServices, + getBLEDeviceCharacteristics } diff --git a/packages/api-proxy/src/platform/api/ble-connection/index.ios.js b/packages/api-proxy/src/platform/api/ble-connection/index.ios.js new file mode 100644 index 0000000000..d5233f8253 --- /dev/null +++ b/packages/api-proxy/src/platform/api/ble-connection/index.ios.js @@ -0,0 +1,861 @@ +import { noop, type } from '@mpxjs/utils' +import mpx from '@mpxjs/core' +import { Platform, PermissionsAndroid } from 'react-native' +import { base64ToArrayBuffer } from '../base/index' + +// BleManager 相关 +// 全局状态管理 +let bleManagerInitialized = false +let DiscoverPeripheralSubscription = null +let updateStateSubscription = null +let discovering = false +let getDevices = [] // 记录已扫描的设备列表 +const deviceFoundCallbacks = [] +const onStateChangeCallbacks = [] +const characteristicCallbacks = [] +const onBLEConnectionStateCallbacks = [] +let characteristicSubscriptions = {} +const connectedDevices = new Set() +let createBLEConnectionTimeout = null +const BLEDeviceCharacteristics = {} // 记录已连接设备的特征值 +const connectedDeviceId = [] + +// 请求蓝牙权限 +const requestBluetoothPermission = async () => { + if (__mpx_mode__ === 'android') { + const permissions = [] + if (Platform.Version >= 23 && Platform.Version < 31) { + permissions.push(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) + } else if (Platform.Version >= 31) { + permissions.push( + PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, + PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT + ) + } + + if (permissions.length === 0) { + return true + } + const granted = await PermissionsAndroid.requestMultiple(permissions) + return Object.values(granted).every( + result => result === PermissionsAndroid.RESULTS.GRANTED + ) + } + return true +} + +const removeBluetoothDevicesDiscovery = function () { + if (DiscoverPeripheralSubscription) { + DiscoverPeripheralSubscription.remove() + DiscoverPeripheralSubscription = null + } +} +const removeUpdateStateSubscription = function () { + if (updateStateSubscription && onStateChangeCallbacks.length === 0 && onBLEConnectionStateCallbacks.length === 0) { + updateStateSubscription.remove() + updateStateSubscription = null + } +} +const commonFailHandler = function (errMsg, fail, complete) { + const result = { + errMsg + } + if (!bleManagerInitialized) { + Object.assign(result, { + errCode: 10000, + errno: 1500101 + }) + } + fail(result) + complete(result) +} + +function openBluetoothAdapter (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { success = noop, fail = noop, complete = noop } = options + let bluetoothPermission = requestBluetoothPermission + if (mpx.config?.rnConfig?.bluetoothPermission) { // 安卓需要验证权限,开放给用户可以自定义验证权限的方法 + bluetoothPermission = mpx.config.rnConfig.bluetoothPermission + } + // 先请求权限,再初始化蓝牙管理器 + bluetoothPermission().then((hasPermissions) => { + if (!hasPermissions) { + commonFailHandler('openBluetoothAdapter:fail no permission', fail, complete) + return + } + + if (bleManagerInitialized) { + commonFailHandler('openBluetoothAdapter:fail already opened', fail, complete) + return + } + + BleManager.start({ showAlert: false }).then(() => { + bleManagerInitialized = true + + // 检查蓝牙状态 + setTimeout(() => { + BleManager.checkState().then((state) => { + if (state === 'on') { + const result = { + errno: 0, + errMsg: 'openBluetoothAdapter:ok' + } + success(result) + complete(result) + } else { + commonFailHandler('openBluetoothAdapter:fail bluetooth not enabled', fail, complete) + } + }).catch((error) => { + commonFailHandler('openBluetoothAdapter:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) + }, 1000) + }).catch((error) => { + commonFailHandler('openBluetoothAdapter:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) + }).catch(() => { + commonFailHandler('openBluetoothAdapter:fail no permission', fail, complete) + }) +} + +function closeBluetoothAdapter (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { success = noop, fail = noop, complete = noop } = options + if (!bleManagerInitialized) { + const result = { + errMsg: 'closeBluetoothAdapter:fail 请先调用 wx.openBluetoothAdapter 接口进行初始化操作' + } + fail(result) + complete(result) + return + } + try { + // 停止扫描 + if (discovering) { + BleManager.stopScan() + discovering = false + } + + if (createBLEConnectionTimeout) { // 清除掉正在连接的蓝牙设备 + clearTimeout(createBLEConnectionTimeout) + createBLEConnectionTimeout = null + } + + removeUpdateStateSubscription() + // 清理状态 + bleManagerInitialized = false + discovering = false + getDevices = [] + connectedDeviceId.length = 0 + connectedDevices.forEach((id) => { + BleManager.disconnect(id).catch(() => {}) + }) + connectedDevices.clear() + deviceFoundCallbacks.length = 0 + onStateChangeCallbacks.length = 0 + characteristicCallbacks.length = 0 + onBLEConnectionStateCallbacks.length = 0 + if (valueForCharacteristicSubscriptions) { + valueForCharacteristicSubscriptions.remove() + valueForCharacteristicSubscriptions = null + } + + removeBluetoothDevicesDiscovery() + + // 清理订阅 + Object.keys(characteristicSubscriptions).forEach(key => { + if (characteristicSubscriptions[key]) { + characteristicSubscriptions[key].remove() + } + }) + characteristicSubscriptions = {} + + const result = { + errMsg: 'closeBluetoothAdapter:ok' + } + success(result) + complete(result) + } catch (error) { + const result = { + errMsg: 'closeBluetoothAdapter:fail ' + error.message + } + fail(result) + complete(result) + } +} + +function startBluetoothDevicesDiscovery (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { + services = [], + allowDuplicatesKey = false, + success = noop, + fail = noop, + complete = noop + } = options + + if (!bleManagerInitialized) { + commonFailHandler('startBluetoothDevicesDiscovery:fail ble adapter hans\'t been opened or ble is unavailable.', fail, complete) + return + } + DiscoverPeripheralSubscription = BleManager.onDiscoverPeripheral((device) => { + const advertising = device.advertising || {} + const advertisData = (advertising.manufacturerData && Object.values(advertising.manufacturerData)[0]?.data) || null + const deviceInfo = { + deviceId: device.id, + name: device.name || advertising.localName || '未知设备', + RSSI: device.rssi || 0, + advertisData: advertisData ? base64ToArrayBuffer(advertisData) : advertisData, + advertisServiceUUIDs: advertising.serviceUUIDs || [], + localName: advertising.localName || '', + serviceData: advertising.serviceData || {}, + connectable: advertising.isConnectable || false + } + if (allowDuplicatesKey === false) { + const existingDeviceIndex = getDevices.findIndex(existingDevice => existingDevice.deviceId === deviceInfo.deviceId) + if (existingDeviceIndex > -1) { + if (device.name) { + getDevices.splice(existingDeviceIndex, 1, deviceInfo) + } + return + } + } + deviceFoundCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + devices: [deviceInfo] + }) + } + }) + getDevices.push(deviceInfo) + // 处理设备发现逻辑 + }) + BleManager.scan(services, 0, allowDuplicatesKey).then((res) => { // 必须,没有开启扫描,onDiscoverPeripheral回调不会触发 + onStateChangeCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + available: true, + discovering: true + }) + } + }) + discovering = true + getDevices = [] // 清空之前的发现设备列表 + const result = { + errMsg: 'startBluetoothDevicesDiscovery:ok', + isDiscovering: true + } + success(result) + complete(result) + }).catch((error) => { + commonFailHandler('startBluetoothDevicesDiscovery:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) +} + +function stopBluetoothDevicesDiscovery (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { success = noop, fail = noop, complete = noop } = options + + if (!bleManagerInitialized) { + commonFailHandler('stopBluetoothDevicesDiscovery:fail ble adapter hans\'t been opened or ble is unavailable.', fail, complete) + return + } + removeBluetoothDevicesDiscovery() + BleManager.stopScan().then(() => { + discovering = false + onStateChangeCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + available: true, + discovering: false + }) + } + }) + const result = { + errMsg: 'stopBluetoothDevicesDiscovery:ok' + } + success(result) + complete(result) + }).catch((error) => { + commonFailHandler('stopBluetoothDevicesDiscovery:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) +} + +function onBluetoothDeviceFound (callback) { + if (deviceFoundCallbacks.indexOf(callback) === -1) { + deviceFoundCallbacks.push(callback) + } +} + +function offBluetoothDeviceFound (callback) { + const index = deviceFoundCallbacks.indexOf(callback) + if (index > -1) { + deviceFoundCallbacks.splice(index, 1) + } +} + +function getConnectedBluetoothDevices (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { services = [], success = noop, fail = noop, complete = noop } = options + + if (!bleManagerInitialized) { + commonFailHandler('getConnectedBluetoothDevices:fail 请先调用 wx.openBluetoothAdapter 接口进行初始化操作', fail, complete) + return + } + + BleManager.getConnectedPeripherals(services).then((peripherals) => { + const devices = peripherals.map(peripheral => ({ + deviceId: peripheral.id, + name: peripheral.name || '未知设备' + })) + const result = { + errMsg: 'getConnectedBluetoothDevices:ok', + devices: devices + } + success(result) + complete(result) + }).catch((error) => { + commonFailHandler('getConnectedBluetoothDevices:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) +} + +function getBluetoothAdapterState (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { success = noop, fail = noop, complete = noop } = options + + if (!bleManagerInitialized) { + commonFailHandler('getBluetoothAdapterState:fail ble adapter need open first.', fail, complete) + return + } + + BleManager.checkState().then((state) => { + const result = { + errMsg: 'getBluetoothAdapterState:ok', + discovering, + available: state === 'on' + } + success(result) + complete(result) + }).catch((error) => { + commonFailHandler('getBluetoothAdapterState:fail ' + (typeof error === 'string' ? error : ''), fail, complete) + }) +} +function onDidUpdateState () { + const BleManager = require('react-native-ble-manager').default + updateStateSubscription = BleManager.onDidUpdateState((state) => { + onStateChangeCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + available: state.state === 'on', + discovering: state.state === 'on' ? discovering : false + }) + } + }) + if (onBLEConnectionStateCallbacks.length && connectedDeviceId.length && state.state !== 'on') { + connectedDeviceId.forEach((id) => { + onBLEConnectionStateCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + deviceId: id, + connected: false + }) + } + }) + }) + } + }) +} + +function onBluetoothAdapterStateChange (callback) { + if (!updateStateSubscription) { + onDidUpdateState() + } + if (onStateChangeCallbacks.indexOf(callback) === -1) { + onStateChangeCallbacks.push(callback) + } +} + +function offBluetoothAdapterStateChange (callback) { + const index = onStateChangeCallbacks.indexOf(callback) + if (index > -1) { + onStateChangeCallbacks.splice(index, 1) + } + if (deviceFoundCallbacks.length === 0) { + removeUpdateStateSubscription() + } +} + +function getBluetoothDevices (options = {}) { // 该能力只是获取应用级别已连接设备列表,非手机级别的已连接设备列表 + const { success = noop, fail = noop, complete = noop } = options + if (!bleManagerInitialized) { + const result = { + errMsg: 'getBluetoothDevices:fail ble adapter hans\'t been opened or ble is unavailable.' + } + fail(result) + complete(result) + return + } + const result = { + errMsg: 'getBluetoothDevices:ok', + devices: getDevices // 返回已扫描的设备列表 + } + success(result) + complete(result) +} + +function writeBLECharacteristicValue (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, serviceId, characteristicId, value, writeType, success = noop, fail = noop, complete = noop } = options + if (!deviceId || !serviceId || !characteristicId || !value) { + const result = { + errMsg: 'writeBLECharacteristicValue:fail parameter error', + errno: 1001 + } + success(result) + fail(result) + return + } + let writeTypeValue = writeType + if (!writeType) { + // 与小程序拉齐 iOS 未传值的情况优先 write,安卓优先 writeNoResponse 。 + writeTypeValue = __mpx_mode__ === 'ios' ? 'write' : 'writeNoResponse' + } + // 将ArrayBuffer转换为byte array + const bytes = Array.from(new Uint8Array(value)) + const writeMethod = writeTypeValue === 'writeNoResponse' + ? BleManager.writeWithoutResponse(deviceId, serviceId, characteristicId, bytes) + : BleManager.write(deviceId, serviceId, characteristicId, bytes) + writeMethod.then(() => { + const result = { + errMsg: 'writeBLECharacteristicValue:ok' + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'writeBLECharacteristicValue:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) +} + +function readBLECharacteristicValue (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, serviceId, characteristicId, success = noop, fail = noop, complete = noop } = options + + if (!deviceId || !serviceId || !characteristicId) { + const result = { + errMsg: 'readBLECharacteristicValue:ok', + errno: 1509000 + } + success(result) + complete(result) + return + } + + BleManager.read(deviceId, serviceId, characteristicId).then((data) => { + // 将byte array转换为ArrayBuffer + const buffer = new ArrayBuffer(data.length) + const view = new Uint8Array(buffer) + data.forEach((byte, index) => { + view[index] = byte + }) + + const result = { + errMsg: 'readBLECharacteristicValue:ok', + value: buffer + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'readBLECharacteristicValue:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) +} + +function notifyBLECharacteristicValueChange (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, serviceId, characteristicId, state = true, success = noop, fail = noop, complete = noop } = options + + if (!deviceId || !serviceId || !characteristicId) { + const result = { + errMsg: 'notifyBLECharacteristicValueChange:ok', + errno: 1509000 + } + success(result) + complete(result) + return + } + + const subscriptionKey = `${deviceId}_${serviceId}_${characteristicId}` + + if (state) { + // 启用监听 + BleManager.startNotification(deviceId, serviceId, characteristicId).then(() => { + characteristicSubscriptions[subscriptionKey] = true + + const result = { + errMsg: 'notifyBLECharacteristicValueChange:ok' + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'notifyBLECharacteristicValueChange:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) + } else { + // 停止监听 + BleManager.stopNotification(deviceId, serviceId, characteristicId).then(() => { + delete characteristicSubscriptions[subscriptionKey] + + const result = { + errMsg: 'notifyBLECharacteristicValueChange:ok' + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'notifyBLECharacteristicValueChange:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) + } +} + +let valueForCharacteristicSubscriptions = null +function onBLECharacteristicValueChange (callback) { + const BleManager = require('react-native-ble-manager').default + if (characteristicCallbacks.length === 0) { + valueForCharacteristicSubscriptions = BleManager.onDidUpdateValueForCharacteristic((data) => { + // 将byte array转换为ArrayBuffer + const buffer = new ArrayBuffer(data.value.length) + const view = new Uint8Array(buffer) + data.value.forEach((byte, index) => { + view[index] = byte + }) + const result = { + deviceId: data.peripheral, + serviceId: data.service, + characteristicId: data.characteristic, + value: buffer + } + characteristicCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb(result) + } + }) + }) + } + if (characteristicCallbacks.indexOf(callback) === -1) { + characteristicCallbacks.push(callback) + } +} + +function offBLECharacteristicValueChange (callback) { + const index = characteristicCallbacks.indexOf(callback) + if (index > -1) { + characteristicCallbacks.splice(index, 1) + } + if (characteristicCallbacks.length === 0 && valueForCharacteristicSubscriptions) { + valueForCharacteristicSubscriptions.remove() + valueForCharacteristicSubscriptions = null + } +} + +function setBLEMTU (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, mtu, success = noop, fail = noop, complete = noop } = options + if (!mtu) { + commonFailHandler('setBLEMTU:fail parameter error: parameter.mtu should be Number instead of Undefined;', fail, complete) + return + } + if (!deviceId) { + commonFailHandler('setBLEMTU:fail parameter error: parameter.deviceId should be String instead of Undefined;', fail, complete) + return + } + if (!deviceId && !mtu) { + commonFailHandler('setBLEMTU:fail parameter error: parameter.deviceId should be String instead of Undefined;parameter.mtu should be Number instead of Undefined;', fail, complete) + return + } + + BleManager.requestMTU(deviceId, mtu).then((actualMtu) => { + const result = { + errMsg: 'setBLEMTU:ok', + mtu: actualMtu + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'setBLEMTU:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) +} + +function getBLEDeviceRSSI (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, success = noop, fail = noop, complete = noop } = options + + if (!deviceId) { + const result = { + errMsg: 'getBLEDeviceRSSI:ok', + errno: 1509000 + } + success(result) + complete(result) + return + } + + BleManager.readRSSI(deviceId).then((rssi) => { + const result = { + errMsg: 'getBLEDeviceRSSI:ok', + RSSI: rssi + } + success(result) + complete(result) + }).catch((error) => { + const errmsg = typeof error === 'string' ? error : (typeof error === 'string' ? error : '') + const result = { + errMsg: 'getBLEDeviceRSSI:fail ' + errmsg + } + fail(result) + complete(result) + }) +} + +function getBLEDeviceServices (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, success = noop, fail = noop, complete = noop } = options + + if (!deviceId) { + const result = { + errMsg: 'getBLEDeviceServices:ok', + errno: 1509000, + services: [] + } + fail(result) + complete(result) + return + } + BleManager.retrieveServices(deviceId).then((peripheralInfo) => { + const services = peripheralInfo.services.map(service => ({ + uuid: service.uuid + })) + + // 存储服务信息 + BLEDeviceCharacteristics[deviceId] = peripheralInfo + + const result = { + errMsg: 'getBLEDeviceServices:ok', + services: services + } + success(result) + complete(result) + }).catch((error) => { + const errmsg = typeof error === 'string' ? error : (typeof error === 'string' ? error : '') + const result = { + errMsg: 'getBLEDeviceServices:fail ' + errmsg + } + fail(result) + complete(result) + }) +} + +function getBLEDeviceCharacteristics (options = {}) { + const { deviceId, serviceId, success = noop, fail = noop, complete = noop } = options + + if (!deviceId || !serviceId) { + const result = { + errMsg: 'getBLEDeviceCharacteristics:ok', + errno: 1509000, + characteristics: [] + } + success(result) + complete(result) + return + } + + const peripheralInfo = BLEDeviceCharacteristics[deviceId] + if (!peripheralInfo) { + const result = { + errMsg: 'getBLEDeviceCharacteristics:fail device services not retrieved' + } + fail(result) + complete(result) + return + } + const characteristicsList = peripheralInfo.characteristics || [] + const service = characteristicsList.find(c => c.service.toLowerCase() === serviceId.toLowerCase()) + if (!service && !characteristicsList.length) { + const result = { + errMsg: 'getBLEDeviceCharacteristics:fail service not found' + } + fail(result) + complete(result) + return + } + const characteristics = characteristicsList.map(char => ({ + uuid: char.characteristic, + properties: { + read: !!char.properties.Read, + write: !!char.properties.Write, + notify: !!char.properties.Notify, + indicate: !!char.properties.Indicate, + writeNoResponse: !!char.properties.WriteWithoutResponse + } + })) + + const result = { + errMsg: 'getBLEDeviceCharacteristics:ok', + characteristics + } + success(result) + complete(result) +} + +function createBLEConnection (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, timeout, success = noop, fail = noop, complete = noop } = options + + if (!deviceId) { + const result = { + errMsg: 'createBLEConnection:ok', + errno: 1509000 + } + fail(result) + complete(result) + return + } + + BleManager.connect(deviceId, { + autoconnect: true + }).then(() => { + if (connectedDeviceId.indexOf(deviceId) === -1) { + connectedDeviceId.push(deviceId) // 记录一下已连接的设备id + } + clearTimeout(createBLEConnectionTimeout) + onBLEConnectionStateCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + deviceId, + connected: true + }) + } + }) + connectedDevices.add(deviceId) + const result = { + errMsg: 'createBLEConnection:ok' + } + success(result) + complete(result) + }).catch((error) => { + clearTimeout(createBLEConnectionTimeout) + const result = { + errMsg: 'createBLEConnection:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) + if (timeout) { + createBLEConnectionTimeout = setTimeout(() => { // 超时处理,仅ios会一直连接,android不会 + BleManager.disconnect(deviceId).catch(() => {}) + }, timeout) + } +} + +function closeBLEConnection (options = {}) { + const BleManager = require('react-native-ble-manager').default + const { deviceId, success = noop, fail = noop, complete = noop } = options + + if (!deviceId) { + const result = { + errMsg: 'closeBLEConnection:ok', + errno: 1509000 + } + success(result) + complete(result) + return + } + + BleManager.disconnect(deviceId).then(() => { + const index = connectedDeviceId.indexOf(deviceId) + if (index !== -1) { + connectedDeviceId.splice(index, 1) // 记录一下已连接的设备id + } + onBLEConnectionStateCallbacks.forEach(cb => { + if (type(cb) === 'Function') { + cb({ + deviceId, + connected: false + }) + } + }) + connectedDevices.delete(deviceId) + const result = { + errMsg: 'closeBLEConnection:ok' + } + success(result) + complete(result) + }).catch((error) => { + const result = { + errMsg: 'closeBLEConnection:fail ' + (typeof error === 'string' ? error : '') + } + fail(result) + complete(result) + }) +} + +function onBLEConnectionStateChange (callback) { + if (!updateStateSubscription) { + onDidUpdateState() + } + if (onBLEConnectionStateCallbacks.indexOf(callback) === -1) { + onBLEConnectionStateCallbacks.push(callback) + } +} + +function offBLEConnectionStateChange (callback) { + const index = onBLEConnectionStateCallbacks.indexOf(callback) + if (index !== -1) { + onBLEConnectionStateCallbacks.splice(index, 1) + } + if (onBLEConnectionStateCallbacks.length === 0) { + removeUpdateStateSubscription() + } +} + +export { + openBluetoothAdapter, + closeBluetoothAdapter, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, + onBluetoothDeviceFound, + offBluetoothDeviceFound, + getConnectedBluetoothDevices, + getBluetoothAdapterState, + onBluetoothAdapterStateChange, + offBluetoothAdapterStateChange, + getBluetoothDevices, + writeBLECharacteristicValue, + readBLECharacteristicValue, + notifyBLECharacteristicValueChange, + onBLECharacteristicValueChange, + offBLECharacteristicValueChange, + setBLEMTU, + getBLEDeviceRSSI, + getBLEDeviceServices, + getBLEDeviceCharacteristics, + createBLEConnection, + closeBLEConnection, + onBLEConnectionStateChange, + offBLEConnectionStateChange +} diff --git a/packages/api-proxy/src/platform/api/ble-connection/index.js b/packages/api-proxy/src/platform/api/ble-connection/index.js index 06c95c804f..18656eeb31 100644 --- a/packages/api-proxy/src/platform/api/ble-connection/index.js +++ b/packages/api-proxy/src/platform/api/ble-connection/index.js @@ -6,8 +6,71 @@ const createBLEConnection = ENV_OBJ.createBLEConnection || envError('createBLECo const onBLEConnectionStateChange = ENV_OBJ.onBLEConnectionStateChange || envError('onBLEConnectionStateChange') +const offBLEConnectionStateChange = ENV_OBJ.offBLEConnectionStateChange || envError('offBLEConnectionStateChange') + +const openBluetoothAdapter = ENV_OBJ.openBluetoothAdapter || envError('openBluetoothAdapter') + +const closeBluetoothAdapter = ENV_OBJ.closeBluetoothAdapter || envError('closeBluetoothAdapter') + +const startBluetoothDevicesDiscovery = ENV_OBJ.startBluetoothDevicesDiscovery || envError('startBluetoothDevicesDiscovery') + +const stopBluetoothDevicesDiscovery = ENV_OBJ.stopBluetoothDevicesDiscovery || envError('stopBluetoothDevicesDiscovery') + +const onBluetoothDeviceFound = ENV_OBJ.onBluetoothDeviceFound || envError('onBluetoothDeviceFound') + +const offBluetoothDeviceFound = ENV_OBJ.offBluetoothDeviceFound || envError('offBluetoothDeviceFound') + +const getConnectedBluetoothDevices = ENV_OBJ.getConnectedBluetoothDevices || envError('getConnectedBluetoothDevices') + +const getBluetoothAdapterState = ENV_OBJ.getBluetoothAdapterState || envError('getBluetoothAdapterState') + +const onBluetoothAdapterStateChange = ENV_OBJ.onBluetoothAdapterStateChange || envError('onBluetoothAdapterStateChange') + +const offBluetoothAdapterStateChange = ENV_OBJ.offBluetoothAdapterStateChange || envError('offBluetoothAdapterStateChange') + +const getBluetoothDevices = ENV_OBJ.getBluetoothDevices || envError('getBluetoothDevices') + +const writeBLECharacteristicValue = ENV_OBJ.writeBLECharacteristicValue || envError('writeBLECharacteristicValue') + +const readBLECharacteristicValue = ENV_OBJ.readBLECharacteristicValue || envError('readBLECharacteristicValue') + +const notifyBLECharacteristicValueChange = ENV_OBJ.notifyBLECharacteristicValueChange || envError('notifyBLECharacteristicValueChange') + +const onBLECharacteristicValueChange = ENV_OBJ.onBLECharacteristicValueChange || envError('onBLECharacteristicValueChange') + +const offBLECharacteristicValueChange = ENV_OBJ.offBLECharacteristicValueChange || envError('offBLECharacteristicValueChange') + +const setBLEMTU = ENV_OBJ.setBLEMTU || envError('setBLEMTU') + +const getBLEDeviceRSSI = ENV_OBJ.getBLEDeviceRSSI || envError('getBLEDeviceRSSI') + +const getBLEDeviceServices = ENV_OBJ.getBLEDeviceServices || envError('getBLEDeviceServices') + +const getBLEDeviceCharacteristics = ENV_OBJ.getBLEDeviceCharacteristics || envError('getBLEDeviceCharacteristics') + export { - closeBLEConnection, + onBLEConnectionStateChange, + offBLEConnectionStateChange, + openBluetoothAdapter, + closeBluetoothAdapter, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, + onBluetoothDeviceFound, + offBluetoothDeviceFound, + getConnectedBluetoothDevices, + getBluetoothAdapterState, + onBluetoothAdapterStateChange, + offBluetoothAdapterStateChange, + getBluetoothDevices, + writeBLECharacteristicValue, + readBLECharacteristicValue, + notifyBLECharacteristicValueChange, + onBLECharacteristicValueChange, + offBLECharacteristicValueChange, + setBLEMTU, + getBLEDeviceRSSI, + getBLEDeviceServices, + getBLEDeviceCharacteristics, createBLEConnection, - onBLEConnectionStateChange + closeBLEConnection } diff --git a/packages/api-proxy/src/platform/api/device/wifi/index.ios.js b/packages/api-proxy/src/platform/api/device/wifi/index.ios.js new file mode 100644 index 0000000000..a503aecfec --- /dev/null +++ b/packages/api-proxy/src/platform/api/device/wifi/index.ios.js @@ -0,0 +1,236 @@ +import { PermissionsAndroid } from 'react-native' +import { noop, type } from '@mpxjs/utils' +import mpx from '@mpxjs/core' +let startWifiReady = false +const wifiListListeners = [] + +async function requestWifiPermission () { + const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, { + title: 'Location permission is required for WiFi connections', + message: + 'This app needs location permission as this is required ' + + 'to scan for wifi networks.', + buttonNegative: 'DENY', + buttonPositive: 'ALLOW' + }) + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + return true + } else { + return false + } +} + +function startWifi (options = {}) { + const { success = noop, fail = noop, complete = noop } = options + if (__mpx_mode__ === 'ios') { + const result = { + errMsg: 'startWifi:fail ios system not support, you need to manually go to the Settings to enable wifi' + } + fail(result) + complete(result) + return + } + const WifiManager = require('react-native-wifi-reborn').default + let wifiPermission = requestWifiPermission + if (mpx.config?.rnConfig?.wifiPermission) { + wifiPermission = mpx.config.rnConfig.wifiPermission + } + wifiPermission().then(async () => { + let enabled + try { + enabled = await WifiManager.isEnabled() + } catch (e) { + enabled = false + } + if (!enabled) { + const result = { + errMsg: 'startWifi:fail wifi not turned on', + errCode: 12005 + } + fail(result) + complete(result) + return + } + startWifiReady = true + const result = { + errMsg: 'startWifi:success' + } + success(result) + complete(result) + }).catch((err) => { + const result = { + errMsg: 'startWifi:fail ' + (typeof err === 'string' ? err : ''), + errCode: 12001 + } + fail(result) + complete(result) + }) +} + +function stopWifi (options = {}) { + const { success = noop, fail = noop, complete = noop } = options + if (__mpx_mode__ === 'ios') { + const result = { + errMsg: 'stopWifi:fail ios system not support, you need to manually go to the Settings to enable wifi' + } + fail(result) + complete(result) + return + } + startWifiReady = false + wifiListListeners.length = 0 + const result = { + errMsg: 'stopWifi:success' + } + success(result) + complete(result) +} + +function getWifiList (options = {}) { + const { success = noop, fail = noop, complete = noop } = options + if (__mpx_mode__ === 'ios') { + const result = { + errMsg: 'getWifiList:fail ios system not support' + } + fail(result) + complete(result) + return + } + if (!startWifiReady) { + const result = { + errMsg: 'getWifiList:fail not init startWifi', + errCode: 12000 + } + fail(result) + complete(result) + return + } + const WifiManager = require('react-native-wifi-reborn').default + WifiManager.reScanAndLoadWifiList().then((res) => { + const wifiList = res.map(item => { + return { + SSID: item.SSID, + BSSID: item.BSSID, + frequency: item.frequency, + signalStrength: 100 + (item.level || 0) + } + }) + wifiListListeners.forEach(callback => { + if (type(callback) === 'Function') { + callback({ wifiList }) + } + }) + const result = { + errMsg: 'getWifiList:success', + errno: 0, + errCode: 0 + } + success(result) + complete(result) + }).catch(() => { + const result = { + errMsg: 'getWifiList:fail' + } + fail(result) + complete(result) + }) +} + +function onGetWifiList (callback) { + if (!startWifiReady && wifiListListeners.indexOf(callback) > -1) { + return + } + wifiListListeners.push(callback) +} + +function offGetWifiList (callback) { + if (!startWifiReady) { + return + } + const index = wifiListListeners.indexOf(callback) + if (index > -1) { + wifiListListeners.splice(index, 1) + } +} + +function getConnectedWifi (options = {}) { + const { partialInfo = false, success = noop, fail = noop, complete = noop } = options + + if (!startWifiReady) { + const result = { + errMsg: 'getConnectedWifi:fail not init startWifi', + errCode: 12000 + } + fail(result) + complete(result) + return + } + const WifiManager = require('react-native-wifi-reborn').default + if (partialInfo) { + WifiManager.getCurrentWifiSSID().then((res) => { + const wifi = { + SSID: res, + BSSID: '', // iOS无法获取BSSID + signalStrength: 0, + frequency: 0 + } + const result = { + wifi: wifi, + errMsg: 'getConnectedWifi:ok' + } + success(result) + complete(result) + }).catch(() => { + const result = { + errMsg: 'getConnectedWifi:fail' + } + fail(result) + complete(result) + }) + } else { + Promise.all([ + WifiManager.getCurrentWifiSSID().catch(() => null), + WifiManager.getBSSID().catch(() => ''), + WifiManager.getCurrentSignalStrength().catch(() => 0), + WifiManager.getFrequency().catch(() => 0) + ]).then(([ssid, bssid, signalStrength, frequency]) => { + if (!ssid) { + const result = { + errMsg: 'getConnectedWifi:fail' + } + fail(result) + complete(result) + return + } + + const wifi = { + SSID: ssid, + BSSID: bssid, + signalStrength: signalStrength, + frequency: frequency + } + + const result = { + wifi: wifi, + errMsg: 'getConnectedWifi:ok' + } + success(result) + complete(result) + }).catch(() => { + const result = { + errMsg: 'getConnectedWifi:fail' + } + fail(result) + complete(result) + }) + } +} + +export { + startWifi, + stopWifi, + getWifiList, + onGetWifiList, + offGetWifiList, + getConnectedWifi +} diff --git a/packages/api-proxy/src/platform/api/device/wifi/index.js b/packages/api-proxy/src/platform/api/device/wifi/index.js new file mode 100644 index 0000000000..cfbbcc6f60 --- /dev/null +++ b/packages/api-proxy/src/platform/api/device/wifi/index.js @@ -0,0 +1,22 @@ +import { ENV_OBJ, envError } from '../../../../common/js' + +const startWifi = ENV_OBJ.startWifi || envError('startWifi') + +const stopWifi = ENV_OBJ.stopWifi || envError('stopWifi') + +const getWifiList = ENV_OBJ.getWifiList || envError('getWifiList') + +const getConnectedWifi = ENV_OBJ.getConnectedWifi || envError('getConnectedWifi') + +const onGetWifiList = ENV_OBJ.onGetWifiList || envError('onGetWifiList') + +const offGetWifiList = ENV_OBJ.offGetWifiList || envError('offGetWifiList') + +export { + startWifi, + stopWifi, + getWifiList, + onGetWifiList, + offGetWifiList, + getConnectedWifi +} diff --git a/packages/api-proxy/src/platform/index.js b/packages/api-proxy/src/platform/index.js index aba7057503..143986e12e 100644 --- a/packages/api-proxy/src/platform/index.js +++ b/packages/api-proxy/src/platform/index.js @@ -37,6 +37,9 @@ export * from './api/create-selector-query' // getNetworkType, onNetworkStatusChange, offNetworkStatusChange export * from './api/device/network' +// startWifi, stopWifi, getWifiList, onGetWifiList, offGetWifiList, getConnectedWifi +export * from './api/device/wifi' + // downloadFile, uploadFile export * from './api/file'