Moved update logic to platform to reduce server requests for multiple rooms

This commit is contained in:
Arne 2019-09-21 23:43:44 +02:00
parent 7594843bb3
commit 114ff70d60
3 changed files with 257 additions and 226 deletions

View File

@ -11,19 +11,19 @@ module.exports = function (_Service, _Characteristic)
return NeatoVacuumRobotAccessory;
};
function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined)
function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined)
{
this.platform = platform;
this.boundary = boundary;
this.log = platform.log;
this.refresh = platform.refresh;
this.hiddenServices = platform.hiddenServices;
this.robot = robot;
this.robot = robotObject.device;
this.nextRoom = null;
if (typeof boundary === 'undefined')
{
this.name = robot.name;
this.name = this.robot.name;
}
else
{
@ -46,7 +46,6 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined)
platform.boundaryNames.push(this.boundary.name);
this.name = this.robot.name + ' - ' + this.boundary.name;
}
this.lastUpdate = null;
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
@ -72,24 +71,22 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined)
new Service.Switch(serviceName, "cleanBoundary:" + boundary.id);
this.log("Adding zone cleaning for: " + boundary.name);
}
this.updateRobotTimer();
}
NeatoVacuumRobotAccessory.prototype = {
identify: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
// hide serial and secret in log
let _serial = that.robot._serial;
let _secret = that.robot._secret;
that.robot._serial = "*****";
that.robot._secret = "*****";
that.log(that.robot);
that.robot._serial = _serial;
that.robot._secret = _secret;
let _serial = this.robot._serial;
let _secret = this.robot._secret;
this.robot._serial = "*****";
this.robot._secret = "*****";
this.log(this.robot);
this.robot._serial = _serial;
this.robot._secret = _secret;
callback();
});
},
@ -111,10 +108,7 @@ NeatoVacuumRobotAccessory.prototype = {
.setCharacteristic(Characteristic.Name, this.robot.name + ' - ' + this.boundary.name)
}
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
this.services = [this.informationService, this.vacuumRobotBatteryService];
this.services = [this.informationService];
if (typeof this.boundary === "undefined")
{
@ -144,7 +138,11 @@ NeatoVacuumRobotAccessory.prototype = {
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this));
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
this.services.push(this.vacuumRobotCleanService);
this.services.push(this.vacuumRobotBatteryService);
if (this.hiddenServices.indexOf('dock') === -1)
this.services.push(this.vacuumRobotGoToDockService);
@ -178,7 +176,7 @@ NeatoVacuumRobotAccessory.prototype = {
getClean: function (callback, boundary)
{
this.updateRobot((error, result) =>
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
let cleaning;
if (typeof boundary === 'undefined')
@ -197,7 +195,7 @@ NeatoVacuumRobotAccessory.prototype = {
setClean: function (on, callback, boundary)
{
this.updateRobot((error, result) =>
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
// Start
if (on)
@ -235,11 +233,6 @@ NeatoVacuumRobotAccessory.prototype = {
this.setGoToDock(true, (error, result) =>
{
this.nextRoom = boundary;
setTimeout(() =>
{
this.clean(callback, boundary);
}, 1000);
});
}
// Start new cleaning of new room
@ -273,8 +266,7 @@ NeatoVacuumRobotAccessory.prototype = {
{
setTimeout(() =>
{
clearTimeout(this.timer);
this.updateRobotTimer();
this.platform.updateRobotTimer(this.robot._serial);
}, 60 * 1000);
}
@ -330,31 +322,30 @@ NeatoVacuumRobotAccessory.prototype = {
setGoToDock: function (on, callback)
{
let that = this;
this.updateRobot(function (error, result)
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
if (that.robot.canPause)
if (this.robot.canPause)
{
debug(that.name + ": Pause cleaning to go to dock");
that.robot.pauseCleaning(function (error, result)
debug(this.name + ": Pause cleaning to go to dock");
this.robot.pauseCleaning((error, result) =>
{
setTimeout(function ()
setTimeout(() =>
{
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
debug(this.name + ": Go to dock");
this.robot.sendToBase(callback);
}, 1000);
});
}
else if (that.robot.canGoToBase)
else if (this.robot.canGoToBase)
{
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
debug(this.name + ": Go to dock");
this.robot.sendToBase(callback);
}
else
{
that.log.warn(that.name + ": Can't go to dock at the moment");
this.log.warn(this.name + ": Can't go to dock at the moment");
callback();
}
}
@ -367,140 +358,107 @@ NeatoVacuumRobotAccessory.prototype = {
getEco: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Eco mode is " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.eco);
debug(this.name + ": Eco Mode is " + (this.robot.eco ? 'ON' : 'OFF'));
callback(false, this.robot.eco);
});
},
setEco: function (on, callback)
{
this.robot.eco = on;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Eco mode ");
debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " Eco Mode ");
callback();
},
getNoGoLines: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Nogo Lines are " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.noGoLines ? 1 : 0);
debug(this.name + ": NoGoLine is " + (this.robot.eco ? 'ON' : 'OFF'));
callback(false, this.robot.noGoLines ? 1 : 0);
});
},
setNoGoLines: function (on, callback)
{
this.robot.noGoLines = on;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Nogo lines ");
debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " NoGoLine ");
callback();
},
getExtraCare: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Extra Care Navigation is " + (that.robot.navigationMode == 2 ? 'ON' : 'OFF'));
callback(false, that.robot.navigationMode == 2 ? 1 : 0);
debug(this.name + ": Care Nav is " + (this.robot.navigationMode === 2 ? 'ON' : 'OFF'));
callback(false, this.robot.navigationMode === 2 ? 1 : 0);
});
},
setExtraCare: function (on, callback)
{
this.robot.navigationMode = on ? 2 : 1;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Extra Care Navigation ");
debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " Care Nav ");
callback();
},
getSchedule: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial,() =>
{
debug(that.name + ": Schedule is " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.isScheduleEnabled);
debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON' : 'OFF'));
callback(false, this.robot.isScheduleEnabled );
});
},
setSchedule: function (on, callback)
{
let that = this;
this.updateRobot(function (error, result)
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
debug(that.name + ": Enabled Schedule");
that.robot.enableSchedule(callback);
debug(this.name + ": Enabled Schedule");
this.robot.enableSchedule(callback);
}
else
{
debug(that.name + ": Disabled Schedule");
that.robot.disableSchedule(callback);
debug(this.name + ": Disabled Schedule");
this.robot.disableSchedule(callback);
}
});
},
getDock: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Is " + (that.robot.isDocked ? '' : 'not ') + "docked");
callback(false, that.robot.isDocked ? 1 : 0);
debug(this.name + ": The Dock is " + (this.robot.isDocked ? '' : 'un ') + "occupied");
callback(false, this.robot.isDocked ? 1 : 0);
});
},
getBatteryLevel: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Battery is at " + that.robot.charge + "%");
callback(false, that.robot.charge);
debug(this.name + ": Battery is " + this.robot.charge + "%");
callback(false, this.robot.charge);
});
},
getBatteryChargingState: function (callback)
{
let that = this;
this.updateRobot(function ()
this.platform.updateRobot(this.robot._serial, () =>
{
debug(that.name + ": Is " + (that.robot.isCharging ? '' : 'not ') + "charging");
callback(false, that.robot.isCharging);
debug(this.name + ": Battery is " + (this.robot.isCharging ? '' : 'not ') + "charging");
callback(false, this.robot.isCharging);
});
},
updateRobot: function (callback)
updated: function ()
{
let that = this;
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000)
{
callback();
}
else
{
debug(this.name + ": Updating robot state");
this.robot.getState(function (error, result)
{
if (error)
{
that.log.error("Cannot update robot. Check if robot is online. " + error);
}
that.lastUpdate = new Date();
callback();
});
}
},
updateRobotTimer: function ()
{
this.updateRobot((error, result) =>
{
if (!this.boundary)
{
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
@ -544,27 +502,8 @@ NeatoVacuumRobotAccessory.prototype = {
this.clean((error, result) =>
{
this.nextRoom = null;
debug("Starting cleaning of next room");
}, this.nextRoom);
}
// robot is currently cleaning, refresh is set to auto or specific interval -> continue updating
if (this.robot.canPause && this.refresh !== 0)
{
let refreshTime = this.refresh === 'auto' ? 60 : this.refresh;
debug(this.name + ": Updating state in background every " + refreshTime + " seconds while cleaning");
this.timer = setTimeout(this.updateRobotTimer.bind(this), refreshTime * 1000);
}
// robot is not cleaning, but a specific refresh interval is set -> continue updating
else if (this.refresh !== 'auto' && this.refresh !== 0)
{
debug(this.name + ": Updating state in background every " + this.refresh + " seconds (user setting)");
this.timer = setTimeout(this.updateRobotTimer.bind(this), this.refresh * 1000);
}
// robot is not cleaning, no specific refresh interval is set -> stop updating
else
{
debug(this.name + ": Disabled Background Updates");
}
});
},
};

