diff --git a/src/characteristics/spotHeight.ts b/src/characteristics/spotHeight.ts index 1546aed..a0fedcb 100644 --- a/src/characteristics/spotHeight.ts +++ b/src/characteristics/spotHeight.ts @@ -1,19 +1,21 @@ import type { Characteristic, WithUUID } from 'homebridge'; import { Formats, Perms } from 'homebridge'; -export default function spotHeight(CustomCharacteristic: typeof Characteristic): WithUUID Characteristic> { - return class SpotHeight extends CustomCharacteristic { - static readonly UUID = 'CA282DB2-62BF-4325-A1BE-F8BB5478781A'; +export default function spotHeight( + CustomCharacteristic: typeof Characteristic, +): WithUUID Characteristic> { + return class SpotHeight extends CustomCharacteristic { + static readonly UUID = 'CA282DB2-62BF-4325-A1BE-F8BB5478781A'; - constructor() { - super('Spot ↕', SpotHeight.UUID, { - format: Formats.INT, - unit: 'cm', - maxValue: 400, - minValue: 100, - minStep: 50, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] - }); - } - }; -} \ No newline at end of file + constructor() { + super('Spot ↕', SpotHeight.UUID, { + format: Formats.INT, + unit: 'cm', + maxValue: 400, + minValue: 100, + minStep: 50, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + } + }; +} diff --git a/src/characteristics/spotRepeat.ts b/src/characteristics/spotRepeat.ts index f4d8f09..57f8fb7 100644 --- a/src/characteristics/spotRepeat.ts +++ b/src/characteristics/spotRepeat.ts @@ -1,15 +1,17 @@ import type { Characteristic, WithUUID } from 'homebridge'; import { Formats, Perms } from 'homebridge'; -export default function spotRepeat(CustomCharacteristic: typeof Characteristic): WithUUID Characteristic> { - return class SpotRepeat extends CustomCharacteristic { - static readonly UUID = '1E79C603-63B8-4E6A-9CE1-D31D67981831'; +export default function spotRepeat( + CustomCharacteristic: typeof Characteristic, +): WithUUID Characteristic> { + return class SpotRepeat extends CustomCharacteristic { + static readonly UUID = '1E79C603-63B8-4E6A-9CE1-D31D67981831'; - constructor() { - super('Spot 2x', SpotRepeat.UUID, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] - }); - } - }; -} \ No newline at end of file + constructor() { + super('Spot 2x', SpotRepeat.UUID, { + format: Formats.BOOL, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + } + }; +} diff --git a/src/characteristics/spotWidth.ts b/src/characteristics/spotWidth.ts index 8632445..f6449f0 100644 --- a/src/characteristics/spotWidth.ts +++ b/src/characteristics/spotWidth.ts @@ -1,19 +1,21 @@ import type { Characteristic, WithUUID } from 'homebridge'; import { Formats, Perms } from 'homebridge'; -export default function spotWidth(CustomCharacteristic: typeof Characteristic): WithUUID Characteristic> { - return class SpotWidth extends CustomCharacteristic { - static readonly UUID = 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E'; +export default function spotWidth( + CustomCharacteristic: typeof Characteristic, +): WithUUID Characteristic> { + return class SpotWidth extends CustomCharacteristic { + static readonly UUID = 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E'; - constructor() { - super('Spot ↔', SpotWidth.UUID, { - format: Formats.INT, - unit: 'cm', - maxValue: 400, - minValue: 100, - minStep: 50, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] - }); - } - }; -} \ No newline at end of file + constructor() { + super('Spot ↔', SpotWidth.UUID, { + format: Formats.INT, + unit: 'cm', + maxValue: 400, + minValue: 100, + minStep: 50, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + } + }; +} diff --git a/src/defaults.ts b/src/defaults.ts index ae232fa..795f060 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -1,6 +1,6 @@ -import { RobotService } from "./models/services"; +import { RobotService } from './models/services'; export const BACKGROUND_INTERVAL = 30; export const PREFIX = false; export const ALL_SERVICES = new Set(Object.values(RobotService)); -export const LOCALE = "en" \ No newline at end of file +export const LOCALE = 'en'; diff --git a/src/homebridgeKoboldPlatform.ts b/src/homebridgeKoboldPlatform.ts index bb2db04..ec5ac35 100644 --- a/src/homebridgeKoboldPlatform.ts +++ b/src/homebridgeKoboldPlatform.ts @@ -1,219 +1,273 @@ -import {API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service} from "homebridge"; -import KoboldApi from "node-kobold-control"; -import {PLATFORM_NAME, PLUGIN_NAME} from "./settings"; -import {KoboldVacuumRobotAccessory} from "./accessories/koboldVacuumRobot"; +import { + API, + Characteristic, + DynamicPlatformPlugin, + Logger, + PlatformAccessory, + PlatformConfig, + Service, +} from 'homebridge'; +import KoboldApi from 'node-kobold-control'; +import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; +import { KoboldVacuumRobotAccessory } from './accessories/koboldVacuumRobot'; /** * HomebridgePlatform * This class is the main constructor for your plugin, this is where you should * parse the user config and discover/register accessories with Homebridge. */ -export class HomebridgeKoboldPlatform implements DynamicPlatformPlugin -{ - public readonly Service: typeof Service = this.api.hap.Service; - public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; +export class HomebridgeKoboldPlatform implements DynamicPlatformPlugin { + public readonly Service: typeof Service = this.api.hap.Service; + public readonly Characteristic: typeof Characteristic = + this.api.hap.Characteristic; - // this is used to track restored cached accessories - public readonly cachedRobotAccessories: PlatformAccessory[] = []; + // this is used to track restored cached accessories + public readonly cachedRobotAccessories: PlatformAccessory[] = []; - constructor( - public readonly log: Logger, - public readonly config: PlatformConfig, - public readonly api: API) - { - this.api.on("didFinishLaunching", () => { - this.discoverRobots(); - }); - } + constructor( + public readonly log: Logger, + public readonly config: PlatformConfig, + public readonly api: API, + ) { + this.api.on('didFinishLaunching', () => { + this.discoverRobots(); + }); + } - /** - * This function is invoked when homebridge restores cached accessories from disk at startup. - * It should be used to setup event handlers for characteristics and update respective values. - */ - configureAccessory(accessory: PlatformAccessory) - { - // add the restored accessory to the accessories cache so we can track if it has already been registered - this.cachedRobotAccessories.push(accessory); - } + /** + * This function is invoked when homebridge restores cached accessories from disk at startup. + * It should be used to setup event handlers for characteristics and update respective values. + */ + configureAccessory(accessory: PlatformAccessory) { + // add the restored accessory to the accessories cache so we can track if it has already been registered + this.cachedRobotAccessories.push(accessory); + } - - discoverRobots() - { - const client = new KoboldApi.Client(); + discoverRobots() { + const client = new KoboldApi.Client(); - try - { - // Login - client.authorize((this.config)["token"], (error) => { - if (error) - { - this.log.error("Cannot connect to Vorwerk server. No new robots will be found and existing robots will be unresponsive. Retrying in 5 minutes."); - this.log.error("Error: " + error); + try { + // Login + client.authorize(this.config['token'], (error) => { + if (error) { + this.log.error( + `Cannot connect to Vorwerk server. + No new robots will be found and existing robots will be unresponsive. + Retrying in 5 minutes.`, + ); + this.log.error('Error: ' + error); - setTimeout(() => { - this.discoverRobots(); - }, 5 * 60 * 1000); - return; - } + setTimeout(() => { + this.discoverRobots(); + }, 5 * 60 * 1000); + return; + } - // Get all robots from account - client.getRobots((error, robots) => { - if (error) - { - this.log.error("Successful login but can't list the robots in your Vorwerk robots. Retrying in 5 minutes."); - this.log.error("Error: " + error); + // Get all robots from account + client.getRobots((error, robots) => { + if (error) { + this.log.error( + 'Successful login but can\'t list the robots in your Vorwerk robots. Retrying in 5 minutes.', + ); + this.log.error('Error: ' + error); - setTimeout(() => { - this.discoverRobots(); - }, 5 * 60 * 1000); - return; - } + setTimeout(() => { + this.discoverRobots(); + }, 5 * 60 * 1000); + return; + } - // Vorwerk robots in account - if (robots.length === 0) - { - this.log.error("Vorwerk account has no robots."); - } - else - { - this.log.info("Vorwerk account has " + robots.length + " robot" + (robots.length === 1 ? "" : "s")); - } + // Vorwerk robots in account + if (robots.length === 0) { + this.log.error('Vorwerk account has no robots.'); + } else { + this.log.info( + 'Vorwerk account has ' + + robots.length + + ' robot' + + (robots.length === 1 ? '' : 's'), + ); + } - // Vorwerk robots in cache - this.log.debug("Plugin Cache has " + this.cachedRobotAccessories.length + " robot" + (this.cachedRobotAccessories.length === 1 ? "" : "s")); - for (let cachedRobot of this.cachedRobotAccessories) - { - let accountRobot = robots.find(robot => this.api.hap.uuid.generate(robot._serial) === cachedRobot.UUID); - if (accountRobot) - { - this.log.debug("[" + cachedRobot.displayName + "] Cached robot found in Vorwerk account."); - } - else - { - this.log.error("[" + cachedRobot.displayName + "] Cached robot not found in Vorwerk account. Robot will now be removed from homebridge."); - this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [cachedRobot]); - } - } + // Vorwerk robots in cache + this.log.debug( + 'Plugin Cache has ' + + this.cachedRobotAccessories.length + + ' robot' + + (this.cachedRobotAccessories.length === 1 ? '' : 's'), + ); + for (const cachedRobot of this.cachedRobotAccessories) { + const accountRobot = robots.find( + (robot) => + this.api.hap.uuid.generate(robot._serial) === cachedRobot.UUID, + ); + if (accountRobot) { + this.log.debug( + '[' + + cachedRobot.displayName + + '] Cached robot found in Vorwerk account.', + ); + } else { + this.log.error( + '[' + + cachedRobot.displayName + + '] Cached robot not found in Vorwerk account. Robot will now be removed from homebridge.', + ); + this.api.unregisterPlatformAccessories( + PLUGIN_NAME, + PLATFORM_NAME, + [cachedRobot], + ); + } + } - // Add / Update homebridge accessories with robot information from Vorwerk. This must be done for new and existing robots to reflect changes in the name, firmware, pluginconfig etc. - for (let robot of robots) - { - // Check if robot already exists as an accessory - const uuid = this.api.hap.uuid.generate(robot._serial); - const cachedRobot = this.cachedRobotAccessories.find(accessory => accessory.UUID === uuid); + // Add / Update homebridge accessories with robot information from Vorwerk. + // This must be done for new and existing robots to reflect changes in the name, firmware, pluginconfig etc. + for (const robot of robots) { + // Check if robot already exists as an accessory + const uuid = this.api.hap.uuid.generate(robot._serial); + const cachedRobot = this.cachedRobotAccessories.find( + (accessory) => accessory.UUID === uuid, + ); - if (cachedRobot) - { - this.log.debug("[" + robot.name + "] Connecting to cached robot and updating information."); - } - else - { - this.log.debug("[" + robot.name + "] Connecting to new robot and updating information."); - } + if (cachedRobot) { + this.log.debug( + '[' + + robot.name + + '] Connecting to cached robot and updating information.', + ); + } else { + this.log.debug( + '[' + + robot.name + + '] Connecting to new robot and updating information.', + ); + } - robot.getState((error, state) => { - if (error) - { - this.log.error("[" + robot.name + "] Cannot connect to robot. Is the robot connected to the internet? Retrying in 5 minutes."); - this.log.error("Error: " + error); - setTimeout(() => { - this.discoverRobots(); - }, 5 * 60 * 1000); - } - else - { - try - { - robot.meta = state.meta; - robot.availableServices = state.availableServices; + robot.getState((error, state) => { + if (error) { + this.log.error( + '[' + + robot.name + + '] Cannot connect to robot. Is the robot connected to the internet? Retrying in 5 minutes.', + ); + this.log.error('Error: ' + error); + setTimeout(() => { + this.discoverRobots(); + }, 5 * 60 * 1000); + } else { + try { + robot.meta = state.meta; + robot.availableServices = state.availableServices; - // Update existing robot accessor - if (cachedRobot) - { - // TODO update maps + // Update existing robot accessor + if (cachedRobot) { + // TODO update maps - cachedRobot.context.robot = robot; - this.api.updatePlatformAccessories([cachedRobot]); - new KoboldVacuumRobotAccessory(this, cachedRobot, this.config); - this.log.info("[" + robot.name + "] Successfully loaded robot from cache"); - } - // Create new robot accessory - else - { - // TODO get maps + cachedRobot.context.robot = robot; + this.api.updatePlatformAccessories([cachedRobot]); + new KoboldVacuumRobotAccessory( + this, + cachedRobot, + this.config, + ); + this.log.info( + '[' + + robot.name + + '] Successfully loaded robot from cache', + ); + } else { + // Create new robot accessory + // TODO get maps - const newRobot = new this.api.platformAccessory(robot.name, uuid); - newRobot.context.robot = robot; - new KoboldVacuumRobotAccessory(this, newRobot, this.config); - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [newRobot]); - this.log.info("[" + robot.name + "] Successfully created as new robot"); - } - } - catch (error) - { - this.log.error("[" + robot.name + "] Creating accessory failed. Error: " + error); - throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - } - } + const newRobot = new this.api.platformAccessory( + robot.name, + uuid, + ); + newRobot.context.robot = robot; + new KoboldVacuumRobotAccessory(this, newRobot, this.config); + this.api.registerPlatformAccessories( + PLUGIN_NAME, + PLATFORM_NAME, + [newRobot], + ); + this.log.info( + '[' + robot.name + '] Successfully created as new robot', + ); + } + } catch (error) { + this.log.error( + '[' + + robot.name + + '] Creating accessory failed. Error: ' + + error, + ); + throw new this.api.hap.HapStatusError( + this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE, + ); + } + } - // // Get all maps for each robot - // robot.getPersistentMaps((error, maps) => { - // if (error) - // { - // this.log.error("Error updating persistent maps: " + error + ": " + maps); - // callback(); - // } - // // Robot has no maps - // else if (maps.length === 0) - // { - // robot.maps = []; - // this.robotAccessories.push({device: robot, meta: state.meta, availableServices: state.availableServices}); - // loadedRobots++; - // if (loadedRobots === robots.length) - // { - // callback(); - // } - // } - // // Robot has maps - // else - // { - // robot.maps = maps; - // let loadedMaps = 0; - // robot.maps.forEach((map) => { - // // Save zones in each map - // robot.getMapBoundaries(map.id, (error, result) => { - // if (error) - // { - // this.log.error("Error getting boundaries: " + error + ": " + result) - // } - // else - // { - // map.boundaries = result.boundaries; - // } - // loadedMaps++; - // - // // Robot is completely requested if zones for all maps are loaded - // if (loadedMaps === robot.maps.length) - // { - // this.robotAccessories.push({device: robot, meta: state.meta, availableServices: state.availableServices}); - // loadedRobots++; - // if (loadedRobots === robots.length) - // { - // callback(); - // } - // } - // }) - // }); - // } - // }); - }); - } - }); - }); - } - catch (error) - { - this.log.error("Can't log on to Vorwerk cloud. Please check your internet connection and your credentials. Try again later if the neato servers have issues. Error: " + error); - } - } + // // Get all maps for each robot + // robot.getPersistentMaps((error, maps) => { + // if (error) + // { + // this.log.error("Error updating persistent maps: " + error + ": " + maps); + // callback(); + // } + // // Robot has no maps + // else if (maps.length === 0) + // { + // robot.maps = []; + // this.robotAccessories.push({device: robot, meta: state.meta, availableServices: state.availableServices}); + // loadedRobots++; + // if (loadedRobots === robots.length) + // { + // callback(); + // } + // } + // // Robot has maps + // else + // { + // robot.maps = maps; + // let loadedMaps = 0; + // robot.maps.forEach((map) => { + // // Save zones in each map + // robot.getMapBoundaries(map.id, (error, result) => { + // if (error) + // { + // this.log.error("Error getting boundaries: " + error + ": " + result) + // } + // else + // { + // map.boundaries = result.boundaries; + // } + // loadedMaps++; + // + // // Robot is completely requested if zones for all maps are loaded + // if (loadedMaps === robot.maps.length) + // { + // this.robotAccessories.push({device: robot, meta: state.meta, availableServices: state.availableServices}); + // loadedRobots++; + // if (loadedRobots === robots.length) + // { + // callback(); + // } + // } + // }) + // }); + // } + // }); + }); + } + }); + }); + } catch (error) { + this.log.error( + `Can't log on to Vorwerk cloud. Please check your internet connection and your credentials. + Try again later if the neato servers have issues. Error: ` + + error, + ); + } + } } diff --git a/src/index.ts b/src/index.ts index 9564ba7..1f3029d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,11 @@ -import {API} from "homebridge"; +import { API } from 'homebridge'; -import {PLATFORM_NAME} from "./settings"; -import {HomebridgeKoboldPlatform} from "./homebridgeKoboldPlatform"; +import { PLATFORM_NAME } from './settings'; +import { HomebridgeKoboldPlatform } from './homebridgeKoboldPlatform'; /** * This method registers the platform with Homebridge */ -export = (api: API) => -{ - api.registerPlatform(PLATFORM_NAME, HomebridgeKoboldPlatform); +export = (api: API) => { + api.registerPlatform(PLATFORM_NAME, HomebridgeKoboldPlatform); }; diff --git a/src/models/options.ts b/src/models/options.ts index 91b552b..141c619 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,21 +1,19 @@ -export class Options -{ - public eco: boolean; - public extraCare: boolean; - public noGoLines: boolean; - public spotCharacteristics: boolean; - public spotRepeat: boolean; - public spotWidth: number; - public spotHeight: number; - - constructor() - { - this.eco = false; - this.extraCare = false; - this.noGoLines = false; - this.spotCharacteristics = false; - this.spotRepeat = false; - this.spotWidth = 200; - this.spotHeight = 200; - } -} \ No newline at end of file +export class Options { + public eco: boolean; + public extraCare: boolean; + public noGoLines: boolean; + public spotCharacteristics: boolean; + public spotRepeat: boolean; + public spotWidth: number; + public spotHeight: number; + + constructor() { + this.eco = false; + this.extraCare = false; + this.noGoLines = false; + this.spotCharacteristics = false; + this.spotRepeat = false; + this.spotWidth = 200; + this.spotHeight = 200; + } +} diff --git a/src/models/services.ts b/src/models/services.ts index eb8dfab..0495f9d 100644 --- a/src/models/services.ts +++ b/src/models/services.ts @@ -4,16 +4,16 @@ export enum CleanType { } export enum RobotService { - CLEAN = "clean", - CLEAN_SPOT = "cleanSpot", - CLEAN_ZONE = "cleanZone", - GO_TO_DOCK = "goToDock", - DOCKED = "dockState", - BIN_FULL = "binFull", - FIND_ME = "findMe", - SCHEDULE = "schedule", - ECO = "eco", - NOGO_LINES = "noGoLines", - EXTRA_CARE = "extraCare", - BATTERY = "battery", + CLEAN = 'clean', + CLEAN_SPOT = 'cleanSpot', + CLEAN_ZONE = 'cleanZone', + GO_TO_DOCK = 'goToDock', + DOCKED = 'dockState', + BIN_FULL = 'binFull', + FIND_ME = 'findMe', + SCHEDULE = 'schedule', + ECO = 'eco', + NOGO_LINES = 'noGoLines', + EXTRA_CARE = 'extraCare', + BATTERY = 'battery', }