import { BluetoothService } from '@/uni_modules/ak-sbsrv/utssdk/interface.uts' // Platform-specific entrypoint: import the platform index per build target to avoid bundler including Android-only code in web builds import { bluetoothService } from '@/uni_modules/ak-sbsrv/utssdk/app-android/index.uts' import type { BleDevice, BleService, BleCharacteristic } from '@/uni_modules/ak-sbsrv/utssdk/interface.uts' import { ProtocolHandler } from '@/uni_modules/ak-sbsrv/utssdk/protocol_handler.uts' import { dfuManager } from '@/uni_modules/ak-sbsrv/utssdk/app-android/dfu_manager.uts' import { PermissionManager } from '@/ak/PermissionManager.uts' type ShowingCharacteristicsFor = { __$originalPosition?: UTSSourceMapPosition<"ShowingCharacteristicsFor", "pages/akbletest.uvue", 107, 7>; deviceId : string, serviceId : string } const __sfc__ = defineComponent({ data() { return { scanning: false, connecting: false, disconnecting: false, devices: [] as BleDevice[], connectedIds: [] as string[], logs: [] as string[], showingServicesFor: '', services: [] as BleService[], showingCharacteristicsFor: { deviceId: '', serviceId: '' } as ShowingCharacteristicsFor, characteristics: [] as BleCharacteristic[], // 新增协议相关参数 protocolDeviceId: '', protocolServiceId: '', protocolWriteCharId: '', protocolNotifyCharId: '', // protocol handler instances/cache protocolHandlerMap: new Map(), protocolHandler: null as ProtocolHandler | null, // optional services input (comma-separated UUIDs) optionalServicesInput: '', // presets for common BLE services (label -> UUID). 'custom' allows free-form input. presetOptions: [ { label: '无', value: '' }, { label: 'Battery Service (180F)', value: '0000180f-0000-1000-8000-00805f9b34fb' }, { label: 'Device Information (180A)', value: '0000180a-0000-1000-8000-00805f9b34fb' }, { label: 'Generic Attribute (1801)', value: '00001801-0000-1000-8000-00805f9b34fb' }, { label: 'Nordic DFU', value: '00001530-1212-efde-1523-785feabcd123' }, { label: 'Nordic UART (NUS)', value: '6e400001-b5a3-f393-e0a9-e50e24dcca9e' }, { label: '自定义', value: 'custom' } ], presetSelected: '', // map of characteristicId -> boolean (is currently subscribed) notifyingMap: new Map(), } }, mounted() { PermissionManager.requestBluetoothPermissions((granted : boolean) => { if (!granted) { uni.showToast({ title: '请授权蓝牙和定位权限', icon: 'none' }); } }); this.log('页面 mounted: 初始化事件监听和蓝牙权限请求完成') // deviceFound - only accept devices whose name starts with 'CF' or 'BCL' bluetoothService.on('deviceFound', (payload) => { try { // this.log('[event] deviceFound -> ' + this._fmt(payload)) // console.log('[event] deviceFound -> ' + this._fmt(payload)) // payload can be UTSJSONObject-like or plain object. Normalize. let rawDevice = payload?.device; if (rawDevice == null) { this.log('[event] deviceFound - payload.device is null, ignoring'); return; } // extract name let name : string | null = rawDevice.name; if (name == null) { this.log('[event] deviceFound - 无名称,忽略: ' + this._fmt(rawDevice as any)) return; } const n = name as string; if (!(n.startsWith('CF') || n.startsWith('BCL'))) { this.log('[event] deviceFound - 名称不匹配前缀,忽略: ' + n) return; } const exists = this.devices.some(d => d != null && d.name == n); if (!exists) { // rawDevice is non-null here per earlier guard this.devices.push(rawDevice as BleDevice); const deviceIdStr = (rawDevice.deviceId != null) ? rawDevice.deviceId : ''; this.log('发现设备: ' + n + ' (' + deviceIdStr + ')'); } else { const deviceIdStr = (rawDevice.deviceId != null) ? rawDevice.deviceId : ''; this.log('发现重复设备: ' + n + ' (' + deviceIdStr + ')') } } catch (err) { this.log('[error] deviceFound handler error: ' + getErrorMessage(err)) console.log(err, " at pages/akbletest.uvue:198") } }) // scanFinished bluetoothService.on('scanFinished', (payload) => { try { this.scanning = false this.log('[event] scanFinished -> ' + this._fmt(payload)) } catch (err) { this.log('[error] scanFinished handler error: ' + getErrorMessage(err)) } }) // connectionStateChanged bluetoothService.on('connectionStateChanged', (payload) => { try { this.log('[event] connectionStateChanged -> ' + this._fmt(payload)) if (payload != null) { const device = payload.device const state = payload.state this.log(`设备 ${device?.deviceId} 连接状态变为: ${state}`) // maintain connectedIds if (state == 2) { if (device != null && device.deviceId != null && !this.connectedIds.includes(device.deviceId)) { this.connectedIds.push(device.deviceId) this.log(`已记录已连接设备: ${device.deviceId}`) } } else if (state == 0) { if (device != null && device.deviceId != null) { this.connectedIds = this.connectedIds.filter(id => id !== device.deviceId) this.log(`已移除已断开设备: ${device.deviceId}`) } } } } catch (err) { this.log('[error] connectionStateChanged handler error: ' + getErrorMessage(err)) } }) }, methods: { async startDfuFlow(deviceId : string, staticFilePath : string = '') { if (staticFilePath != null && staticFilePath !== '') { this.log('DFU 开始: 使用内置固件文件 ' + staticFilePath) } else { this.log('DFU 开始: 请选择固件文件') } try { let chosenPath : string | null = null let fileName : string | null = null if (staticFilePath != null && staticFilePath !== '') { // Use the app's bundled static file path chosenPath = staticFilePath.replace(/^\/+/, '') const tmpName = staticFilePath.split(/[\/]/).pop() fileName = (tmpName != null && tmpName !== '') ? tmpName : staticFilePath } else { const res = await new Promise((resolve, reject) => { uni.chooseFile({ count: 1, success: (r) => resolve(r), fail: (e) => reject(e) }) }) console.log(res, " at pages/akbletest.uvue:257") // Generator-friendly: avoid property iteration or bracket indexing. // Serialize and regex-match common file fields (path/uri/tempFilePath/name). try { const s = (() => { try { return JSON.stringify(res); } catch (e) { return ''; } })() const m = s.match(/"(?:path|uri|tempFilePath|temp_file_path|tempFilePath|name)"\s*:\s*"([^"]+)"/i) if (m != null && m.length >= 2) { const capturedCandidate : string | null = (m[1] != null ? m[1] : null) const captured : string = capturedCandidate != null ? capturedCandidate : '' if (captured !== '') { chosenPath = captured const toTest : string = captured if (!(/^[a-zA-Z]:\\|^\\\//.test(toTest) || /:\/\//.test(toTest))) { const m2 = s.match(/"(?:path|uri|tempFilePath|temp_file_path|tempFilePath)"\s*:\s*"([^"]+)"/i) if (m2 != null && m2.length >= 2 && m2[1] != null) { const pathCandidate : string = m2[1] != null ? ('' + m2[1]) : '' if (pathCandidate !== '') chosenPath = pathCandidate } } } } const nameMatch = s.match(/"name"\s*:\s*"([^"]+)"/i) if (nameMatch != null && nameMatch.length >= 2 && nameMatch[1] != null) { const nm : string = nameMatch[1] != null ? ('' + nameMatch[1]) : '' if (nm !== '') fileName = nm } } catch (err) { /* ignore */ } } if (chosenPath == null || chosenPath == '') { this.log('未选择文件') return } // filePath is non-null and non-empty here const fpStr : string = chosenPath as string const lastSeg = fpStr.split(/[\/]/).pop(); const displayName = (fileName != null && fileName !== '') ? fileName : (lastSeg != null && lastSeg !== '' ? lastSeg : fpStr) this.log('已选文件: ' + displayName + ' 路径: ' + fpStr) const bytes = await this._readFileAsUint8Array(fpStr) this.log('固件读取完成, 大小: ' + bytes.length) try { await dfuManager.startDfu(deviceId, bytes, { useNordic: false, onProgress: (p : number) => this.log('DFU 进度: ' + p + '%'), onLog: (s : string) => this.log('DFU: ' + s), controlTimeout: 30000 }) this.log('DFU 完成') } catch (e) { this.log('DFU 失败: ' + getErrorMessage(e)) } } catch (e) { console.log('选择或读取固件失败: ' + e, " at pages/akbletest.uvue:308") } }, _readFileAsUint8Array(path : string) : Promise { return new Promise((resolve, reject) => { try { console.log('should readfile', " at pages/akbletest.uvue:315") const fsm = uni.getFileSystemManager() console.log(fsm, " at pages/akbletest.uvue:317") // Read file as ArrayBuffer directly to avoid base64 encoding issues fsm.readFile({ filePath: path, success: (res) => { try { const data = res.data as ArrayBuffer const arr = new Uint8Array(data) resolve(arr) } catch (e) { reject(e) } }, fail: (err) => { reject(err) } }) } catch (e) { reject(e) } }) }, log(msg : string) { const ts = new Date().toISOString(); this.logs.unshift(`[${ts}] ${msg}`) if (this.logs.length > 100) this.logs.length = 100 }, _fmt(obj : any) : string { try { if (obj == null) return 'null' if (typeof obj == 'string') return obj return JSON.stringify(obj) } catch (e) { return '' + obj } }, onPresetChange(e : any) { try { // Some platforms emit { detail: { value: 'x' } }, others emit { value: 'x' } or just 'x'. // Serialize and regex-extract to avoid direct property access that the UTS->Kotlin generator may emit incorrectly. const s = (() => { try { return JSON.stringify(e); } catch (err) { return ''; } })() let val : string = this.presetSelected // try detail.value first const m = s.match(/"detail"\s*:\s*\{[^}]*"value"\s*:\s*"([^\"]+)"/i) if (m != null && m.length >= 2 && m[1] != null) { val = '' + m[1] } else { const m2 = s.match(/"value"\s*:\s*"([^\"]+)"/i) if (m2 != null && m2.length >= 2 && m2[1] != null) { val = '' + m2[1] } } this.presetSelected = val if (val == 'custom' || val == '') { this.log('已选择预设: ' + (val == 'custom' ? '自定义' : '无')) return; } this.optionalServicesInput = val; this.log('已选择预设服务 UUID: ' + val) } catch (err) { this.log('[error] onPresetChange: ' + getErrorMessage(err)) } }, scanDevices() { try { this.scanning = true this.devices = [] // prepare optional services: prefer free-form input, otherwise use selected preset (unless preset is 'custom' or empty) let raw = (this.optionalServicesInput != null ? this.optionalServicesInput : '').trim(); if (raw.length == 0 && this.presetSelected != null && this.presetSelected !== '' && this.presetSelected !== 'custom') { raw = this.presetSelected; } // normalize helper: expand 16-bit UUIDs like '180F' to full 128-bit UUIDs const normalize = (s : string) => { if (s == null || s.length == 0) return ''; const u = s.toLowerCase().replace(/^0x/, '').trim(); const hex = u.replace(/[^0-9a-f]/g, ''); if (/^[0-9a-f]{4}$/.test(hex)) return `0000${hex}-0000-1000-8000-00805f9b34fb`; return s; }; const optionalServices = raw.length > 0 ? raw.split(',').map(s => normalize(s.trim())).filter(s => s.length > 0) : [] this.log('开始扫描... optionalServices=' + JSON.stringify(optionalServices)) bluetoothService.scanDevices({ "protocols": ['BLE'], "optionalServices": optionalServices }) .then(() => { this.log('scanDevices resolved') }) .catch((e) => { this.log('[error] scanDevices failed: ' + getErrorMessage(e)) this.scanning = false }) } catch (err) { this.log('[error] scanDevices thrown: ' + getErrorMessage(err)) this.scanning = false } }, connect(deviceId : string) { this.connecting = true this.log(`connect start -> ${deviceId}`) try { bluetoothService.connectDevice(deviceId, 'BLE', { timeout: 10000 }).then(() => { if (!this.connectedIds.includes(deviceId)) this.connectedIds.push(deviceId) this.log('连接成功: ' + deviceId) }).catch((e) => { this.log('连接失败: ' + getErrorMessage(e!)); }).finally(() => { this.connecting = false this.log(`connect finished -> ${deviceId}`) }) } catch (err) { this.log('[error] connect thrown: ' + getErrorMessage(err)) this.connecting = false } }, disconnect(deviceId : string) { if (!this.connectedIds.includes(deviceId)) return this.disconnecting = true this.log(`disconnect start -> ${deviceId}`) bluetoothService.disconnectDevice(deviceId, 'BLE').then(() => { this.log('已断开: ' + deviceId) this.connectedIds = this.connectedIds.filter(id => id !== deviceId) // 清理协议处理器缓存 this.protocolHandlerMap.delete(deviceId) }).catch((e) => { this.log('断开失败: ' + getErrorMessage(e!)); }).finally(() => { this.disconnecting = false this.log(`disconnect finished -> ${deviceId}`) }) }, showServices(deviceId : string) { this.showingServicesFor = deviceId this.services = [] this.log(`showServices start -> ${deviceId}`) bluetoothService.getServices(deviceId).then((list) => { this.log('showServices result -> ' + this._fmt(list)) this.services = list as BleService[] this.log('服务数: ' + (list != null ? list.length : 0) + ' [' + deviceId + ']'); }).catch((e) => { this.log('获取服务失败: ' + getErrorMessage(e!)); }).finally(() => { this.log(`showServices finished -> ${deviceId}`) }); }, closeServices() { this.showingServicesFor = '' this.services = [] }, showCharacteristics(deviceId : string, serviceId : string) { this.showingCharacteristicsFor = { deviceId, serviceId } this.characteristics = [] bluetoothService.getCharacteristics(deviceId, serviceId).then((list) => { this.characteristics = list as BleCharacteristic[] console.log('特征数: ' + (list != null ? list.length : 0) + ' [' + deviceId + ']', " at pages/akbletest.uvue:462"); // 自动查找可用的写入和通知特征 const writeChar = this.characteristics.find(c => c.properties.write) const notifyChar = this.characteristics.find(c => c.properties.notify) if (writeChar != null && notifyChar != null) { this.protocolDeviceId = deviceId this.protocolServiceId = serviceId this.protocolWriteCharId = writeChar.uuid this.protocolNotifyCharId = notifyChar.uuid let abs = bluetoothService as BluetoothService this.protocolHandler = new ProtocolHandler(abs) let handler = this.protocolHandler handler?.setConnectionParameters(deviceId, serviceId, writeChar.uuid, notifyChar.uuid) handler?.initialize()?.then(() => { console.log("协议处理器已初始化,可进行协议测试", " at pages/akbletest.uvue:476") })?.catch(e => { console.log("协议处理器初始化失败: " + getErrorMessage(e!), " at pages/akbletest.uvue:478") }) } }).catch((e) => { console.log('获取特征失败: ' + getErrorMessage(e!), " at pages/akbletest.uvue:482"); }); // tracking notifying state // this.$set(this, 'notifyingMap', this.notifyingMap || {}); }, closeCharacteristics() { this.showingCharacteristicsFor = { deviceId: '', serviceId: '' } this.characteristics = [] }, charProps(char : BleCharacteristic) : string { const p = char.properties const parts = [] as string[] if (p.read) parts.push('R') if (p.write) parts.push('W') if (p.notify) parts.push('N') if (p.indicate) parts.push('I') return parts.join('/') // return [p.read ? 'R' : '', p.write ? 'W' : '', p.notify ? 'N' : '', p.indicate ? 'I' : ''].filter(Boolean).join('/') }, isNotifying(uuid : string) { return this.notifyingMap.has(uuid) && this.notifyingMap.get(uuid) == true }, async readCharacteristic(deviceId : string, serviceId : string, charId : string) { try { this.log(`readCharacteristic ${charId} ...`) const buf = await bluetoothService.readCharacteristic(deviceId, serviceId, charId) let text = '' try { text = new TextDecoder().decode(new Uint8Array(buf)) } catch (e) { text = '' } const hex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(' ') console.log(`读取 ${charId}: text='${text}' hex='${hex}'`, " at pages/akbletest.uvue:511") this.log(`读取 ${charId}: text='${text}' hex='${hex}'`) } catch (e) { this.log('读取特征失败: ' + getErrorMessage(e)) } }, async writeCharacteristic(deviceId : string, serviceId : string, charId : string) { try { const payload = new Uint8Array([0x01]) const ok = await bluetoothService.writeCharacteristic(deviceId, serviceId, charId, payload, null) if (ok) this.log(`写入 ${charId} 成功`); else this.log(`写入 ${charId} 失败`); } catch (e) { this.log('写入特征失败: ' + getErrorMessage(e)) } }, async toggleNotify(deviceId : string, serviceId : string, charId : string) { try { const map = this.notifyingMap const cur = map.get(charId) == true if (cur) { // unsubscribe await bluetoothService.unsubscribeCharacteristic(deviceId, serviceId, charId) map.set(charId, false) this.log(`取消订阅 ${charId}`) } else { // subscribe with callback await bluetoothService.subscribeCharacteristic(deviceId, serviceId, charId, (payload : any) => { let data : ArrayBuffer | null = null try { if (payload instanceof ArrayBuffer) { data = payload } else if (payload != null && typeof payload == 'string') { // some runtimes deliver base64 strings try { const s = atob(payload) const tmp = new Uint8Array(s.length) for (let i = 0; i < s.length; i++) { const ch = s.charCodeAt(i) tmp[i] = (ch == null) ? 0 : (ch & 0xff) } data = tmp.buffer } catch (e) { data = null } } else if (payload != null && (payload as UTSJSONObject).get('data') instanceof ArrayBuffer) { data = (payload as UTSJSONObject).get('data') as ArrayBuffer } const arr = data != null ? new Uint8Array(data) : new Uint8Array([]) const hex = Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join(' ') this.log(`notify ${charId}: ${hex}`) } catch (e) { this.log('notify callback error: ' + getErrorMessage(e)) } }) map.set(charId, true) this.log(`订阅 ${charId}`) } } catch (e) { this.log('订阅/取消订阅失败: ' + getErrorMessage(e)) } }, autoConnect() { if (this.connecting) return; this.connecting = true; const toConnect = this.devices.filter(d => !this.connectedIds.includes(d.deviceId)); if (toConnect.length == 0) { this.log('没有可自动连接的设备'); this.connecting = false; return; } let successCount = 0; let failCount = 0; let finished = 0; toConnect.forEach(device => { bluetoothService.connectDevice(device.deviceId, 'BLE', { timeout: 10000 }).then(() => { if (!this.connectedIds.includes(device.deviceId)) this.connectedIds.push(device.deviceId); this.log('自动连接成功: ' + device.deviceId); successCount++; // this.getOrInitProtocolHandler(device.deviceId); }).catch((e) => { this.log('自动连接失败: ' + device.deviceId + ' ' + getErrorMessage(e!)); failCount++; }).finally(() => { finished++; if (finished == toConnect.length) { this.connecting = false; this.log(`自动连接完成,成功${successCount},失败${failCount}`); } }); }); }, autoDiscoverInterfaces(deviceId : string) { this.log('自动发现接口中...') bluetoothService.getAutoBleInterfaces(deviceId) .then((res) => { console.log(res, " at pages/akbletest.uvue:604") this.log('自动发现接口成功: ' + JSON.stringify(res)) }) .catch((e) => { console.log(e, " at pages/akbletest.uvue:608") this.log('自动发现接口失败: ' + getErrorMessage(e!)) }) }, // 新增:测试电量功能 async getOrInitProtocolHandler(deviceId : string) : Promise { let handler = this.protocolHandlerMap.get(deviceId); if (handler == null) { // 自动发现接口 const res = await bluetoothService.getAutoBleInterfaces(deviceId); handler = new ProtocolHandler(bluetoothService as BluetoothService); handler.setConnectionParameters(deviceId, res.serviceId, res.writeCharId, res.notifyCharId); await handler.initialize(); this.protocolHandlerMap.set(deviceId, handler); this.log(`协议处理器已初始化: ${deviceId}`); } return handler!; }, async getDeviceInfo(deviceId : string) { this.log('获取设备信息中...'); try { // First try protocol handler (if device exposes custom protocol) try { const handler = await this.getOrInitProtocolHandler(deviceId); // 获取电量 const battery = await handler.testBatteryLevel(); this.log('协议: 电量: ' + battery); // 获取软件/硬件版本 const swVersion = await handler.testVersionInfo(false); this.log('协议: 软件版本: ' + swVersion); const hwVersion = await handler.testVersionInfo(true); this.log('协议: 硬件版本: ' + hwVersion); } catch (protoErr) { this.log('协议处理器不可用或初始化失败,继续使用通用 GATT 查询: ' + ((protoErr != null && protoErr instanceof Error) ? protoErr.message : this._fmt(protoErr))); } // Additionally, attempt to read standard services: Generic Access (0x1800), Generic Attribute (0x1801), Battery (0x180F) const stdServices = ['1800', '1801', '180f'].map(s => { const hex = s.toLowerCase().replace(/^0x/, ''); return /^[0-9a-f]{4}$/.test(hex) ? `0000${hex}-0000-1000-8000-00805f9b34fb` : s; }); // fetch services once to avoid repeated GATT server queries const services = await bluetoothService.getServices(deviceId); for (const svc of stdServices) { try { this.log('读取服务: ' + svc); // find matching service const found = services.find((x : any) => { const uuid = (x as UTSJSONObject).get('uuid') return uuid != null && uuid.toString().toLowerCase() == svc.toLowerCase() }); if (found == null) { this.log('未发现服务 ' + svc + '(需重新扫描并包含 optionalServices)'); continue; } const chars = await bluetoothService.getCharacteristics(deviceId, found?.uuid as string); console.log(`服务 ${svc} 包含 ${chars.length} 个特征`, chars, " at pages/akbletest.uvue:665"); for (const c of chars) { try { if (c.properties?.read == true) { const buf = await bluetoothService.readCharacteristic(deviceId, found?.uuid as string, c.uuid); // try to decode as utf8 then hex let text = ''; try { text = new TextDecoder().decode(new Uint8Array(buf)); } catch (e) { text = ''; } const hex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(' '); console.log(`特征 ${c.uuid} 读取: text='${text}' hex='${hex}'`, " at pages/akbletest.uvue:674"); } else { console.log(`特征 ${c.uuid} 不可读`, " at pages/akbletest.uvue:676"); } } catch (e) { console.log(`读取特征 ${c.uuid} 失败: ${getErrorMessage(e)}`, " at pages/akbletest.uvue:679"); } } } catch (e) { console.log('查询服务 ' + svc + ' 失败: ' + getErrorMessage(e), " at pages/akbletest.uvue:683"); } } } catch (e) { console.log('获取设备信息失败: ' + getErrorMessage(e), " at pages/akbletest.uvue:688"); } } } }) function getErrorMessage(e : Error | string | null) : string { if (e == null) return ''; if (typeof e == 'string') return e; try { return JSON.stringify(e); } catch (err) { return '' + e; } } export default __sfc__ function GenPagesAkbletestRender(this: InstanceType): any | null { const _ctx = this const _cache = this.$.renderCache return _cE("scroll-view", _uM({ direction: "vertical", class: "container" }), [ _cE("view", _uM({ class: "section" }), [ _cE("button", _uM({ onClick: _ctx.scanDevices, disabled: _ctx.scanning }), _tD(_ctx.scanning ? '正在扫描...' : '扫描设备'), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), _cE("input", _uM({ modelValue: _ctx.optionalServicesInput, onInput: ($event: UniInputEvent) => {(_ctx.optionalServicesInput) = $event.detail.value}, placeholder: "可选服务 UUID, 逗号分隔", style: _nS(_uM({"margin-left":"12px","width":"40%"})) }), null, 44 /* STYLE, PROPS, NEED_HYDRATION */, ["modelValue", "onInput"]), _cE("button", _uM({ onClick: _ctx.autoConnect, disabled: _ctx.connecting || _ctx.devices.length == 0, style: _nS(_uM({"margin-left":"12px"})) }), _tD(_ctx.connecting ? '正在自动连接...' : '自动连接'), 13 /* TEXT, STYLE, PROPS */, ["onClick", "disabled"]), _cE("view", null, [ _cE("text", null, "设备计数: " + _tD(_ctx.devices.length), 1 /* TEXT */), _cE("text", _uM({ style: _nS(_uM({"font-size":"12px","color":"gray"})) }), _tD(_ctx._fmt(_ctx.devices)), 5 /* TEXT, STYLE */) ]), isTrue(_ctx.devices.length) ? _cE("view", _uM({ key: 0 }), [ _cE("text", null, "已发现设备:"), _cE(Fragment, null, RenderHelpers.renderList(_ctx.devices, (item, __key, __index, _cached): any => { return _cE("view", _uM({ key: item.deviceId, class: "device-item" }), [ _cE("text", null, _tD(item.name!='' ? item.name : '未知设备') + " (" + _tD(item.deviceId) + ")", 1 /* TEXT */), _cE("button", _uM({ onClick: () => {_ctx.connect(item.deviceId)} }), "连接", 8 /* PROPS */, ["onClick"]), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 0, onClick: () => {_ctx.disconnect(item.deviceId)}, disabled: _ctx.disconnecting }), "断开", 8 /* PROPS */, ["onClick", "disabled"]) : _cC("v-if", true), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 1, onClick: () => {_ctx.showServices(item.deviceId)} }), "查看服务", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 2, onClick: () => {_ctx.autoDiscoverInterfaces(item.deviceId)} }), "自动发现接口", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 3, onClick: () => {_ctx.getDeviceInfo(item.deviceId)} }), "设备信息", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 4, onClick: () => {_ctx.startDfuFlow(item.deviceId)} }), "DFU 升级", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(_ctx.connectedIds.includes(item.deviceId)) ? _cE("button", _uM({ key: 5, onClick: () => {_ctx.startDfuFlow(item.deviceId, '/static/OmFw2509140009.zip')} }), "使用内置固件 DFU", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true) ]) }), 128 /* KEYED_FRAGMENT */) ]) : _cC("v-if", true) ]), _cE("view", _uM({ class: "section" }), [ _cE("text", null, "日志:"), _cE("scroll-view", _uM({ direction: "vertical", style: _nS(_uM({"height":"240px"})) }), [ _cE(Fragment, null, RenderHelpers.renderList(_ctx.logs, (log, idx, __index, _cached): any => { return _cE("text", _uM({ key: idx, style: _nS(_uM({"font-size":"12px"})) }), _tD(log), 5 /* TEXT, STYLE */) }), 128 /* KEYED_FRAGMENT */) ], 4 /* STYLE */) ]), isTrue(_ctx.showingServicesFor) ? _cE("view", _uM({ key: 0 }), [ _cE("view", _uM({ class: "section" }), [ _cE("text", null, "设备 " + _tD(_ctx.showingServicesFor) + " 的服务:", 1 /* TEXT */), isTrue(_ctx.services.length) ? _cE("view", _uM({ key: 0 }), [ _cE(Fragment, null, RenderHelpers.renderList(_ctx.services, (srv, __key, __index, _cached): any => { return _cE("view", _uM({ key: srv.uuid, class: "service-item" }), [ _cE("text", null, _tD(srv.uuid), 1 /* TEXT */), _cE("button", _uM({ onClick: () => {_ctx.showCharacteristics(_ctx.showingServicesFor, srv.uuid)} }), "查看特征", 8 /* PROPS */, ["onClick"]) ]) }), 128 /* KEYED_FRAGMENT */) ]) : _cE("view", _uM({ key: 1 }), [ _cE("text", null, "无服务") ]), _cE("button", _uM({ onClick: _ctx.closeServices }), "关闭", 8 /* PROPS */, ["onClick"]) ]) ]) : _cC("v-if", true), isTrue(_ctx.showingCharacteristicsFor) ? _cE("view", _uM({ key: 1 }), [ _cE("view", _uM({ class: "section" }), [ _cE("text", null, "服务 的特征:"), isTrue(_ctx.characteristics.length) ? _cE("view", _uM({ key: 0 }), [ _cE(Fragment, null, RenderHelpers.renderList(_ctx.characteristics, (char, __key, __index, _cached): any => { return _cE("view", _uM({ key: char.uuid, class: "char-item" }), [ _cE("text", null, _tD(char.uuid) + " [" + _tD(_ctx.charProps(char)) + "]", 1 /* TEXT */), _cE("view", _uM({ style: _nS(_uM({"display":"flex","flex-direction":"row","margin-top":"6px"})) }), [ isTrue(char.properties?.read) ? _cE("button", _uM({ key: 0, onClick: () => {_ctx.readCharacteristic(_ctx.showingCharacteristicsFor.deviceId, _ctx.showingCharacteristicsFor.serviceId, char.uuid)} }), "读取", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(char.properties?.write) ? _cE("button", _uM({ key: 1, onClick: () => {_ctx.writeCharacteristic(_ctx.showingCharacteristicsFor.deviceId, _ctx.showingCharacteristicsFor.serviceId, char.uuid)} }), "写入(测试)", 8 /* PROPS */, ["onClick"]) : _cC("v-if", true), isTrue(char.properties?.notify) ? _cE("button", _uM({ key: 2, onClick: () => {_ctx.toggleNotify(_ctx.showingCharacteristicsFor.deviceId, _ctx.showingCharacteristicsFor.serviceId, char.uuid)} }), _tD(_ctx.isNotifying(char.uuid) ? '取消订阅' : '订阅'), 9 /* TEXT, PROPS */, ["onClick"]) : _cC("v-if", true) ], 4 /* STYLE */) ]) }), 128 /* KEYED_FRAGMENT */) ]) : _cE("view", _uM({ key: 1 }), [ _cE("text", null, "无特征") ]), _cE("button", _uM({ onClick: _ctx.closeCharacteristics }), "关闭", 8 /* PROPS */, ["onClick"]) ]) ]) : _cC("v-if", true) ]) } const GenPagesAkbletestStyles = [_uM([["container", _pS(_uM([["paddingTop", 16], ["paddingRight", 16], ["paddingBottom", 16], ["paddingLeft", 16], ["flex", 1]]))], ["section", _pS(_uM([["marginBottom", 18]]))], ["device-item", _pS(_uM([["display", "flex"], ["flexDirection", "row"], ["flexWrap", "wrap"]]))], ["service-item", _pS(_uM([["marginTop", 6], ["marginRight", 0], ["marginBottom", 6], ["marginLeft", 0]]))], ["char-item", _pS(_uM([["marginTop", 6], ["marginRight", 0], ["marginBottom", 6], ["marginLeft", 0]]))]])]