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 ,
Characteristic ;
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 ;
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)
this . robots = [ ] ;
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 = [ ] ;
this . getRobots ( ( ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-21 23:43:44 +02:00
this . robots . forEach ( ( robot , i ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-24 20:06:04 +02:00
this . log ( "Found robot #" + ( i + 1 ) + " named \"" + robot . device . name + "\" with serial \"" + robot . device . _serial . substring ( 0 , 9 ) + "XXXXXXXXXXXX\"" ) ;
2019-09-22 16:07:35 +02:00
// Start Update Intervall
2019-09-21 23:43:44 +02:00
this . updateRobotTimer ( robot . device . _serial ) ;
let NeatoVacuumRobotAccessory = require ( './accessories/neatoVacuumRobot' ) ( Service , Characteristic ) ;
2019-09-22 22:30:29 +02:00
let mainAccessory = new NeatoVacuumRobotAccessory ( this , robot ) ;
2019-09-21 23:43:44 +02:00
accessories . push ( mainAccessory ) ;
2019-09-19 11:25:36 +02:00
2019-09-21 23:43:44 +02:00
robot . mainAccessory = mainAccessory ;
robot . roomAccessories = [ ] ;
2019-09-19 11:25:36 +02:00
2019-09-24 20:06:04 +02:00
// For testing purposes only
2019-09-26 08:59:17 +02:00
robot . boundary = { name : "Testroom" , id : "1" } ;
let roomAccessory = new NeatoVacuumRobotAccessory ( this , robot ) ;
accessories . push ( roomAccessory ) ;
robot . roomAccessories . push ( roomAccessory ) ;
2019-09-24 20:06:04 +02:00
2019-09-26 08:59:17 +02:00
// 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);
// }
// })
// }
// })
// }
2019-09-21 23:43:44 +02:00
} ) ;
2019-09-19 09:37:16 +02:00
callback ( accessories ) ;
} ) ;
} ,
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-09-21 23:43:44 +02:00
// Get 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." ) ;
this . robots = [ ] ;
2019-09-19 09:37:16 +02:00
callback ( ) ;
}
else
{
2019-09-21 23:43:44 +02:00
debug ( "Found " + robots . length + " robots" ) ;
let requestedRobot = 0 ;
robots . forEach ( ( robot ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-21 23:43:44 +02:00
// Get Maps for each robot
robot . getPersistentMaps ( ( error , result ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-21 23:43:44 +02:00
if ( error )
{
this . log . error ( "Error updating persistent maps: " + error + ": " + result ) ;
callback ( ) ;
}
else if ( result . length === 0 )
{
robot . maps = [ ] ;
callback ( ) ;
}
else
2019-09-19 09:37:16 +02:00
{
robot . maps = result ;
2019-09-21 23:43:44 +02:00
let requestedMap = 0 ;
2019-09-19 09:37:16 +02:00
robot . maps . forEach ( ( map ) =>
{
2019-09-21 23:43:44 +02:00
// Get Map Boundary Lines
2019-09-19 09:37:16 +02:00
robot . getMapBoundaries ( map . id , ( error , result ) =>
{
if ( error )
{
2019-09-21 23:43:44 +02:00
this . log . error ( "Error getting boundaries: " + error + ": " + result )
2019-09-19 09:37:16 +02:00
}
else
{
map . boundaries = result . boundaries ;
}
2019-09-21 23:43:44 +02:00
requestedMap ++ ;
// Robot is completely requested if all maps are requested
if ( requestedMap === robot . maps . length )
2019-09-19 09:37:16 +02:00
{
2019-09-22 16:07:35 +02:00
// Get additional information
robot . getState ( ( error , result ) =>
2019-09-19 09:37:16 +02:00
{
2019-09-22 16:07:35 +02:00
if ( error )
{
this . log . error ( "Error getting robot meta information: " + error + ": " + result ) ;
callback ( ) ;
}
else
{
2019-09-23 11:48:35 +02:00
this . robots . push ( { device : robot , meta : result . meta , availableServices : result . availableServices } ) ;
2019-09-22 16:07:35 +02:00
requestedRobot ++ ;
// Initial request is complete if all robots are requested.
if ( requestedRobot === robots . length )
{
callback ( ) ;
}
}
} ) ;
2019-09-19 09:37:16 +02:00
}
} )
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
} ;