178
index.js
View File

@ -21,6 +21,9 @@ function NeatoVacuumRobotPlatform(log, config)
this.password = config['password'];
this.hiddenServices = ('disabled' in config ? config['disabled'] : '');
// Array of real robots and associated robot accessories (incl rooms)
this.robots = [];
if ('refresh' in config && config['refresh'] !== 'auto')
{
// parse config parameter
@ -28,37 +31,45 @@ function NeatoVacuumRobotPlatform(log, config)
// 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';
}
debug("Refresh is set to: " + this.refresh);
this.log("Refresh is set to: " + this.refresh + (this.refresh !== 'auto' ? ' seconds' : ''));
}
NeatoVacuumRobotPlatform.prototype = {
accessories: function (callback)
{
debug("##############################################");
debug("################# GET ROBOTS #################");
debug("##############################################");
let accessories = [];
let platform = this;
platform.boundaryNames = [];
this.getRobots(function ()
this.boundaryNames = [];
this.getRobots(() =>
{
if (platform.robots)
this.robots.forEach((robot, i) =>
{
platform.robots.forEach((robot, i) =>
{
platform.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\"");
this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial + "\"");
this.updateRobotTimer(robot.device._serial);
let NeatoVacuumRobotAccessory = require('./accessories/neatVacuumRobot')(Service, Characteristic);
let robotAccessory = new NeatoVacuumRobotAccessory(robot, platform);
accessories.push(robotAccessory);
let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic);
let mainAccessory = new NeatoVacuumRobotAccessory(robot, this);
accessories.push(mainAccessory);
if (robot.maps)
robot.mainAccessory = mainAccessory;
robot.roomAccessories = [];
if (robot.device.maps)
{
robot.maps.forEach((map) =>
robot.device.maps.forEach((map) =>
{
if (map.boundaries)
{
@ -66,14 +77,16 @@ NeatoVacuumRobotPlatform.prototype = {
{
if (boundary.type === "polygon")
{
accessories.push(new NeatoVacuumRobotAccessory(robot, platform, boundary))
}
})
let roomAccessory = new NeatoVacuumRobotAccessory(robot, this, boundary);
accessories.push(roomAccessory);
robot.roomAccessories.push(roomAccessory);
}
})
}
})
}
});
callback(accessories);
});
},
@ -82,83 +95,162 @@ NeatoVacuumRobotPlatform.prototype = {
{
debug("Loading your robots");
let client = new botvac.Client();
let that = this;
// Login
client.authorize(this.email, this.password, false, (error) =>
{
if (error)
{
that.log(error);
that.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.");
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)
{
that.log(error);
that.log.error("Successful login but can't connect to your neato robot.");
this.log.error("Successful login but can't connect to your neato robot: " + error);
callback();
}
else
else if (robots.length === 0)
{
if (robots.length === 0)
{
that.log.error("Successful login but no robots associated with your account.");
that.robots = [];
this.log.error("Successful login but no robots associated with your account.");
this.robots = [];
callback();
}
else
{
debug("Found " + robots.length + " robots");
let updatedRobotCount = 0;
that.robots = robots;
that.robots.forEach((robot) =>
let requestedRobot = 0;
robots.forEach((robot) =>
{
// Get Maps for each robot
robot.getPersistentMaps((error, result) =>
{
if (error)
{
that.log("Error updating persistent maps: " + error + ": " + result);
this.log.error("Error updating persistent maps: " + error + ": " + result);
callback();
return;
}
robot.maps = result;
let processedMapCount = 0;
if (robot.maps.length === 0)
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 getting boundaries: " + error + ": " + result)
this.log.error("Error getting boundaries: " + error + ": " + result)
}
else
{
map.boundaries = result.boundaries;
}
processedMapCount++;
if (processedMapCount === robot.maps.length)
requestedMap++;
// Robot is completely requested if all maps are requested
if (requestedMap === robot.maps.length)
{
updatedRobotCount++;
if (updatedRobotCount === that.robots.length)
this.robots.push({device: robot});
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.device.getState(function (error, result)
{
if (error)
{
this.log.error("Cannot update robot. Check if robot is online. " + error);
}
robot.lastUpdate = new Date();
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");
}
});
},
};

View File

@ -1,6 +1,6 @@
{
"name": "homebridge-neato",
"version": "0.7.0-beta.0",
"version": "0.7.0-beta.1",
"description": "A Neato vacuum robot plugin for homebridge.",
"license": "MIT",
"keywords": [