UNPKG

homebridge-kobold

Version:

A Vorwerk Kobold vacuum robot plugin for homebridge.

617 lines 29.1 kB
import Debug from 'debug'; import 'colors'; const debug = Debug('homebridge-kobold'); const dictionaries = { en: { clean: 'Clean', 'clean the': 'Clean the', goToDock: 'Go to Dock', dockState: 'Dock', eco: 'Eco Mode', noGoLines: 'NoGo Lines', extraCare: 'Extra Care', schedule: 'Schedule', findMe: 'Find me', cleanSpot: 'Clean Spot', battery: 'Battery', }, de: { clean: 'Sauge', 'clean the': 'Sauge', goToDock: 'Zur Basis', dockState: 'In der Basis', eco: 'Eco Modus', noGoLines: 'NoGo Linien', extraCare: 'Extra Care', schedule: 'Zeitplan', findMe: 'Finde mich', cleanSpot: 'Spot Reinigung', battery: 'Batterie', }, fr: { clean: 'Aspirer', 'clean the': 'Aspirer', goToDock: 'Retour à la base', dockState: 'Sur la base', eco: 'Eco mode', noGoLines: 'Lignes NoGo', extraCare: 'Extra Care', schedule: 'Planifier', findMe: 'Me retrouver', cleanSpot: 'Nettoyage local', battery: 'Batterie', }, }; export class KoboldVacuumAccessory { platform; accessory; robotObject; robot; meta; dict; cleanService; batteryService; goToDockService; dockStateService; ecoService; noGoLinesService; extraCareService; scheduleService; findMeService; spotCleanService; spotWidthCharacteristic; spotHeightCharacteristic; spotRepeatCharacteristic; spotPlusFeatures; refreshSetting; nextRoom = null; name; boundaryServices = new Map(); boundaryLabels = new Map(); pendingBoundaryStates = new Map(); constructor(platform, accessory, robotObject) { this.platform = platform; this.accessory = accessory; this.robotObject = robotObject; this.robot = robotObject.device; this.meta = robotObject.meta; this.refreshSetting = this.platform.refresh; this.dict = dictionaries[this.platform.language] ?? dictionaries.en; this.spotPlusFeatures = Array.isArray(this.robotObject.availableServices?.spotCleaning) ? this.robotObject.availableServices.spotCleaning.includes('basic') : false; this.name = this.robot.name; this.accessory.displayName = this.name; this.accessory.context.displayName = this.name; this.accessory.context.robotSerial = this.robot._serial; this.accessory.context.boundaryId = null; const modelName = typeof this.meta.modelName === 'string' ? this.meta.modelName : 'Unknown Model'; const firmware = typeof this.meta.firmware === 'string' ? this.meta.firmware : 'Unknown'; this.informationService() .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Vorwerk Deutschland Stiftung & Co. KG') .setCharacteristic(this.platform.Characteristic.Model, modelName) .setCharacteristic(this.platform.Characteristic.SerialNumber, this.robot._serial) .setCharacteristic(this.platform.Characteristic.FirmwareRevision, firmware) .setCharacteristic(this.platform.Characteristic.Name, this.robot.name); this.cleanService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.clean}`, 'clean'); this.cleanService.getCharacteristic(this.platform.Characteristic.On) .onSet(value => this.setClean(value)) .onGet(() => this.getClean()); this.batteryService = this.createService(this.platform.Service.Battery, `${this.name} ${this.dict.battery}`, 'battery', true); if (this.batteryService) { this.batteryService.getCharacteristic(this.platform.Characteristic.BatteryLevel) .onGet(this.getBatteryLevel.bind(this)); this.batteryService.getCharacteristic(this.platform.Characteristic.ChargingState) .onGet(this.getBatteryChargingState.bind(this)); } const exposeDock = !this.platform.isServiceHidden('dock'); this.goToDockService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.goToDock}`, 'goToDock', exposeDock); if (exposeDock && this.goToDockService) { this.goToDockService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setGoToDock.bind(this)) .onGet(this.getGoToDock.bind(this)); } const exposeDockState = !this.platform.isServiceHidden('dockstate'); this.dockStateService = this.createService(this.platform.Service.OccupancySensor, `${this.name} ${this.dict.dockState}`, 'dockState', exposeDockState); if (exposeDockState && this.dockStateService) { this.dockStateService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) .onGet(this.getDock.bind(this)); } const exposeEco = !this.platform.isServiceHidden('eco'); this.ecoService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.eco}`, 'eco', exposeEco); if (exposeEco && this.ecoService) { this.ecoService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setEco.bind(this)) .onGet(this.getEco.bind(this)); } const exposeNoGo = !this.platform.isServiceHidden('nogolines'); this.noGoLinesService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.noGoLines}`, 'noGoLines', exposeNoGo); if (exposeNoGo && this.noGoLinesService) { this.noGoLinesService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setNoGoLines.bind(this)) .onGet(this.getNoGoLines.bind(this)); } const exposeExtraCare = !this.platform.isServiceHidden('extracare'); this.extraCareService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.extraCare}`, 'extraCare', exposeExtraCare); if (exposeExtraCare && this.extraCareService) { this.extraCareService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setExtraCare.bind(this)) .onGet(this.getExtraCare.bind(this)); } const exposeSchedule = !this.platform.isServiceHidden('schedule'); this.scheduleService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.schedule}`, 'schedule', exposeSchedule); if (exposeSchedule && this.scheduleService) { this.scheduleService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setSchedule.bind(this)) .onGet(this.getSchedule.bind(this)); } const exposeFind = !this.platform.isServiceHidden('find'); this.findMeService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.findMe}`, 'findMe', exposeFind); if (exposeFind && this.findMeService) { this.findMeService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setFindMe.bind(this)) .onGet(this.getFindMe.bind(this)); } const exposeSpot = !this.platform.isServiceHidden('spot'); this.spotCleanService = this.createService(this.platform.Service.Switch, `${this.name} ${this.dict.cleanSpot}`, 'cleanSpot', exposeSpot); if (exposeSpot && this.spotCleanService) { this.spotCleanService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setSpotClean.bind(this)) .onGet(this.getSpotClean.bind(this)); this.spotRepeatCharacteristic = this.getOrAddCharacteristic(this.spotCleanService, this.platform.spotCharacteristics.SpotRepeatCharacteristic); this.spotRepeatCharacteristic ?.onSet(this.setSpotRepeat.bind(this)) .onGet(this.getSpotRepeat.bind(this)); if (this.spotPlusFeatures) { this.spotWidthCharacteristic = this.getOrAddCharacteristic(this.spotCleanService, this.platform.spotCharacteristics.SpotWidthCharacteristic); this.spotHeightCharacteristic = this.getOrAddCharacteristic(this.spotCleanService, this.platform.spotCharacteristics.SpotHeightCharacteristic); this.spotWidthCharacteristic ?.onSet(this.setSpotWidth.bind(this)) .onGet(this.getSpotWidth.bind(this)); this.spotHeightCharacteristic ?.onSet(this.setSpotHeight.bind(this)) .onGet(this.getSpotHeight.bind(this)); } } this.setupBoundaryServices(); } updated() { const currentClean = this.cleanService.getCharacteristic(this.platform.Characteristic.On).value; const robotCanPause = !!this.robot.canPause; if ((currentClean ?? false) !== robotCanPause) { this.cleanService.updateCharacteristic(this.platform.Characteristic.On, robotCanPause); } if (this.goToDockService) { const dockValue = this.goToDockService.getCharacteristic(this.platform.Characteristic.On).value; if (dockValue === true && !!this.robot.dockHasBeenSeen) { this.goToDockService.updateCharacteristic(this.platform.Characteristic.On, false); } } if (this.scheduleService) { const scheduleValue = this.scheduleService.getCharacteristic(this.platform.Characteristic.On).value; const scheduleEnabled = !!this.robot.isScheduleEnabled; if ((scheduleValue ?? false) !== scheduleEnabled) { this.scheduleService.updateCharacteristic(this.platform.Characteristic.On, scheduleEnabled); } } const isDocked = !!this.robot.isDocked; this.dockStateService?.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, isDocked ? 1 : 0); this.ecoService?.updateCharacteristic(this.platform.Characteristic.On, !!this.robot.eco); this.noGoLinesService?.updateCharacteristic(this.platform.Characteristic.On, !!this.robot.noGoLines); this.extraCareService?.updateCharacteristic(this.platform.Characteristic.On, this.robot.navigationMode === 2); const repeatValue = this.robot.spotRepeat ?? false; this.spotRepeatCharacteristic?.updateValue(repeatValue); if (this.spotPlusFeatures && this.spotWidthCharacteristic && this.spotHeightCharacteristic) { const widthProps = this.spotWidthCharacteristic.props; const heightProps = this.spotHeightCharacteristic.props; const widthValid = this.robot.spotWidth !== undefined && this.robot.spotWidth >= (widthProps.minValue ?? 0) && this.robot.spotWidth <= (widthProps.maxValue ?? Number.MAX_SAFE_INTEGER) ? this.robot.spotWidth : widthProps.minValue ?? this.robot.spotWidth ?? widthProps.minValue ?? 0; const heightValid = this.robot.spotHeight !== undefined && this.robot.spotHeight >= (heightProps.minValue ?? 0) && this.robot.spotHeight <= (heightProps.maxValue ?? Number.MAX_SAFE_INTEGER) ? this.robot.spotHeight : heightProps.minValue ?? this.robot.spotHeight ?? heightProps.minValue ?? 0; this.spotWidthCharacteristic.updateValue(widthValid); this.spotHeightCharacteristic.updateValue(heightValid); } this.boundaryServices.forEach((service, boundaryId) => { const isCleaningBoundary = !!this.robot.canPause && this.robot.cleaningBoundaryId === boundaryId; const boundaryValue = service.getCharacteristic(this.platform.Characteristic.On).value; if ((boundaryValue ?? false) !== isCleaningBoundary) { service.updateCharacteristic(this.platform.Characteristic.On, isCleaningBoundary); } else { const pendingState = this.pendingBoundaryStates.get(boundaryId); if (pendingState !== null && pendingState !== undefined) { this.pendingBoundaryStates.set(boundaryId, null); } } }); this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.robot.charge ?? 0); this.batteryService?.updateCharacteristic(this.platform.Characteristic.ChargingState, !!this.robot.isCharging); if (this.nextRoom != null && this.robot.isDocked) { const boundaryId = this.nextRoom; if (!this.boundaryLabels.has(boundaryId)) { this.nextRoom = null; return; } void this.clean({ boundaryId }).then(() => { this.nextRoom = null; const boundaryName = this.boundaryLabels.get(boundaryId) ?? boundaryId; debug(`${this.name}: ## Starting cleaning of next room (${boundaryName})`); }).catch(() => { this.nextRoom = null; }); } } informationService() { return (this.accessory.getService(this.platform.Service.AccessoryInformation) || this.accessory.addService(this.platform.Service.AccessoryInformation)); } boundaryServiceName(boundaryName) { const splitName = boundaryName.split(' '); if (splitName.length >= 2 && /[']s$/g.test(splitName[splitName.length - 2])) { return `${this.dict.clean} ${boundaryName}`; } return `${this.dict['clean the']} ${boundaryName}`; } ensureUniqueBoundaryName(name, usedNames) { let candidate = name; let counter = 2; while (usedNames.has(candidate)) { candidate = `${name} ${counter}`; counter += 1; } usedNames.add(candidate); return candidate; } setupBoundaryServices() { const maps = (Array.isArray(this.robot.maps) ? this.robot.maps : []); const usedNames = new Set(); maps.forEach(map => { if (!Array.isArray(map.boundaries)) { return; } map.boundaries.forEach(boundary => { if (boundary.type !== 'polygon') { return; } if (this.boundaryServices.has(boundary.id)) { return; } const displayName = this.ensureUniqueBoundaryName(boundary.name, usedNames); this.boundaryLabels.set(boundary.id, displayName); const serviceName = this.boundaryServiceName(displayName); const service = this.createService(this.platform.Service.Switch, serviceName, `cleanBoundary:${boundary.id}`); service.getCharacteristic(this.platform.Characteristic.On) .onSet(value => this.setClean(value, boundary.id)) .onGet(() => this.getClean(boundary.id)); this.boundaryServices.set(boundary.id, service); this.pendingBoundaryStates.set(boundary.id, null); }); }); } createService(serviceType, name, subtype, expose = true) { const existing = this.accessory.getServiceById(serviceType, subtype); if (expose) { if (existing) { this.applyConfiguredName(existing, name); return existing; } const service = this.accessory.addService(serviceType, name, subtype); this.applyConfiguredName(service, name); return service; } if (existing) { this.accessory.removeService(existing); } const service = new serviceType(name, subtype); this.applyConfiguredName(service, name); return service; } getOrAddCharacteristic(service, characteristic) { const ctor = characteristic; if (service.testCharacteristic(ctor)) { return service.getCharacteristic(characteristic); } return service.addCharacteristic(characteristic); } applyConfiguredName(service, name) { service.setCharacteristic(this.platform.Characteristic.Name, name); service.addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); service.setCharacteristic(this.platform.Characteristic.ConfiguredName, name); } asBool(value) { return value === true || value === 1; } async getClean(boundaryId) { if (boundaryId) { const pending = this.pendingBoundaryStates.get(boundaryId); if (pending !== null && pending !== undefined) { return pending; } } await this.platform.updateRobot(this.robot._serial); const cleaning = boundaryId ? !!this.robot.canPause && this.robot.cleaningBoundaryId === boundaryId : !!this.robot.canPause; debug(`${this.name}: Cleaning ${boundaryId ?? 'house'} is ${cleaning ? 'ON'.brightGreen : 'OFF'.red}`); return cleaning; } async setClean(value, boundaryId) { const on = this.asBool(value); const boundaryName = boundaryId ? this.boundaryLabels.get(boundaryId) ?? boundaryId : 'home'; debug(`${this.name}: ${on ? 'Enabled '.brightGreen : 'Disabled'.red} Clean ${boundaryName}`); if (boundaryId) { this.pendingBoundaryStates.set(boundaryId, on); } await this.platform.updateRobot(this.robot._serial); if (on) { if (!boundaryId || this.robot.cleaningBoundaryId === boundaryId) { if (this.robot.canResume) { debug(`${this.name}: ## Resume cleaning`); await this.runCommand(cb => this.robot.resumeCleaning(cb)); } else if (this.robot.canStart) { debug(`${this.name}: ## Start cleaning`); await this.clean({ boundaryId }); } else { debug(`${this.name}: Cannot start, maybe already cleaning (expected)`); } } else if (this.robot.canPause || this.robot.canResume) { debug(`${this.name}: ## Returning to dock to start cleaning of new room`); await this.goToDockSequence(); this.nextRoom = boundaryId; } else { debug(`${this.name}: ## Start cleaning of new room`); await this.clean({ boundaryId }); } } else if (this.robot.canPause) { debug(`${this.name}: ## Pause cleaning`); await this.runCommand(cb => this.robot.pauseCleaning(cb)); } else { debug(`${this.name}: Already paused`); } await this.platform.updateRobot(this.robot._serial); if (boundaryId) { this.pendingBoundaryStates.set(boundaryId, null); } } async clean(options) { if (this.refreshSetting === 'auto') { setTimeout(() => { this.platform.updateRobotTimer(this.robot._serial); }, 60 * 1000); } const boundaryId = options?.boundaryId ?? null; const spot = options?.spot; const eco = !!this.robot.eco; const extraCare = this.robot.navigationMode === 2; const noGoLines = !!this.robot.noGoLines; const room = boundaryId ? this.boundaryLabels.get(boundaryId) ?? '' : ''; const roomLabel = room !== '' ? `${room} ` : ''; const detailText = `eco: ${eco}, extraCare: ${extraCare}, nogoLines: ${noGoLines}, spot: ${JSON.stringify(spot)}`; debug(`${this.name}: ## Start cleaning (${roomLabel}${detailText})`); if (!boundaryId && !spot) { await this.runCommand((cb) => this.robot.startCleaning(eco, extraCare ? 2 : 1, noGoLines, cb), (error, result) => { this.platform.log.error(`Cannot start cleaning. ${error}: ${JSON.stringify(result)}`); }); } else if (boundaryId) { await this.runCommand((cb) => this.robot.startCleaningBoundary(eco, extraCare, boundaryId, cb), (error, result) => { this.platform.log.error(`Cannot start room cleaning. ${error}: ${JSON.stringify(result)}`); }); } else if (spot) { const widthValue = spot.width ?? this.robot.spotWidth ?? this.spotWidthCharacteristic?.props.minValue ?? 100; const heightValue = spot.height ?? this.robot.spotHeight ?? this.spotHeightCharacteristic?.props.minValue ?? 100; await this.runCommand((cb) => this.robot.startSpotCleaning(eco, widthValue, heightValue, spot.repeat, extraCare ? 2 : 1, cb), (error, result) => { this.platform.log.error(`Cannot start spot cleaning. ${error}: ${JSON.stringify(result)}`); }); } } async getGoToDock() { return false; } async setGoToDock(value) { const on = this.asBool(value); if (!on) { return; } await this.platform.updateRobot(this.robot._serial); await this.goToDockSequence(); } async getEco() { await this.platform.updateRobot(this.robot._serial); const eco = !!this.robot.eco; debug(`${this.name}: Eco Mode is ${eco ? 'ON'.brightGreen : 'OFF'.red}`); return eco; } async setEco(value) { const on = this.asBool(value); this.robot.eco = on; debug(`${this.name}: ${on ? 'Enabled '.red : 'Disabled'.red} Eco Mode`); } async getNoGoLines() { await this.platform.updateRobot(this.robot._serial); const noGo = !!this.robot.noGoLines; debug(`${this.name}: NoGoLine is ${noGo ? 'ON'.brightGreen : 'OFF'.red}`); return noGo ? 1 : 0; } async setNoGoLines(value) { const on = this.asBool(value); this.robot.noGoLines = on; debug(`${this.name}: ${on ? 'Enabled '.brightGreen : 'Disabled'.red} NoGoLine`); } async getExtraCare() { await this.platform.updateRobot(this.robot._serial); debug(`${this.name}: Care Nav is ${this.robot.navigationMode === 2 ? 'ON'.brightGreen : 'OFF'.red}`); return this.robot.navigationMode === 2 ? 1 : 0; } async setExtraCare(value) { const on = this.asBool(value); this.robot.navigationMode = on ? 2 : 1; debug(`${this.name}: ${on ? 'Enabled '.brightGreen : 'Disabled'.red} Care Nav`); } async getSchedule() { await this.platform.updateRobot(this.robot._serial); const enabled = !!this.robot.isScheduleEnabled; debug(`${this.name}: Schedule is ${enabled ? 'ON'.brightGreen : 'OFF'.red}`); return enabled; } async setSchedule(value) { const on = this.asBool(value); await this.platform.updateRobot(this.robot._serial); if (on) { debug(this.name + ': ' + 'Enabled'.brightGreen + ' Schedule'); await this.runCommand(cb => this.robot.enableSchedule(cb)); } else { debug(this.name + ': ' + 'Disabled'.red + ' Schedule'); await this.runCommand(cb => this.robot.disableSchedule(cb)); } } async getFindMe() { return false; } async setFindMe(value) { const on = this.asBool(value); if (!on) { return; } debug(`${this.name}: ## Find me`); setTimeout(() => { this.findMeService?.updateCharacteristic(this.platform.Characteristic.On, false); }, 1000); await this.runCommand(cb => this.robot.findMe(cb)); } async getSpotClean() { const characteristic = this.spotCleanService?.getCharacteristic(this.platform.Characteristic.On); const spotService = characteristic?.value; return spotService ?? false; } async setSpotClean(value) { const on = this.asBool(value); const spot = { width: this.spotPlusFeatures && this.spotWidthCharacteristic ? (this.spotWidthCharacteristic.value ?? this.robot.spotWidth ?? this.spotWidthCharacteristic.props.minValue ?? 100) : this.robot.spotWidth ?? null, height: this.spotPlusFeatures && this.spotHeightCharacteristic ? (this.spotHeightCharacteristic.value ?? this.robot.spotHeight ?? this.spotHeightCharacteristic.props.minValue ?? 100) : this.robot.spotHeight ?? null, repeat: !!(this.spotRepeatCharacteristic?.value ?? false), }; await this.platform.updateRobot(this.robot._serial); if (on) { if (this.robot.canResume) { debug(`${this.name}: ## Resume (spot) cleaning`); await this.runCommand(cb => this.robot.resumeCleaning(cb)); } else if (this.robot.canStart) { await this.clean({ spot }); } else { debug(`${this.name}: Cannot start spot cleaning, maybe already cleaning`); } } else if (this.robot.canPause) { debug(`${this.name}: ## Pause cleaning`); await this.runCommand(cb => this.robot.pauseCleaning(cb)); } else { debug(`${this.name}: Already paused`); } } async getSpotWidth() { await this.platform.updateRobot(this.robot._serial); const width = this.robot.spotWidth ?? this.spotWidthCharacteristic?.props.minValue ?? 100; debug(`${this.name}: Spot width is ${width}cm`); return width; } async setSpotWidth(value) { const numericValue = typeof value === 'number' ? value : Number(value); this.robot.spotWidth = Number.isNaN(numericValue) ? this.robot.spotWidth ?? 100 : numericValue; debug(`${this.name}: Set spot width to ${this.robot.spotWidth}cm`); } async getSpotHeight() { await this.platform.updateRobot(this.robot._serial); const height = this.robot.spotHeight ?? this.spotHeightCharacteristic?.props.minValue ?? 100; debug(`${this.name}: Spot height is ${height}cm`); return height; } async setSpotHeight(value) { const numericValue = typeof value === 'number' ? value : Number(value); this.robot.spotHeight = Number.isNaN(numericValue) ? this.robot.spotHeight ?? 100 : numericValue; debug(`${this.name}: Set spot height to ${this.robot.spotHeight}cm`); } async getSpotRepeat() { await this.platform.updateRobot(this.robot._serial); const repeat = this.robot.spotRepeat ?? false; debug(`${this.name}: Spot repeat is ${repeat ? 'ON'.brightGreen : 'OFF'.red}`); return repeat; } async setSpotRepeat(value) { const on = this.asBool(value); this.robot.spotRepeat = on; debug(`${this.name}: ${on ? 'Enabled '.brightGreen : 'Disabled'.red} Spot repeat`); } async getDock() { await this.platform.updateRobot(this.robot._serial); const isDocked = !!this.robot.isDocked; debug(`${this.name}: The Dock is ${isDocked ? 'OCCUPIED'.brightGreen : 'NOT OCCUPIED'.red}`); return isDocked ? 1 : 0; } async getBatteryLevel() { await this.platform.updateRobot(this.robot._serial); const charge = this.robot.charge ?? 0; debug(`${this.name}: Battery is ${charge}%`); return charge; } async getBatteryChargingState() { await this.platform.updateRobot(this.robot._serial); const isCharging = !!this.robot.isCharging; debug(`${this.name}: Battery is ${isCharging ? 'CHARGING'.brightGreen : 'NOT CHARGING'.red}`); return isCharging; } async goToDockSequence() { if (this.robot.canPause) { debug(`${this.name}: ## Pause cleaning to go to dock`); await this.runCommand(cb => this.robot.pauseCleaning(cb)); await this.delay(1000); debug(`${this.name}: ## Go to dock`); await this.runCommand(cb => this.robot.sendToBase(cb)); } else if (this.robot.canGoToBase) { debug(`${this.name}: ## Go to dock`); await this.runCommand(cb => this.robot.sendToBase(cb)); } else { this.platform.log.warn(`${this.name}: Can't go to dock at the moment`); } } async runCommand(command, onError) { return new Promise((resolve, reject) => { command((error, result) => { if (error) { onError?.(error, result); reject(error instanceof Error ? error : new Error(String(error))); } else { resolve(result); } }); }); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } //# sourceMappingURL=platformAccessory.js.map