diff --git a/src/accessories/koboldVacuumRobot.ts b/src/accessories/koboldVacuumRobot.ts index 7204854..87555d9 100644 --- a/src/accessories/koboldVacuumRobot.ts +++ b/src/accessories/koboldVacuumRobot.ts @@ -1,9 +1,17 @@ -import {CharacteristicValue, Logger, PlatformAccessory, PlatformAccessoryEvent, PlatformConfig, Service, WithUUID, CharacteristicGetHandler, CharacteristicSetHandler, Characteristic} from 'homebridge'; -import {HomebridgeKoboldPlatform} from '../homebridgeKoboldPlatform'; +import { + CharacteristicValue, + Logger, + PlatformAccessory, + PlatformAccessoryEvent, + PlatformConfig, + Service, + WithUUID, +} from 'homebridge'; +import { HomebridgeKoboldPlatform } from '../homebridgeKoboldPlatform'; import spotRepeat from '../characteristics/spotRepeat'; import spotWidth from '../characteristics/spotWidth'; import spotHeight from '../characteristics/spotHeight'; -import {Options} from '../models/options'; +import { Options } from '../models/options'; import { RobotService, CleanType } from '../models/services'; import { ALL_SERVICES, BACKGROUND_INTERVAL, LOCALE, PREFIX } from '../defaults'; @@ -15,694 +23,796 @@ import { CharacteristicHandler } from '../characteristics/characteristicHandler' * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class KoboldVacuumRobotAccessory -{ - // Homebridge - private log: Logger; - private readonly batteryService?: Service; - private readonly cleanService?: Service; - private readonly findMeService?: Service; - private readonly goToDockService?: Service; - private readonly dockStateService?: Service; - private readonly binFullService?: Service; - private readonly ecoService?: Service; - private readonly noGoLinesService?: Service; - private readonly extraCareService?: Service; - private readonly scheduleService?: Service; - private readonly spotCleanService?: Service; - private spotPlusFeatures: boolean; +export class KoboldVacuumRobotAccessory { + // Homebridge + private log: Logger; + private readonly batteryService?: Service; + private readonly cleanService?: Service; + private readonly findMeService?: Service; + private readonly goToDockService?: Service; + private readonly dockStateService?: Service; + private readonly binFullService?: Service; + private readonly ecoService?: Service; + private readonly noGoLinesService?: Service; + private readonly extraCareService?: Service; + private readonly scheduleService?: Service; + private readonly spotCleanService?: Service; + private spotPlusFeatures: boolean; - // Context - private robot: any; - private readonly options: Options; + // Context + private robot: any; + private readonly options: Options; - // Config - private readonly backgroundUpdateInterval: number; - private readonly locale: availableLocales; - private readonly prefix: boolean; - private readonly availableServices: Set; + // Config + private readonly backgroundUpdateInterval: number; + private readonly locale: availableLocales; + private readonly prefix: boolean; + private readonly availableServices: Set; - // Transient - private isSpotCleaning: boolean; - private timer: any; + // Transient + private isSpotCleaning: boolean; + private timer: any; - /** - * These are just used to create a working example - * You should implement your own code to track the state of your accessory - */ + /** + * These are just used to create a working example + * You should implement your own code to track the state of your accessory + */ - constructor( - private readonly platform: HomebridgeKoboldPlatform, - private readonly accessory: PlatformAccessory, - private readonly config: PlatformConfig) - { - this.log = platform.log; + constructor( + private readonly platform: HomebridgeKoboldPlatform, + private readonly accessory: PlatformAccessory, + private readonly config: PlatformConfig, + ) { + this.log = platform.log; - this.robot = accessory.context.robot; - this.options = accessory.context.options || new Options(); - this.spotPlusFeatures = false; + this.robot = accessory.context.robot; + this.options = accessory.context.options || new Options(); + this.spotPlusFeatures = false; - this.backgroundUpdateInterval = KoboldVacuumRobotAccessory.parseBackgroundUpdateInterval(this.config['backgroundUpdate']); - this.prefix = this.config['prefix'] || PREFIX; - this.locale = this.config['language'] || LOCALE; - this.availableServices = new Set(this.config['services']) || ALL_SERVICES; + this.backgroundUpdateInterval = + KoboldVacuumRobotAccessory.parseBackgroundUpdateInterval( + this.config['backgroundUpdate'], + ); + this.prefix = this.config['prefix'] || PREFIX; + this.locale = this.config['language'] || LOCALE; + this.availableServices = new Set(this.config['services']) || ALL_SERVICES; - this.isSpotCleaning = false; + this.isSpotCleaning = false; - // Information - this.accessory.getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, "Vorwerk Deutschland Stiftung & Co. KG") - .setCharacteristic(this.platform.Characteristic.Model, this.robot.meta.modelName) - .setCharacteristic(this.platform.Characteristic.SerialNumber, this.robot._serial) - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.robot.meta.firmware) - .setCharacteristic(this.platform.Characteristic.Name, this.robot.name); + // Information + this.accessory + .getService(this.platform.Service.AccessoryInformation)! + .setCharacteristic( + this.platform.Characteristic.Manufacturer, + 'Vorwerk Deutschland Stiftung & Co. KG', + ) + .setCharacteristic( + this.platform.Characteristic.Model, + this.robot.meta.modelName, + ) + .setCharacteristic( + this.platform.Characteristic.SerialNumber, + this.robot._serial, + ) + .setCharacteristic( + this.platform.Characteristic.FirmwareRevision, + this.robot.meta.firmware, + ) + .setCharacteristic(this.platform.Characteristic.Name, this.robot.name); - // Identify - this.accessory.on(PlatformAccessoryEvent.IDENTIFY, () => { - this.robot.findMe(); + // Identify + this.accessory.on(PlatformAccessoryEvent.IDENTIFY, () => { + this.robot.findMe(); - this.robot.getState((error, result) => { - this.log.info("[" + this.robot.name + "] Identified"); - if (error) - { - this.debug(DebugType.INFO, JSON.stringify("Error: " + error)); - } - this.debug(DebugType.INFO, "Status: " + JSON.stringify(result)); - this.debug(DebugType.INFO, - "Config: Background Update Interval: " + this.backgroundUpdateInterval + ", Prefix: " + this.prefix + ", Enabled services: " + JSON.stringify(this.availableServices)); - }); - }); + this.robot.getState((error, result) => { + this.log.info('[' + this.robot.name + '] Identified'); + if (error) { + this.debug(DebugType.INFO, JSON.stringify('Error: ' + error)); + } + this.debug(DebugType.INFO, 'Status: ' + JSON.stringify(result)); + this.debug( + DebugType.INFO, + 'Config: Background Update Interval: ' + + this.backgroundUpdateInterval + + ', Prefix: ' + + this.prefix + + ', Enabled services: ' + + JSON.stringify(this.availableServices), + ); + }); + }); - [ - this.getClean, - this.setClean, - this.getSpotClean, - this.setSpotClean, - this.getGoToDock, - this.setGoToDock, - this.getDocked, - this.getBinFull, - this.getFindMe, - this.setFindMe, - this.getSchedule, - this.setSchedule, - this.getEco, - this.setEco, - this.getNoGoLines, - this.setNoGoLines, - this.getExtraCare, - this.setExtraCare - ].forEach((f)=>f.bind(this)) + [ + this.getClean, + this.setClean, + this.getSpotClean, + this.setSpotClean, + this.getGoToDock, + this.setGoToDock, + this.getDocked, + this.getBinFull, + this.getFindMe, + this.setFindMe, + this.getSchedule, + this.setSchedule, + this.getEco, + this.setEco, + this.getNoGoLines, + this.setNoGoLines, + this.getExtraCare, + this.setExtraCare, + ].forEach((f) => { + f = f.bind(this); + }); - // Services - this.cleanService = this.registerService(RobotService.CLEAN, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getClean, - setCharacteristicHandler: this.setClean - }]); - this.spotCleanService = this.registerService(RobotService.CLEAN_SPOT, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getSpotClean, - setCharacteristicHandler: this.setSpotClean - }]); - this.goToDockService = this.registerService(RobotService.GO_TO_DOCK, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getGoToDock, - setCharacteristicHandler: this.setGoToDock - }]); - this.dockStateService = this.registerService(RobotService.DOCKED, this.platform.Service.OccupancySensor, [{ - characteristic: this.platform.Characteristic.OccupancyDetected.OccupancyDetected, - getCharacteristicHandler: this.getDocked, - }]); - this.binFullService = this.registerService(RobotService.BIN_FULL, this.platform.Service.OccupancySensor, [{ - characteristic: this.platform.Characteristic.OccupancyDetected.OccupancyDetected, - getCharacteristicHandler: this.getBinFull, - }]); - this.findMeService = this.registerService(RobotService.FIND_ME, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getFindMe, - setCharacteristicHandler: this.setFindMe - }]); - this.scheduleService = this.registerService(RobotService.SCHEDULE, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getSchedule, - setCharacteristicHandler: this.setSchedule - }]); - this.ecoService = this.registerService(RobotService.ECO, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getEco, - setCharacteristicHandler: this.setEco - }]); - this.noGoLinesService = this.registerService(RobotService.NOGO_LINES, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getNoGoLines, - setCharacteristicHandler: this.setNoGoLines - }]); - this.extraCareService = this.registerService(RobotService.EXTRA_CARE, this.platform.Service.Switch, [{ - characteristic: this.platform.Characteristic.On, - getCharacteristicHandler: this.getExtraCare, - setCharacteristicHandler: this.setExtraCare - }]); - this.batteryService = this.registerService(RobotService.BATTERY, this.platform.Service.Battery); - - // This should be the main switch if the accessory is grouped in homekit - if (this.cleanService) - { - this.cleanService.setPrimaryService(true); - } + // Services + this.cleanService = this.registerService( + RobotService.CLEAN, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getClean, + setCharacteristicHandler: this.setClean, + }, + ], + ); + this.spotCleanService = this.registerService( + RobotService.CLEAN_SPOT, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getSpotClean, + setCharacteristicHandler: this.setSpotClean, + }, + ], + ); + this.goToDockService = this.registerService( + RobotService.GO_TO_DOCK, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getGoToDock, + setCharacteristicHandler: this.setGoToDock, + }, + ], + ); + this.dockStateService = this.registerService( + RobotService.DOCKED, + this.platform.Service.OccupancySensor, + [ + { + characteristic: + this.platform.Characteristic.OccupancyDetected.OccupancyDetected, + getCharacteristicHandler: this.getDocked, + }, + ], + ); + this.binFullService = this.registerService( + RobotService.BIN_FULL, + this.platform.Service.OccupancySensor, + [ + { + characteristic: + this.platform.Characteristic.OccupancyDetected.OccupancyDetected, + getCharacteristicHandler: this.getBinFull, + }, + ], + ); + this.findMeService = this.registerService( + RobotService.FIND_ME, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getFindMe, + setCharacteristicHandler: this.setFindMe, + }, + ], + ); + this.scheduleService = this.registerService( + RobotService.SCHEDULE, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getSchedule, + setCharacteristicHandler: this.setSchedule, + }, + ], + ); + this.ecoService = this.registerService( + RobotService.ECO, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getEco, + setCharacteristicHandler: this.setEco, + }, + ], + ); + this.noGoLinesService = this.registerService( + RobotService.NOGO_LINES, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getNoGoLines, + setCharacteristicHandler: this.setNoGoLines, + }, + ], + ); + this.extraCareService = this.registerService( + RobotService.EXTRA_CARE, + this.platform.Service.Switch, + [ + { + characteristic: this.platform.Characteristic.On, + getCharacteristicHandler: this.getExtraCare, + setCharacteristicHandler: this.setExtraCare, + }, + ], + ); + this.batteryService = this.registerService( + RobotService.BATTERY, + this.platform.Service.Battery, + ); - // Start background update - this.updateRobotPeriodically().then(() => { - // Add special characteristics to set spot cleaning options - this.spotPlusFeatures = ((typeof this.robot.availableServices.spotCleaning !== 'undefined') && this.robot.availableServices.spotCleaning.includes("basic")); - this.addSpotCleanCharacteristics(); + // This should be the main switch if the accessory is grouped in homekit + if (this.cleanService) { + this.cleanService.setPrimaryService(true); + } - // Save/Load options - if (!accessory.context.options) - { - this.options.eco = this.robot.eco; - this.options.noGoLines = this.robot.noGoLines; - this.options.extraCare = this.robot.navigationMode == 2; - this.debug(DebugType.INFO, "Options initially set to eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + this.options.extraCare); - accessory.context.options = this.options; - } - else - { - this.debug(DebugType.INFO, "Options loaded from cache eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + this.options.extraCare); - } - }); - } + // Start background update + this.updateRobotPeriodically().then(() => { + // Add special characteristics to set spot cleaning options + this.spotPlusFeatures = + typeof this.robot.availableServices.spotCleaning !== 'undefined' && + this.robot.availableServices.spotCleaning.includes('basic'); + this.addSpotCleanCharacteristics(); - private addSpotCleanCharacteristics() - { - // Only add characteristics of service is available ond characteristics are not added yet - if (this.spotCleanService != null && !this.options.spotCharacteristics) - { - this.spotCleanService.addCharacteristic(spotRepeat(this.platform.Characteristic)) - .onGet(this.getSpotRepeat.bind(this)) - .onSet(this.setSpotRepeat.bind(this)); + // Save/Load options + if (!accessory.context.options) { + this.options.eco = this.robot.eco; + this.options.noGoLines = this.robot.noGoLines; + this.options.extraCare = this.robot.navigationMode === 2; + this.debug( + DebugType.INFO, + 'Options initially set to eco: ' + + this.options.eco + + ', noGoLines: ' + + this.options.noGoLines + + ', extraCare: ' + + this.options.extraCare, + ); + accessory.context.options = this.options; + } else { + this.debug( + DebugType.INFO, + 'Options loaded from cache eco: ' + + this.options.eco + + ', noGoLines: ' + + this.options.noGoLines + + ', extraCare: ' + + this.options.extraCare, + ); + } + }); + } - // Add these only if the robot supports them - if (this.spotPlusFeatures) - { - this.spotCleanService.addCharacteristic(spotWidth(this.platform.Characteristic)) - .onGet(this.getSpotWidth.bind(this)) - .onSet(this.setSpotWidth.bind(this)); - this.spotCleanService.addCharacteristic(spotHeight(this.platform.Characteristic)) - .onGet(this.getSpotHeight.bind(this)) - .onSet(this.setSpotHeight.bind(this)); - } - this.options.spotCharacteristics = true; - } - else if (this.spotCleanService == null) - { - this.options.spotCharacteristics = false; - } - } + private addSpotCleanCharacteristics() { + // Only add characteristics of service is available ond characteristics are not added yet + if (this.spotCleanService && !this.options.spotCharacteristics) { + this.spotCleanService + .addCharacteristic(spotRepeat(this.platform.Characteristic)) + .onGet(this.getSpotRepeat.bind(this)) + .onSet(this.setSpotRepeat.bind(this)); - private registerService( - serviceName: RobotService, - serviceType: WithUUID, - characteristicHandlers: CharacteristicHandler[] = [] - ) : Service | undefined - { - const displayName = (this.prefix ? (this.robot.name + " ") : "") + localize(serviceName, this.locale); - - // query existing service by type and subtype - const existingService = this.accessory.getServiceById(serviceType, serviceName) + // Add these only if the robot supports them + if (this.spotPlusFeatures) { + this.spotCleanService + .addCharacteristic(spotWidth(this.platform.Characteristic)) + .onGet(this.getSpotWidth.bind(this)) + .onSet(this.setSpotWidth.bind(this)); + this.spotCleanService + .addCharacteristic(spotHeight(this.platform.Characteristic)) + .onGet(this.getSpotHeight.bind(this)) + .onSet(this.setSpotHeight.bind(this)); + } + this.options.spotCharacteristics = true; + } else if (this.spotCleanService === null) { + this.options.spotCharacteristics = false; + } + } - if (this.availableServices.has(serviceName)) - { - var service : Service - if (existingService && existingService.displayName === displayName) { - service = existingService - } else { - if (existingService) {this.accessory.removeService(existingService);} // delete to reset display name in case of locale or prefix change - service = this.accessory.addService(serviceType, displayName, serviceName); - } - characteristicHandlers.forEach(ch => { - var char = service.getCharacteristic(ch.characteristic) - if (ch.getCharacteristicHandler) {char.onGet(ch.getCharacteristicHandler)} - if (ch.setCharacteristicHandler) {char.onSet(ch.setCharacteristicHandler)} - }); - return service - } - else - { - if (existingService) - { - this.accessory.removeService(existingService); - } - } - } + private registerService( + serviceName: RobotService, + serviceType: WithUUID, + characteristicHandlers: CharacteristicHandler[] = [], + ): Service | undefined { + const displayName = + (this.prefix ? this.robot.name + ' ' : '') + + localize(serviceName, this.locale); - private static parseBackgroundUpdateInterval(configValue: any) - { - // Parse as number - let backgroundUpdateInterval = parseInt(configValue) || BACKGROUND_INTERVAL; + // query existing service by type and subtype + const existingService = this.accessory.getServiceById( + serviceType, + serviceName, + ); - // must be integer and positive - backgroundUpdateInterval = ((backgroundUpdateInterval % 1) !== 0 || backgroundUpdateInterval < 0) ? BACKGROUND_INTERVAL : backgroundUpdateInterval; + if (this.availableServices.has(serviceName)) { + let service: Service; + if (existingService && existingService.displayName === displayName) { + service = existingService; + } else { + if (existingService) { + this.accessory.removeService(existingService); + } // delete to reset display name in case of locale or prefix change + service = this.accessory.addService( + serviceType, + displayName, + serviceName, + ); + } + characteristicHandlers.forEach((ch) => { + const char = service.getCharacteristic(ch.characteristic); + if (ch.getCharacteristicHandler) { + char.onGet(ch.getCharacteristicHandler); + } + if (ch.setCharacteristicHandler) { + char.onSet(ch.setCharacteristicHandler); + } + }); + return service; + } else { + if (existingService) { + this.accessory.removeService(existingService); + } + } + } - return backgroundUpdateInterval; - } + private static parseBackgroundUpdateInterval(configValue: any) { + // Parse as number + let backgroundUpdateInterval = parseInt(configValue) || BACKGROUND_INTERVAL; - async getClean(): Promise - { - try - { - await this.updateRobot(); - return this.robot.canPause && !this.isSpotCleaning; - } - catch (error) - { - this.log.error("Cannot get cleaning status: " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + // must be integer and positive + backgroundUpdateInterval = + backgroundUpdateInterval % 1 !== 0 || backgroundUpdateInterval < 0 + ? BACKGROUND_INTERVAL + : backgroundUpdateInterval; - async setClean(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set CLEAN HOUSE: " + on); - try - { - await this.updateRobot(); + return backgroundUpdateInterval; + } - // Start - if (on) - { - // Resume cleaning - if (this.robot.canResume) - { - this.debug(DebugType.ACTION, "Resume cleaning"); - await this.robot.resumeCleaning(); - } - // Start cleaning - else if (this.robot.canStart) - { - await this.clean(CleanType.ALL) - } - // Cannot start - else - { - this.debug(DebugType.INFO, "Cannot start, maybe already cleaning (expected)"); - } - } - // Stop - else - { - if (this.robot.canPause) - { - this.debug(DebugType.ACTION, "Pause cleaning"); - await this.robot.pauseCleaning(); - } - else - { - this.debug(DebugType.INFO, "Already paused"); - } - } - } - catch (error) - { - this.log.error("Error setting cleaning to: " + on + ". " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + async getClean(): Promise { + try { + await this.updateRobot(); + return this.robot.canPause && !this.isSpotCleaning; + } catch (error) { + this.log.error('Cannot get cleaning status: ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - async getSpotClean(): Promise - { - try - { - await this.updateRobot(); - return this.robot.canPause && this.isSpotCleaning; - } - catch (error) - { - this.log.error("Cannot get spot cleaning status: " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + async setClean(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set CLEAN HOUSE: ' + on); + try { + await this.updateRobot(); - async setSpotClean(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set SPOT CLEAN: " + on); - try - { - if (on) - { - await this.clean(CleanType.SPOT) - } - else - { - // TODO stop/pause - } - } - catch (error) - { - this.log.error("Error setting spot cleaning to: " + on + ". " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + // Start + if (on) { + // Resume cleaning + if (this.robot.canResume) { + this.debug(DebugType.ACTION, 'Resume cleaning'); + await this.robot.resumeCleaning(); + } + // Start cleaning + else if (this.robot.canStart) { + await this.clean(CleanType.ALL); + } + // Cannot start + else { + this.debug( + DebugType.INFO, + 'Cannot start, maybe already cleaning (expected)', + ); + } + } + // Stop + else { + if (this.robot.canPause) { + this.debug(DebugType.ACTION, 'Pause cleaning'); + await this.robot.pauseCleaning(); + } else { + this.debug(DebugType.INFO, 'Already paused'); + } + } + } catch (error) { + this.log.error('Error setting cleaning to: ' + on + '. ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - getGoToDock() - { - return false; - } + async getSpotClean(): Promise { + try { + await this.updateRobot(); + return this.robot.canPause && this.isSpotCleaning; + } catch (error) { + this.log.error('Cannot get spot cleaning status: ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - async setGoToDock(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set GO TO DOCK: " + on); - if (on) - { - await this.updateRobot(); + async setSpotClean(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set SPOT CLEAN: ' + on); + try { + if (on) { + await this.clean(CleanType.SPOT); + } else { + // TODO stop/pause + } + } catch (error) { + this.log.error('Error setting spot cleaning to: ' + on + '. ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - setTimeout(() => { - if (this.goToDockService) - { - this.goToDockService.updateCharacteristic(this.platform.Characteristic.On, false); - } - }, 10000); + getGoToDock() { + return false; + } - try - { - if (this.robot.canPause) - { - this.debug(DebugType.ACTION, "Pause cleaning to go to dock"); - await this.robot.pauseCleaning(); - setTimeout(async () => { - await this.robot.sendToBase(); - }, 1000); - } - else if (this.robot.canGoToBase) - { - this.debug(DebugType.ACTION, "Going to dock"); - await this.robot.sendToBase(); - } - else - { - this.log.warn("[" + this.robot.name + "] Can't go to dock at the moment"); - } - } - catch (error) - { - this.log.error("Error setting go to dock to: " + on + ". " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } - } + async setGoToDock(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set GO TO DOCK: ' + on); + if (on) { + await this.updateRobot(); - async getDocked(): Promise - { - try - { - await this.updateRobot(); - return this.robot.isDocked; - } - catch (error) - { - this.log.error("Cannot get docked status: " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + setTimeout(() => { + if (this.goToDockService) { + this.goToDockService.updateCharacteristic( + this.platform.Characteristic.On, + false, + ); + } + }, 10000); - async getBinFull(): Promise - { - try - { - await this.updateRobot(); - return this.robot.isBinFull; - } - catch (error) - { - this.log.error("Cannot get bin full status: " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + try { + if (this.robot.canPause) { + this.debug(DebugType.ACTION, 'Pause cleaning to go to dock'); + await this.robot.pauseCleaning(); + setTimeout(async () => { + await this.robot.sendToBase(); + }, 1000); + } else if (this.robot.canGoToBase) { + this.debug(DebugType.ACTION, 'Going to dock'); + await this.robot.sendToBase(); + } else { + this.log.warn( + '[' + this.robot.name + '] Can\'t go to dock at the moment', + ); + } + } catch (error) { + this.log.error('Error setting go to dock to: ' + on + '. ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } + } - async getSchedule(): Promise - { - try - { - await this.updateRobot(); - return this.robot.isScheduleEnabled; - } - catch (error) - { - this.log.error("Cannot get schedule status: " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + async getDocked(): Promise { + try { + await this.updateRobot(); + return this.robot.isDocked; + } catch (error) { + this.log.error('Cannot get docked status: ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - async setSchedule(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set SCHEDULE: " + on); - try - { - if (on) - { - await this.robot.enableSchedule(); - } - else - { - await this.robot.disableSchedule(); - } - } - catch (error) - { - this.log.error("Error setting schedule to: " + on + ". " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + async getBinFull(): Promise { + try { + await this.updateRobot(); + return this.robot.isBinFull; + } catch (error) { + this.log.error('Cannot get bin full status: ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - getEco() - { - return this.options.eco; - } + async getSchedule(): Promise { + try { + await this.updateRobot(); + return this.robot.isScheduleEnabled; + } catch (error) { + this.log.error('Cannot get schedule status: ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - setEco(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set ECO: " + on); - this.options.eco = on; - } + async setSchedule(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set SCHEDULE: ' + on); + try { + if (on) { + await this.robot.enableSchedule(); + } else { + await this.robot.disableSchedule(); + } + } catch (error) { + this.log.error('Error setting schedule to: ' + on + '. ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - getExtraCare() - { - return this.options.extraCare; - } + getEco() { + return this.options.eco; + } - setExtraCare(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set EXTRA CARE: " + on); - this.options.extraCare = on; - } + setEco(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set ECO: ' + on); + this.options.eco = on; + } - getNoGoLines() - { - return this.options.noGoLines; - } + getExtraCare() { + return this.options.extraCare; + } - setNoGoLines(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set NOGO LINES: " + on); - this.options.noGoLines = on; - } + setExtraCare(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set EXTRA CARE: ' + on); + this.options.extraCare = on; + } - getSpotRepeat() - { - return this.options.spotRepeat; - } + getNoGoLines() { + return this.options.noGoLines; + } - setSpotRepeat(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set SPOT REPEAT: " + on); - this.options.spotRepeat = on; - } + setNoGoLines(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set NOGO LINES: ' + on); + this.options.noGoLines = on; + } - getSpotWidth() - { - return this.options.spotWidth; - } + getSpotRepeat() { + return this.options.spotRepeat; + } - setSpotWidth(length: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set SPOT WIDTH: " + length + " cm"); - this.options.spotWidth = length; - } + setSpotRepeat(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set SPOT REPEAT: ' + on); + this.options.spotRepeat = on; + } - getSpotHeight() - { - return this.options.spotHeight; - } + getSpotWidth() { + return this.options.spotWidth; + } - setSpotHeight(length: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set SPOT HEIGHT: " + length + " cm"); - this.options.spotHeight = length; - } + setSpotWidth(length: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set SPOT WIDTH: ' + length + ' cm'); + this.options.spotWidth = length; + } - getFindMe() - { - return false; - } + getSpotHeight() { + return this.options.spotHeight; + } - async setFindMe(on: CharacteristicValue) - { - this.debug(DebugType.STATUS, "Set FIND ME: " + on); - if (on) - { - this.debug(DebugType.ACTION, "Find me"); - setTimeout(() => { - if (this.findMeService) - { - this.findMeService.updateCharacteristic(this.platform.Characteristic.On, false); - } - }, 1000); + setSpotHeight(length: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set SPOT HEIGHT: ' + length + ' cm'); + this.options.spotHeight = length; + } - try - { - await this.robot.findMe(); - } - catch (error) - { - this.log.error(this.robot.name + " ## Cannot start find me. " + error); - throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } - } + getFindMe() { + return false; + } - async clean(cleanType: CleanType) - { - // Enable shorter background update while cleaning - setTimeout(() => { - this.updateRobotPeriodically(); - }, 2 * 60 * 1000); + async setFindMe(on: CharacteristicValue) { + this.debug(DebugType.STATUS, 'Set FIND ME: ' + on); + if (on) { + this.debug(DebugType.ACTION, 'Find me'); + setTimeout(() => { + if (this.findMeService) { + this.findMeService.updateCharacteristic( + this.platform.Characteristic.On, + false, + ); + } + }, 1000); - this.log.info( - "[" + this.robot.name + "] > Start cleaning with options type: " + CleanType[cleanType] + ", eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " - + this.options.extraCare); + try { + await this.robot.findMe(); + } catch (error) { + this.log.error(this.robot.name + ' ## Cannot start find me. ' + error); + throw new this.platform.api.hap.HapStatusError( + this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } + } - try - { - switch (cleanType) - { - case CleanType.ALL: - await this.robot.startCleaning(this.options.eco, this.options.extraCare ? 2 : 1, this.options.noGoLines); - break; - case CleanType.SPOT: - await this.robot.startSpotCleaning(this.options.eco, this.options.spotWidth, this.options.spotHeight, this.options.spotRepeat, this.options.extraCare ? 2 : 1); - break; - } - } - catch (error) - { - this.log.error("Cannot start cleaning. " + error); - } - } + async clean(cleanType: CleanType) { + // Enable shorter background update while cleaning + setTimeout(() => { + this.updateRobotPeriodically(); + }, 2 * 60 * 1000); - async updateRobot() - { - // Data is outdated - if (typeof (this.robot.lastUpdate) === 'undefined' || new Date().getTime() - this.robot.lastUpdate > 2000) - { - this.robot.lastUpdate = new Date().getTime(); - try - { - this.robot.getState((error, result) => { - this.isSpotCleaning = result != null && result.action == 2; + this.log.info( + '[' + + this.robot.name + + '] > Start cleaning with options type: ' + + CleanType[cleanType] + + ', eco: ' + + this.options.eco + + ', noGoLines: ' + + this.options.noGoLines + + ', extraCare: ' + + this.options.extraCare, + ); - // Battery - this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.robot.charge); - this.batteryService?.updateCharacteristic(this.platform.Characteristic.ChargingState, this.robot.isCharging); - }); - } - catch (error) - { - this.log.error("Cannot update robot " + this.robot.name + ". Check if robot is online. " + error); - return false; - } - } - } + try { + switch (cleanType) { + case CleanType.ALL: + await this.robot.startCleaning( + this.options.eco, + this.options.extraCare ? 2 : 1, + this.options.noGoLines, + ); + break; + case CleanType.SPOT: + await this.robot.startSpotCleaning( + this.options.eco, + this.options.spotWidth, + this.options.spotHeight, + this.options.spotRepeat, + this.options.extraCare ? 2 : 1, + ); + break; + } + } catch (error) { + this.log.error('Cannot start cleaning. ' + error); + } + } - async updateRobotPeriodically() - { - this.debug(DebugType.INFO, "Performing background update") + async updateRobot() { + // Data is outdated + if ( + typeof this.robot.lastUpdate === 'undefined' || + new Date().getTime() - this.robot.lastUpdate > 2000 + ) { + this.robot.lastUpdate = new Date().getTime(); + try { + this.robot.getState((error, result) => { + this.isSpotCleaning = result !== null && result.action === 2; - await this.updateRobot() - await this.updateCharacteristics(); + // Battery + this.batteryService?.updateCharacteristic( + this.platform.Characteristic.BatteryLevel, + this.robot.charge, + ); + this.batteryService?.updateCharacteristic( + this.platform.Characteristic.ChargingState, + this.robot.isCharging, + ); + }); + } catch (error) { + this.log.error( + 'Cannot update robot ' + + this.robot.name + + '. Check if robot is online. ' + + error, + ); + return false; + } + } + } - // Clear any other overlapping timers for this robot - clearTimeout(this.timer); + async updateRobotPeriodically() { + this.debug(DebugType.INFO, 'Performing background update'); - // Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available - // this.robot.mainAccessory.updated(); - // this.robot.roomAccessories.forEach(accessory => { - // accessory.updated(); - // }); + await this.updateRobot(); + await this.updateCharacteristics(); - // Periodic refresh interval set in config - let interval; - if (this.robot.canPause) - { - interval = 1; - } - else - { - interval = this.backgroundUpdateInterval; - } + // Clear any other overlapping timers for this robot + clearTimeout(this.timer); - this.debug(DebugType.INFO, "Background update done. Next update in " + interval + " minute" + (interval == 1 ? "" : "s") + ((this.robot.canPause) ? ", robot is currently cleaning." : ".")); - this.timer = setTimeout(this.updateRobotPeriodically.bind(this), interval * 60 * 1000); - } + // Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available + // this.robot.mainAccessory.updated(); + // this.robot.roomAccessories.forEach(accessory => { + // accessory.updated(); + // }); - async updateCharacteristics() - { - if (this.cleanService) - { - this.cleanService.updateCharacteristic(this.platform.Characteristic.On, await this.getClean()); - } - if (this.spotCleanService) - { - this.spotCleanService.updateCharacteristic(this.platform.Characteristic.On, await this.getSpotClean()); - } - if (this.goToDockService) - { - this.goToDockService.updateCharacteristic(this.platform.Characteristic.On, await this.getGoToDock()); - } - if (this.dockStateService) - { - this.dockStateService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getDocked()); - } - if (this.binFullService) - { - this.binFullService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getBinFull()); - } - if (this.scheduleService) - { - this.scheduleService.updateCharacteristic(this.platform.Characteristic.On, await this.getSchedule()); - } - } + // Periodic refresh interval set in config + let interval; + if (this.robot.canPause) { + interval = 1; + } else { + interval = this.backgroundUpdateInterval; + } - private debug(debugType: DebugType, message: String) - { - switch (debugType) - { - case DebugType.ACTION: - this.log.debug("[" + this.robot.name + "] > " + message); - break; - case DebugType.STATUS: - this.log.debug("[" + this.robot.name + "] " + message); - break; - case DebugType.INFO: - this.log.debug("[" + this.robot.name + "] " + message); - break; - } - } + this.debug( + DebugType.INFO, + 'Background update done. Next update in ' + + interval + + ' minute' + + (interval === 1 ? '' : 's') + + (this.robot.canPause ? ', robot is currently cleaning.' : '.'), + ); + this.timer = setTimeout( + this.updateRobotPeriodically.bind(this), + interval * 60 * 1000, + ); + } + + async updateCharacteristics() { + if (this.cleanService) { + this.cleanService.updateCharacteristic( + this.platform.Characteristic.On, + await this.getClean(), + ); + } + if (this.spotCleanService) { + this.spotCleanService.updateCharacteristic( + this.platform.Characteristic.On, + await this.getSpotClean(), + ); + } + if (this.goToDockService) { + this.goToDockService.updateCharacteristic( + this.platform.Characteristic.On, + await this.getGoToDock(), + ); + } + if (this.dockStateService) { + this.dockStateService.updateCharacteristic( + this.platform.Characteristic.OccupancyDetected, + await this.getDocked(), + ); + } + if (this.binFullService) { + this.binFullService.updateCharacteristic( + this.platform.Characteristic.OccupancyDetected, + await this.getBinFull(), + ); + } + if (this.scheduleService) { + this.scheduleService.updateCharacteristic( + this.platform.Characteristic.On, + await this.getSchedule(), + ); + } + } + + private debug(debugType: DebugType, message: string) { + switch (debugType) { + case DebugType.ACTION: + this.log.debug('[' + this.robot.name + '] > ' + message); + break; + case DebugType.STATUS: + this.log.debug('[' + this.robot.name + '] ' + message); + break; + case DebugType.INFO: + this.log.debug('[' + this.robot.name + '] ' + message); + break; + } + } } - -enum DebugType -{ - ACTION, - STATUS, - INFO -} \ No newline at end of file +enum DebugType { + ACTION, + STATUS, + INFO, +} diff --git a/src/characteristics/characteristicHandler.ts b/src/characteristics/characteristicHandler.ts index fbbc7f3..548a279 100644 --- a/src/characteristics/characteristicHandler.ts +++ b/src/characteristics/characteristicHandler.ts @@ -1,7 +1,7 @@ -import { Characteristic, CharacteristicGetHandler, CharacteristicSetHandler, WithUUID } from "homebridge"; +import { Characteristic, CharacteristicGetHandler, CharacteristicSetHandler, WithUUID } from 'homebridge'; export declare interface CharacteristicHandler{ - characteristic: WithUUID Characteristic> - getCharacteristicHandler?: CharacteristicGetHandler, - setCharacteristicHandler?: CharacteristicSetHandler + characteristic: WithUUID Characteristic>; + getCharacteristicHandler?: CharacteristicGetHandler; + setCharacteristicHandler?: CharacteristicSetHandler; } \ No newline at end of file