2017-04-17 16:07:21 +02:00
"use strict" ;
2019-09-19 12:10:17 +02:00
let inherits = require ( 'util' ) . inherits ,
2019-09-19 09:37:16 +02:00
debug = require ( 'debug' ) ( 'homebridge-neato' ) ,
botvac = require ( 'node-botvac' ) ,
2017-04-17 16:07:21 +02:00
2019-09-19 09:37:16 +02:00
Service ,
2019-10-16 22:32:53 +02:00
Characteristic ,
NeatoVacuumRobotAccessory ;
2017-04-17 16:07:21 +02:00
2019-09-19 09:37:16 +02:00
module . exports = function ( homebridge )
{
Service = homebridge . hap . Service ;
Characteristic = homebridge . hap . Characteristic ;
2019-10-16 22:32:53 +02:00
NeatoVacuumRobotAccessory = require ( './accessories/neatoVacuumRobot' ) ( Service , Characteristic ) ;
2019-09-19 09:37:16 +02:00
homebridge . registerPlatform ( "homebridge-neato" , "NeatoVacuumRobot" , NeatoVacuumRobotPlatform ) ;
2019-07-20 22:27:16 +02:00
} ;
2017-04-17 16:07:21 +02:00
2019-09-19 09:37:16 +02:00
function NeatoVacuumRobotPlatform ( log , config )
{
this . log = log ;
this . serial = "1-3-3-7" ;
this . email = config [ 'email' ] ;
this . password = config [ 'password' ] ;
2019-09-25 09:57:23 +02:00
this . hiddenServices = '' ;
this . hiddenServices = ( 'disabled' in config ? config [ 'disabled' ] : this . hiddenServices ) ;
this . hiddenServices = ( 'hidden' in config ? config [ 'hidden' ] : this . hiddenServices ) ;
2019-09-19 09:37:16 +02:00
2019-09-21 23:43:44 +02:00
// Array of real robots and associated robot accessories (incl rooms)
2021-04-28 20:07:39 +02:00
this . robotAccessories = [ ] ;
2019-09-26 08:59:17 +02:00
this . nextRoom = null ;
2019-09-21 23:43:44 +02:00
2019-09-19 09:37:16 +02:00
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
2019-09-21 23:43:44 +02:00
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 ;
}
2019-09-19 09:37:16 +02:00
}
// default auto
else
{
this . refresh = 'auto' ;
}
2019-09-21 23:43:44 +02:00
this . log ( "Refresh is set to: " + this . refresh + ( this . refresh !== 'auto' ? ' seconds' : '' ) ) ;
2017-06-05 16:46:45 +02:00
}
NeatoVacuumRobotPlatform . prototype = {
2019-09-19 09:37:16 +02:00
accessories : function ( callback )
{
2019-09-22 22:16:43 +02:00
debug ( "Get robots" ) ;
2019-09-19 09:37:16 +02:00
let accessories = [ ] ;
2019-09-21 23:43:44 +02:00
this . boundaryNames = [ ] ;
2019-10-16 22:32:53 +02:00
2019-09-21 23:43:44 +02:00
this . getRobots ( ( ) =>
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
// // MOCK MULTIPLE ROBOTS START
// let client = new botvac.Client();
// client.authorize(this.email, this.password, false, (error) =>
// {
// client.getRobots((error, robs) =>
// {
// let testRobot = robs[0];
// testRobot.getState((error, result) =>
// {
// testRobot.name = "Testrobot";
// this.robots.push({device: testRobot, meta: result.meta, availableServices: result.availableServices});
// // MOCK MULTIPLE ROBOTS END
2019-09-22 16:07:35 +02:00
2019-10-16 22:32:53 +02:00
this . robots . forEach ( ( robot , i ) =>
{
this . log ( "Found robot #" + ( i + 1 ) + " named \"" + robot . device . name + "\" with serial \"" + robot . device . _serial . substring ( 0 , 9 ) + "XXXXXXXXXXXX\"" ) ;
2019-09-21 23:43:44 +02:00
2019-10-16 22:32:53 +02:00
let mainAccessory = new NeatoVacuumRobotAccessory ( this , robot ) ;
accessories . push ( mainAccessory ) ;
2019-09-19 11:25:36 +02:00
2019-10-16 22:32:53 +02:00
robot . mainAccessory = mainAccessory ;
robot . roomAccessories = [ ] ;
2019-09-19 11:25:36 +02:00
2019-10-16 22:32:53 +02:00
// Start Update Intervall
this . updateRobotTimer ( robot . device . _serial ) ;
2019-09-24 20:06:04 +02:00
2019-10-16 22:32:53 +02:00
// // MOCK ZONE CLEANING START
// robot.boundary = {name: "Testroom", id: "1"};
// let roomAccessory = new NeatoVacuumRobotAccessory(this, robot);
// accessories.push(roomAccessory);
// robot.roomAccessories.push(roomAccessory);
// // MOCK ZONE CLEANING END
if ( robot . device . maps )
2019-09-26 09:08:35 +02:00
{
2019-10-16 22:32:53 +02:00
robot . device . maps . forEach ( ( map ) =>
2019-09-26 09:08:35 +02:00
{
2019-10-16 22:32:53 +02:00
if ( map . boundaries )
{
map . boundaries . forEach ( ( boundary ) =>
{
if ( boundary . type === "polygon" )
{
robot . boundary = boundary ;
let roomAccessory = new NeatoVacuumRobotAccessory ( this , robot ) ;
accessories . push ( roomAccessory ) ;
2019-09-26 09:08:35 +02:00
2019-10-16 22:32:53 +02:00
robot . roomAccessories . push ( roomAccessory ) ;
}
} )
}
} )
}
} ) ;
callback ( accessories ) ;
// // MOCK MULTIPLE ROBOTS START
// });
// });
// });
// // MOCK MULTIPLE ROBOTS END
2019-09-19 09:37:16 +02:00
} ) ;
} ,
getRobots : function ( callback )
{
debug ( "Loading your robots" ) ;
let client = new botvac . Client ( ) ;
2019-09-21 23:43:44 +02:00
// Login
2019-09-19 09:37:16 +02:00
client . authorize ( this . email , this . password , false , ( error ) =>
{
if ( error )
{
2019-09-21 23:43:44 +02:00
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 ) ;
2019-09-19 09:37:16 +02:00
callback ( ) ;
}
else
{
2019-10-14 12:47:52 +02:00
// Get all robots
2019-09-19 09:37:16 +02:00
client . getRobots ( ( error , robots ) =>
{
if ( error )
{
2019-09-21 23:43:44 +02:00
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." ) ;
2021-04-28 20:07:39 +02:00
this . robotAccessories = [ ] ;
2019-09-19 09:37:16 +02:00
callback ( ) ;
}
else
{
2019-09-21 23:43:44 +02:00
debug ( "Found " + robots . length + " robots" ) ;
2019-10-14 12:47:52 +02:00
let loadedRobots = 0 ;
2019-09-21 23:43:44 +02:00
robots . forEach ( ( robot ) =>
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
// Get additional information for the robot
robot . getState ( ( error , state ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-21 23:43:44 +02:00
if ( error )
{
2019-10-16 22:32:53 +02:00
this . log . error ( "Error getting robot meta information: " + error + ": " + state ) ;
2019-09-21 23:43:44 +02:00
callback ( ) ;
}
else
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
// Get all maps for each robot
robot . getPersistentMaps ( ( error , maps ) =>
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
if ( error )
{
this . log . error ( "Error updating persistent maps: " + error + ": " + maps ) ;
callback ( ) ;
}
// Robot has no maps
else if ( maps . length === 0 )
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
robot . maps = [ ] ;
this . robots . push ( { device : robot , meta : state . meta , availableServices : state . availableServices } ) ;
loadedRobots ++ ;
if ( loadedRobots === robots . length )
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
callback ( ) ;
2019-09-19 09:37:16 +02:00
}
2019-10-16 22:32:53 +02:00
}
// Robot has maps
else
{
robot . maps = maps ;
let loadedMaps = 0 ;
robot . maps . forEach ( ( map ) =>
2019-09-19 09:37:16 +02:00
{
2019-10-16 22:32:53 +02:00
// 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 . robots . push ( { device : robot , meta : state . meta , availableServices : state . availableServices } ) ;
loadedRobots ++ ;
if ( loadedRobots === robots . length )
{
callback ( ) ;
}
}
} )
} ) ;
}
2019-09-21 23:43:44 +02:00
} ) ;
}
} ) ;
} ) ;
2019-09-19 09:37:16 +02:00
}
} ) ;
}
} ) ;
2019-09-21 23:43:44 +02:00
} ,
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" ) ;
2019-09-24 20:06:04 +02:00
robot . lastUpdate = new Date ( ) ;
2019-09-22 16:07:35 +02:00
robot . device . getState ( ( error , result ) =>
2019-09-21 23:43:44 +02:00
{
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" ) ;
}
} ) ;
} ,
2019-09-19 11:25:36 +02:00
} ;