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");
|
|
}
|
|
});
|
|
},
|
|
}; |