From d9b5c7f57202bd7dc399ce489e70d94f01ee41e2 Mon Sep 17 00:00:00 2001 From: Arne Blumentritt Date: Sun, 9 May 2021 20:34:23 +0200 Subject: [PATCH] Added spot cleaning --- CHANGELOG.md | 5 +- src/accessories/neatoVacuumRobot.ts | 187 ++++++++++++++++------------ src/homebridgeNeatoPlatform.ts | 2 +- src/models/options.ts | 13 +- 4 files changed, 116 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ded30..632bfc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,8 +132,7 @@ * Changed config parameter **hidden**. Renamed to **services**, now takes list of services that should be _visible_. Default are all available services. * Fixed robots no longer disappear or change the room after connection issues with the Neato API * Fixed plugin no longer crashes if non smart robot is assigned in neato account -* Fixed options for eco, nogo lines, extra care are now saved in homebridge and will no longer be overridden by Neato API +* Fixed options for eco, nogo lines, extra care, spot repeat, spot size are now saved in homebridge and will no longer be overridden by Neato API ## TODO until 1.0.0 release -* Room cleaning -* Spot size \ No newline at end of file +* Room cleaning \ No newline at end of file diff --git a/src/accessories/neatoVacuumRobot.ts b/src/accessories/neatoVacuumRobot.ts index 9b79d6b..9428053 100644 --- a/src/accessories/neatoVacuumRobot.ts +++ b/src/accessories/neatoVacuumRobot.ts @@ -1,6 +1,10 @@ import {CharacteristicValue, Logger, PlatformAccessory, PlatformAccessoryEvent, PlatformConfig, Service} from 'homebridge'; import {HomebridgeNeatoPlatform} from '../homebridgeNeatoPlatform'; +import spotRepeat from '../characteristics/spotRepeat'; +import spotWidth from '../characteristics/spotWidth'; +import spotHeight from '../characteristics/spotHeight'; import {Options} from '../models/options'; +import {CharacteristicGetHandler, CharacteristicSetHandler} from "hap-nodejs/dist/lib/Characteristic"; /** * Platform Accessory @@ -22,6 +26,7 @@ export class NeatoVacuumRobotAccessory private readonly extraCareService: Service | null; private readonly scheduleService: Service | null; private readonly spotCleanService: Service | null; + private spotPlusFeatures: boolean; // Context private robot: any; @@ -50,6 +55,7 @@ export class NeatoVacuumRobotAccessory this.robot = accessory.context.robot; this.options = accessory.context.options || new Options(); + this.spotPlusFeatures = false; this.backgroundUpdateInterval = NeatoVacuumRobotAccessory.parseBackgroundUpdateInterval(this.config['backgroundUpdate']); this.prefix = this.config['prefix'] || PREFIX; @@ -82,75 +88,22 @@ export class NeatoVacuumRobotAccessory }); // Services - this.cleanService = this.getSwitchService(RobotService.CLEAN_HOUSE); - this.spotCleanService = this.getSwitchService(RobotService.CLEAN_SPOT); - this.goToDockService = this.getSwitchService(RobotService.GO_TO_DOCK); - this.dockStateService = this.getOccupancyService(RobotService.DOCKED) - this.binFullService = this.getOccupancyService(RobotService.BIN_FULL) - this.findMeService = this.getSwitchService(RobotService.FIND_ME); - this.scheduleService = this.getSwitchService(RobotService.SCHEDULE); - this.ecoService = this.getSwitchService(RobotService.ECO); - this.noGoLinesService = this.getSwitchService(RobotService.NOGO_LINES); - this.extraCareService = this.getSwitchService(RobotService.EXTRA_CARE); + this.cleanService = this.getSwitchService(RobotService.CLEAN_HOUSE, this.getCleanHouse.bind(this), this.setCleanHouse.bind(this)); + this.spotCleanService = this.getSwitchService(RobotService.CLEAN_SPOT, this.getSpotClean.bind(this), this.setSpotClean.bind(this)); + this.goToDockService = this.getSwitchService(RobotService.GO_TO_DOCK, this.getGoToDock.bind(this), this.setGoToDock.bind(this)); + this.dockStateService = this.getOccupancyService(RobotService.DOCKED, this.getDocked.bind(this)) + this.binFullService = this.getOccupancyService(RobotService.BIN_FULL, this.getBinFull.bind(this)) + this.findMeService = this.getSwitchService(RobotService.FIND_ME, this.getFindMe.bind(this), this.setFindMe.bind(this)); + this.scheduleService = this.getSwitchService(RobotService.SCHEDULE, this.getSchedule.bind(this), this.setSchedule.bind(this)); + this.ecoService = this.getSwitchService(RobotService.ECO, this.getEco.bind(this), this.setEco.bind(this)); + this.noGoLinesService = this.getSwitchService(RobotService.NOGO_LINES, this.getNoGoLines.bind(this), this.setNoGoLines.bind(this)); + this.extraCareService = this.getSwitchService(RobotService.EXTRA_CARE, this.getExtraCare.bind(this), this.setExtraCare.bind(this)); this.batteryService = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery) + // This should be the main switch if the accessory is grouped in homekit if (this.cleanService) { - this.cleanService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setCleanHouse.bind(this)) - .onGet(this.getCleanHouse.bind(this)); - } - if (this.spotCleanService) - { - this.spotCleanService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setSpotClean.bind(this)) - .onGet(this.getSpotClean.bind(this)); - } - if (this.goToDockService) - { - this.goToDockService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setGoToDock.bind(this)) - .onGet(this.getGoToDock.bind(this)); - } - if (this.dockStateService) - { - this.dockStateService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) - .onGet(this.getDocked.bind(this)); - } - if (this.binFullService) - { - this.binFullService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) - .onGet(this.getBinFull.bind(this)); - } - if (this.findMeService) - { - this.findMeService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setFindMe.bind(this)) - .onGet(this.getFindMe.bind(this)); - } - if (this.scheduleService) - { - this.scheduleService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setSchedule.bind(this)) - .onGet(this.getSchedule.bind(this)); - } - if (this.ecoService) - { - this.ecoService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setEco.bind(this)) - .onGet(this.getEco.bind(this)); - } - if (this.noGoLinesService) - { - this.noGoLinesService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setNoGoLines.bind(this)) - .onGet(this.getNoGoLines.bind(this)); - } - if (this.extraCareService) - { - this.extraCareService.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setExtraCare.bind(this)) - .onGet(this.getExtraCare.bind(this)); + this.cleanService.setPrimaryService(true); } // Start background update @@ -162,6 +115,10 @@ export class NeatoVacuumRobotAccessory 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; + + // Add special characteristics to set spot cleaning options + this.spotPlusFeatures = ((typeof this.robot.availableServices.spotCleaning !== 'undefined') && this.robot.availableServices.spotCleaning.includes("basic")); + this.addSpotCleanCharacteristics(); } else { @@ -170,31 +127,43 @@ export class NeatoVacuumRobotAccessory }); } - private getSwitchService(serviceName: string) + private addSpotCleanCharacteristics() { - let displayName = this.prefix ? this.robot.name + serviceName : serviceName; + // 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)); - if (this.availableServices.includes(serviceName)) - { - return this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.Switch, displayName, serviceName); - } - else - { - if (this.accessory.getService(displayName)) + // Add these only if the robot supports them + if (this.spotPlusFeatures) { - this.accessory.removeService(this.accessory.getService(displayName)); + 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)); } - return null; + this.options.spotCharacteristics = true; + } + else if (this.spotCleanService == null) + { + this.options.spotCharacteristics = false; } } - private getOccupancyService(serviceName: string) + private getSwitchService(serviceName: string, getHandler: CharacteristicGetHandler, setHandler: CharacteristicSetHandler) { let displayName = this.prefix ? this.robot.name + serviceName : serviceName; - if (this.availableServices.includes(serviceName)) { - return this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.OccupancySensor, displayName, serviceName); + let service = this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.Switch, displayName, serviceName); + service.getCharacteristic(this.platform.Characteristic.On) + .onGet(getHandler) + .onSet(setHandler); + return service; } else { @@ -202,7 +171,28 @@ export class NeatoVacuumRobotAccessory { this.accessory.removeService(this.accessory.getService(displayName)); } - return null; + return null + } + } + + private getOccupancyService(serviceName: string, getHandler: CharacteristicGetHandler) + { + let displayName = this.prefix ? this.robot.name + serviceName : serviceName; + + if (this.availableServices.includes(serviceName)) + { + let service = this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.OccupancySensor, displayName, serviceName); + service.getCharacteristic(this.platform.Characteristic.OccupancyDetected.OccupancyDetected) + .onGet(getHandler); + return service; + } + else + { + if (this.accessory.getService(displayName)) + { + this.accessory.removeService(this.accessory.getService(displayName)); + } + return null } } @@ -457,6 +447,39 @@ export class NeatoVacuumRobotAccessory this.options.noGoLines = on; } + getSpotRepeat() + { + return this.options.spotRepeat; + } + + setSpotRepeat(on: CharacteristicValue) + { + this.debug(DebugType.STATUS, "Set SPOT REPEAT: " + on); + this.options.spotRepeat = on; + } + + getSpotWidth() + { + return this.options.spotWidth; + } + + setSpotWidth(length: CharacteristicValue) + { + this.debug(DebugType.STATUS, "Set SPOT WIDTH: " + length + " cm"); + this.options.spotWidth = length; + } + + getSpotHeight() + { + return this.options.spotHeight; + } + + setSpotHeight(length: CharacteristicValue) + { + this.debug(DebugType.STATUS, "Set SPOT HEIGHT: " + length + " cm"); + this.options.spotHeight = length; + } + getFindMe() { return false; @@ -492,7 +515,7 @@ export class NeatoVacuumRobotAccessory // Enable shorter background update while cleaning setTimeout(() => { this.updateRobotPeriodically(); - }, 60 * 1000); + }, 2 * 60 * 1000); this.log.info( "[" + this.robot.name + "] > Start cleaning with options type: " + CleanType[cleanType] + ", eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " @@ -506,7 +529,7 @@ export class NeatoVacuumRobotAccessory 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.spot.width, this.options.spot.height, this.options.spot.repeat, this.options.extraCare ? 2 : 1); + await this.robot.startSpotCleaning(this.options.eco, this.options.spotWidth, this.options.spotHeight, this.options.spotRepeat, this.options.extraCare ? 2 : 1); break; } } diff --git a/src/homebridgeNeatoPlatform.ts b/src/homebridgeNeatoPlatform.ts index d4b9c7e..845e4f7 100644 --- a/src/homebridgeNeatoPlatform.ts +++ b/src/homebridgeNeatoPlatform.ts @@ -40,7 +40,6 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin discoverRobots() { const client = new NeatoApi.Client(); - this.log.debug("blub"); try { @@ -126,6 +125,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin try { robot.meta = state.meta; + robot.availableServices = state.availableServices; // Update existing robot accessor if (cachedRobot) diff --git a/src/models/options.ts b/src/models/options.ts index df3d92b..91b552b 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,18 +1,21 @@ -import {HomebridgeNeatoPlatform} from "../homebridgeNeatoPlatform"; -import {PlatformAccessory, PlatformConfig} from "homebridge"; - export class Options { public eco: boolean; public extraCare: boolean; public noGoLines: boolean; - public spot: any; + public spotCharacteristics: boolean; + public spotRepeat: boolean; + public spotWidth: number; + public spotHeight: number; constructor() { this.eco = false; this.extraCare = false; this.noGoLines = false; - this.spot = {}; + this.spotCharacteristics = false; + this.spotRepeat = false; + this.spotWidth = 200; + this.spotHeight = 200; } } \ No newline at end of file