197 lines
6.8 KiB
TypeScript
197 lines
6.8 KiB
TypeScript
import {API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service} from 'homebridge';
|
|
import Debug from "debug";
|
|
import NeatoApi from "node-botvac";
|
|
import {PLATFORM_NAME, PLUGIN_NAME} from './settings';
|
|
import {NeatoVacuumRobotAccessory} from './accessories/NeatoVacuumRobot';
|
|
|
|
const debug = Debug("homebridge-neato");
|
|
|
|
/**
|
|
* 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 HomebridgeNeatoPlatform 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 robotAccessories: PlatformAccessory[] = [];
|
|
|
|
constructor(
|
|
public readonly log: Logger,
|
|
public readonly config: PlatformConfig,
|
|
public readonly api: API)
|
|
{
|
|
this.log.debug('Finished initializing platform:', this.config.name);
|
|
|
|
this.api.on('didFinishLaunching', () => {
|
|
log.debug('Executed didFinishLaunching callback');
|
|
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)
|
|
{
|
|
this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
|
|
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
|
this.robotAccessories.push(accessory);
|
|
}
|
|
|
|
discoverRobots()
|
|
{
|
|
debug("Discovering new robots");
|
|
let client = new NeatoApi.Client();
|
|
|
|
try
|
|
{
|
|
// Login
|
|
client.authorize((this.config)['email'], (this.config)['password'], false, (error) => {
|
|
if (error)
|
|
{
|
|
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
}
|
|
|
|
// Get all robots from account
|
|
client.getRobots((error, robots) => {
|
|
if (error)
|
|
{
|
|
this.log.error("Successful login but can't connect to your neato robot: " + error);
|
|
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
}
|
|
else if (robots.length === 0)
|
|
{
|
|
this.log.error("Successful login but no robots associated with your account.");
|
|
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
}
|
|
|
|
debug("Found " + robots.length + " robots");
|
|
let loadedRobots = 0;
|
|
|
|
for (let robot of robots)
|
|
{
|
|
// Get additional information for the robot
|
|
robot.getState((error, state) => {
|
|
this.log.debug("Got state for robot: " + robot.name);
|
|
robot.meta = state.meta;
|
|
|
|
if (error)
|
|
{
|
|
this.log.error("Error getting robot meta information: " + error + ": " + state);
|
|
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
}
|
|
|
|
try
|
|
{
|
|
const uuid = this.api.hap.uuid.generate(robot._serial);
|
|
const existingAccessory = this.robotAccessories.find(accessory => accessory.UUID === uuid);
|
|
|
|
if (existingAccessory)
|
|
{
|
|
// the accessory already exists
|
|
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
existingAccessory.context.robot = robot;
|
|
// TODO update maps
|
|
|
|
// if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
|
|
// existingAccessory.context.device = device;
|
|
// this.api.updatePlatformAccessories([existingAccessory]);
|
|
|
|
// create the accessory handler for the restored accessory
|
|
// this is imported from `platformAccessory.ts`
|
|
new NeatoVacuumRobotAccessory(this, existingAccessory, false);
|
|
|
|
// it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.:
|
|
// remove platform accessories when no longer present
|
|
// this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
|
|
// this.log.info('Removing existing accessory from cache:', existingAccessory.displayName);
|
|
}
|
|
else
|
|
{
|
|
this.log.info('Adding new accessory: ', robot.name);
|
|
const accessory = new this.api.platformAccessory(robot.name, uuid);
|
|
|
|
accessory.context.robot = robot;
|
|
new NeatoVacuumRobotAccessory(this, accessory, true);
|
|
|
|
// link the accessory to your platform
|
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
// TODO get maps
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
this.log.error("Error creating robot accessory: " + robot.name);
|
|
this.log.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 neato cloud. Please check your internet connection and your credentials. Try again later if the neato servers have issues: " + error);
|
|
}
|
|
}
|
|
}
|