@@ -11,7 +11,7 @@ import { EventEmitter } from 'node:events'
1111
1212import { BLENotAvailableError , DeviceNotFoundError } from './errors.js'
1313import { BLE_COMMAND_TIMEOUT , BLE_CONNECT_TIMEOUT , BLE_NOTIFY_CHARACTERISTIC_UUID , BLE_SCAN_TIMEOUT , BLE_SERVICE_UUID , BLE_WRITE_CHARACTERISTIC_UUID , DEVICE_MODEL_MAP } from './settings.js'
14- import { Logger , normalizeMAC , withTimeout } from './utils/index.js'
14+ import { Logger , macToDeviceId , normalizeMAC , withTimeout , extractMacFromManufacturerData } from './utils/index.js'
1515
1616/**
1717 * BLE Scanner for discovering SwitchBot devices
@@ -124,8 +124,14 @@ export class BLEScanner extends EventEmitter {
124124 }
125125
126126 for ( const serviceDataItem of advertisement . serviceData ) {
127- // SwitchBot service UUID
128- if ( serviceDataItem . uuid !== 'fd3d' && serviceDataItem . uuid !== '0000fd3d-0000-1000-8000-00805f9b34fb' ) {
127+ // SwitchBot service UUID (current: fd3d, legacy: 000d)
128+ const uuid = typeof serviceDataItem . uuid === 'string' ? serviceDataItem . uuid . toLowerCase ( ) : ''
129+ const isSwitchBotUUID = uuid === 'fd3d' ||
130+ uuid === '0000fd3d-0000-1000-8000-00805f9b34fb' ||
131+ uuid === '000d' ||
132+ uuid === '0000000d-0000-1000-8000-00805f9b34fb'
133+
134+ if ( ! isSwitchBotUUID ) {
129135 continue
130136 }
131137
@@ -135,17 +141,37 @@ export class BLEScanner extends EventEmitter {
135141 continue
136142 }
137143
138- const mac = normalizeMAC ( address )
144+ let normalizedAddress = typeof address === 'string' && address . length > 0 ? normalizeMAC ( address ) : undefined
145+
146+ // Fallback to manufacturer data MAC if service data address is empty
147+ if ( ! normalizedAddress ) {
148+ const manufacturerMac = extractMacFromManufacturerData ( peripheral . advertisement . manufacturerData )
149+ if ( manufacturerMac ) {
150+ normalizedAddress = manufacturerMac
151+ }
152+ }
153+
154+ const fallbackId = typeof peripheral . id === 'string' && peripheral . id . length > 0
155+ ? peripheral . id
156+ : ( normalizedAddress ? macToDeviceId ( normalizedAddress ) : undefined )
157+
158+ if ( ! fallbackId ) {
159+ this . logger . debug ( 'Skipping BLE discovery with no address and no peripheral id' )
160+ continue
161+ }
162+
139163 const advertisement : BLEAdvertisement = {
140- id : peripheral . id ,
141- address : mac ,
164+ id : fallbackId ,
165+ address : normalizedAddress ,
166+ isAddressable : normalizedAddress !== undefined ,
142167 rssi,
143168 serviceData,
144169 }
145170
146- this . discoveredDevices . set ( mac , advertisement )
171+ const discoveryKey = normalizedAddress ?? `id:${ fallbackId } `
172+ this . discoveredDevices . set ( discoveryKey , advertisement )
147173 this . emit ( 'discover' , advertisement )
148- this . logger . debug ( `Discovered ${ serviceData . modelName } at ${ mac } ` )
174+ this . logger . debug ( `Discovered ${ serviceData . modelName } at ${ normalizedAddress ?? fallbackId } ` )
149175 }
150176 } catch ( error ) {
151177 this . logger . error ( 'Error handling discovery' , error )
@@ -247,10 +273,21 @@ export class BLEScanner extends EventEmitter {
247273 }
248274
249275 /**
250- * Get discovered device by MAC
276+ * Get discovered device by MAC or BLE ID
251277 */
252- getDevice ( mac : string ) : BLEAdvertisement | undefined {
253- return this . discoveredDevices . get ( normalizeMAC ( mac ) )
278+ getDevice ( mac : string , bleId ?: string ) : BLEAdvertisement | undefined {
279+ // Try MAC lookup first
280+ const byMac = this . discoveredDevices . get ( normalizeMAC ( mac ) )
281+ if ( byMac ) {
282+ return byMac
283+ }
284+
285+ // Fall back to ID-based lookup
286+ if ( bleId ) {
287+ return this . discoveredDevices . get ( `id:${ bleId } ` )
288+ }
289+
290+ return undefined
254291 }
255292
256293 /**
@@ -263,11 +300,11 @@ export class BLEScanner extends EventEmitter {
263300 /**
264301 * Wait for specific device
265302 */
266- async waitForDevice ( mac : string , timeoutMs = BLE_SCAN_TIMEOUT ) : Promise < BLEAdvertisement > {
303+ async waitForDevice ( mac : string , timeoutMs = BLE_SCAN_TIMEOUT , bleId ?: string ) : Promise < BLEAdvertisement > {
267304 const normalizedMac = normalizeMAC ( mac )
268305
269- // Check if already discovered
270- const existing = this . discoveredDevices . get ( normalizedMac )
306+ // Check if already discovered (try MAC first, then ID)
307+ const existing = this . discoveredDevices . get ( normalizedMac ) || ( bleId ? this . discoveredDevices . get ( `id: ${ bleId } ` ) : undefined )
271308 if ( existing ) {
272309 return existing
273310 }
@@ -276,7 +313,10 @@ export class BLEScanner extends EventEmitter {
276313 return withTimeout (
277314 new Promise < BLEAdvertisement > ( ( resolve ) => {
278315 const handler = ( advertisement : BLEAdvertisement ) => {
279- if ( normalizeMAC ( advertisement . address ) === normalizedMac ) {
316+ // Match by address (if available) or by ID
317+ const matches = ( advertisement . address && normalizeMAC ( advertisement . address ) === normalizedMac ) ||
318+ ( bleId && advertisement . id === bleId )
319+ if ( matches ) {
280320 this . off ( 'discover' , handler )
281321 resolve ( advertisement )
282322 }
@@ -368,9 +408,20 @@ export class BLEConnection {
368408
369409 this . logger . info ( `Connecting to ${ mac } ` )
370410
371- // Find peripheral
411+ // Find peripheral (by address or ID)
372412 const peripherals = await this . noble . peripherals || [ ]
373- const peripheral = peripherals . find ( ( p : any ) => normalizeMAC ( p . address ) === normalizedMac )
413+ let peripheral : any
414+
415+ // Try to find by normalized MAC first
416+ if ( mac . startsWith ( 'id:' ) ) {
417+ // ID-based lookup: extract the ID and find by peripheral.id
418+ const bleId = mac . substring ( 3 )
419+ // Look through peripherals to find matching ID
420+ peripheral = peripherals . find ( ( p : any ) => p . id === bleId )
421+ } else {
422+ // MAC-based lookup
423+ peripheral = peripherals . find ( ( p : any ) => p . address && normalizeMAC ( p . address ) === normalizedMac )
424+ }
374425
375426 if ( ! peripheral ) {
376427 throw new DeviceNotFoundError ( mac )
0 commit comments