278 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
let inherits = require('util').inherits,
 | 
						|
	debug = require('debug')('homebridge-neato'),
 | 
						|
	botvac = require('node-botvac'),
 | 
						|
 | 
						|
	Service,
 | 
						|
	Characteristic;
 | 
						|
 | 
						|
module.exports = function (homebridge)
 | 
						|
{
 | 
						|
	Service = homebridge.hap.Service;
 | 
						|
	Characteristic = homebridge.hap.Characteristic;
 | 
						|
	homebridge.registerPlatform("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobotPlatform);
 | 
						|
};
 | 
						|
 | 
						|
function NeatoVacuumRobotPlatform(log, config)
 | 
						|
{
 | 
						|
	this.log = log;
 | 
						|
	this.serial = "1-3-3-7";
 | 
						|
	this.email = config['email'];
 | 
						|
	this.password = config['password'];
 | 
						|
	this.hiddenServices = '';
 | 
						|
	this.hiddenServices = ('disabled' in config ? config['disabled'] : this.hiddenServices);
 | 
						|
	this.hiddenServices = ('hidden' in config ? config['hidden'] : this.hiddenServices);
 | 
						|
 | 
						|
	// Array of real robots and associated robot accessories (incl rooms)
 | 
						|
	this.robots = [];
 | 
						|
	this.nextRoom = null;
 | 
						|
 | 
						|
	if ('refresh' in config && config['refresh'] !== 'auto')
 | 
						|
	{
 | 
						|
		// parse config parameter
 | 
						|
		this.refresh = parseInt(config['refresh']);
 | 
						|
		// must be integer and positive
 | 
						|
		this.refresh = (typeof this.refresh !== 'number' || (this.refresh % 1) !== 0 || this.refresh < 0) ? 60 : this.refresh;
 | 
						|
		// minimum 60s to save some load on the neato servers
 | 
						|
		if (this.refresh > 0 && this.refresh < 60)
 | 
						|
		{
 | 
						|
			this.log.warn("Minimum refresh time is 60 seconds to not overload the neato servers");
 | 
						|
			this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// default auto
 | 
						|
	else
 | 
						|
	{
 | 
						|
		this.refresh = 'auto';
 | 
						|
	}
 | 
						|
	this.log("Refresh is set to: " + this.refresh + (this.refresh !== 'auto' ? ' seconds' : ''));
 | 
						|
}
 | 
						|
 | 
						|
NeatoVacuumRobotPlatform.prototype = {
 | 
						|
	accessories: function (callback)
 | 
						|
	{
 | 
						|
		debug("Get robots");
 | 
						|
		let accessories = [];
 | 
						|
		this.boundaryNames = [];
 | 
						|
		this.getRobots(() =>
 | 
						|
		{
 | 
						|
			this.robots.forEach((robot, i) =>
 | 
						|
			{
 | 
						|
				this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial.substring(0,9) + "XXXXXXXXXXXX\"");
 | 
						|
 | 
						|
				// Start Update Intervall
 | 
						|
				this.updateRobotTimer(robot.device._serial);
 | 
						|
 | 
						|
				let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic);
 | 
						|
				let mainAccessory = new NeatoVacuumRobotAccessory(this, robot);
 | 
						|
				accessories.push(mainAccessory);
 | 
						|
 | 
						|
				robot.mainAccessory = mainAccessory;
 | 
						|
				robot.roomAccessories = [];
 | 
						|
 | 
						|
				// For testing purposes only
 | 
						|
				robot.boundary = {name: "Testroom", id: "1"};
 | 
						|
				let roomAccessory = new NeatoVacuumRobotAccessory(this, robot);
 | 
						|
				accessories.push(roomAccessory);
 | 
						|
				robot.roomAccessories.push(roomAccessory);
 | 
						|
 | 
						|
				// if (robot.device.maps)
 | 
						|
				// {
 | 
						|
				// 	robot.device.maps.forEach((map) =>
 | 
						|
				// 	{
 | 
						|
				// 		if (map.boundaries)
 | 
						|
				// 		{
 | 
						|
				// 			map.boundaries.forEach((boundary) =>
 | 
						|
				// 			{
 | 
						|
				// 				if (boundary.type === "polygon")
 | 
						|
				// 				{
 | 
						|
				// 					robot.boundary = boundary;
 | 
						|
				// 					let roomAccessory = new NeatoVacuumRobotAccessory(this, robot);
 | 
						|
				// 					accessories.push(roomAccessory);
 | 
						|
				//
 | 
						|
				// 					robot.roomAccessories.push(roomAccessory);
 | 
						|
				// 				}
 | 
						|
				// 			})
 | 
						|
				// 		}
 | 
						|
				// 	})
 | 
						|
				// }
 | 
						|
			});
 | 
						|
			callback(accessories);
 | 
						|
		});
 | 
						|
	},
 | 
						|
 | 
						|
	getRobots: function (callback)
 | 
						|
	{
 | 
						|
		debug("Loading your robots");
 | 
						|
		let client = new botvac.Client();
 | 
						|
 | 
						|
		// Login
 | 
						|
		client.authorize(this.email, this.password, false, (error) =>
 | 
						|
		{
 | 
						|
			if (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);
 | 
						|
				callback();
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				// Get robots
 | 
						|
				client.getRobots((error, robots) =>
 | 
						|
				{
 | 
						|
					if (error)
 | 
						|
					{
 | 
						|
						this.log.error("Successful login but can't connect to your neato robot: " + error);
 | 
						|
						callback();
 | 
						|
					}
 | 
						|
					else if (robots.length === 0)
 | 
						|
					{
 | 
						|
						this.log.error("Successful login but no robots associated with your account.");
 | 
						|
						this.robots = [];
 | 
						|
						callback();
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						debug("Found " + robots.length + " robots");
 | 
						|
						let requestedRobot = 0;
 | 
						|
 | 
						|
						robots.forEach((robot) =>
 | 
						|
						{
 | 
						|
							// Get Maps for each robot
 | 
						|
							robot.getPersistentMaps((error, result) =>
 | 
						|
							{
 | 
						|
								if (error)
 | 
						|
								{
 | 
						|
									this.log.error("Error updating persistent maps: " + error + ": " + result);
 | 
						|
									callback();
 | 
						|
								}
 | 
						|
								else if (result.length === 0)
 | 
						|
								{
 | 
						|
									robot.maps = [];
 | 
						|
									callback();
 | 
						|
								}
 | 
						|
								else
 | 
						|
								{
 | 
						|
									robot.maps = result;
 | 
						|
									let requestedMap = 0;
 | 
						|
									robot.maps.forEach((map) =>
 | 
						|
									{
 | 
						|
										// Get Map Boundary Lines
 | 
						|
										robot.getMapBoundaries(map.id, (error, result) =>
 | 
						|
										{
 | 
						|
											if (error)
 | 
						|
											{
 | 
						|
												this.log.error("Error getting boundaries: " + error + ": " + result)
 | 
						|
											}
 | 
						|
											else
 | 
						|
											{
 | 
						|
												map.boundaries = result.boundaries;
 | 
						|
											}
 | 
						|
											requestedMap++;
 | 
						|
 | 
						|
											// Robot is completely requested if all maps are requested
 | 
						|
											if (requestedMap === robot.maps.length)
 | 
						|
											{
 | 
						|
												// Get additional information
 | 
						|
												robot.getState((error, result) =>
 | 
						|
												{
 | 
						|
													if (error)
 | 
						|
													{
 | 
						|
														this.log.error("Error getting robot meta information: " + error + ": " + result);
 | 
						|
														callback();
 | 
						|
													}
 | 
						|
													else
 | 
						|
													{
 | 
						|
														this.robots.push({device: robot, meta: result.meta, availableServices: result.availableServices});
 | 
						|
														requestedRobot++;
 | 
						|
 | 
						|
														// Initial request is complete if all robots are requested.
 | 
						|
														if (requestedRobot === robots.length)
 | 
						|
														{
 | 
						|
															callback();
 | 
						|
														}
 | 
						|
													}
 | 
						|
												});
 | 
						|
											}
 | 
						|
										})
 | 
						|
									});
 | 
						|
								}
 | 
						|
							});
 | 
						|
						});
 | 
						|
					}
 | 
						|
				});
 | 
						|
			}
 | 
						|
		});
 | 
						|
	},
 | 
						|
 | 
						|
	updateRobot: function (serial, callback)
 | 
						|
	{
 | 
						|
		let robot = this.getRobot(serial);
 | 
						|
 | 
						|
		// Data is up to date
 | 
						|
		if (typeof (robot.lastUpdate) !== 'undefined' && new Date() - robot.lastUpdate < 2000)
 | 
						|
		{
 | 
						|
			callback();
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			debug(robot.device.name + ": ++ Updating robot state");
 | 
						|
			robot.lastUpdate = new Date();
 | 
						|
			robot.device.getState((error, result) =>
 | 
						|
			{
 | 
						|
				if (error)
 | 
						|
				{
 | 
						|
					this.log.error("Cannot update robot. Check if robot is online. " + error);
 | 
						|
				}
 | 
						|
				callback();
 | 
						|
			});
 | 
						|
		}
 | 
						|
	},
 | 
						|
 | 
						|
	getRobot(serial)
 | 
						|
	{
 | 
						|
		let result;
 | 
						|
		this.robots.forEach(function (robot)
 | 
						|
		{
 | 
						|
			if (robot.device._serial === serial)
 | 
						|
			{
 | 
						|
				result = robot;
 | 
						|
			}
 | 
						|
		});
 | 
						|
		return result;
 | 
						|
	},
 | 
						|
 | 
						|
	updateRobotTimer: function (serial)
 | 
						|
	{
 | 
						|
		this.updateRobot(serial, () =>
 | 
						|
		{
 | 
						|
			let robot = this.getRobot(serial);
 | 
						|
			// Clear any other overlapping timers for this robot
 | 
						|
			clearTimeout(robot.timer);
 | 
						|
 | 
						|
			// Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available
 | 
						|
			robot.mainAccessory.updated();
 | 
						|
			robot.roomAccessories.forEach(accessory =>
 | 
						|
			{
 | 
						|
				accessory.updated();
 | 
						|
			});
 | 
						|
 | 
						|
			// Periodic refresh interval set in config
 | 
						|
			if (this.refresh !== 'auto' && this.refresh !== 0)
 | 
						|
			{
 | 
						|
				debug(robot.device.name + ": ++ Next background update in " + this.refresh + " seconds");
 | 
						|
				robot.timer = setTimeout(this.updateRobotTimer.bind(this), this.refresh * 1000, serial);
 | 
						|
			}
 | 
						|
			// Auto refresh set in config
 | 
						|
			else if (this.refresh === 'auto' && robot.device.canPause)
 | 
						|
			{
 | 
						|
				debug(robot.device.name + ": ++ Next background update in 60 seconds while cleaning (auto mode)");
 | 
						|
				robot.timer = setTimeout(this.updateRobotTimer.bind(this), 60 * 1000, serial);
 | 
						|
			}
 | 
						|
			// No refresh
 | 
						|
			else
 | 
						|
			{
 | 
						|
				debug(robot.device.name + ": ++ Stopped background updates");
 | 
						|
			}
 | 
						|
		});
 | 
						|
	},
 | 
						|
}; |