97 Commits

Author SHA1 Message Date
2990bf2d75 just drying someting 2022-05-02 14:20:05 +00:00
Luis R
5c52edeb3b Update README.md 2021-05-12 12:58:49 +02:00
Luis Riegger
56364f2ff3 Update Changelog 2021-05-04 08:04:20 +02:00
Luis Riegger
76dad58bbe 0.8.4 2021-05-04 08:03:07 +02:00
Luis Riegger
1e494ae683 Link to token getter in UI 2021-05-04 08:02:18 +02:00
Luis R
f206d3ddaf Update README.md 2021-05-03 13:16:30 +02:00
Luis Riegger
70099e3c5a Bump node-kobold-control version 2021-05-02 10:06:32 +02:00
Luis R
15f93ba1d7 Update README.md
Add npm shield tag
2021-04-30 15:18:10 +02:00
Luis R
bd6c77a4da Bump version to 0.8.3 2021-04-30 15:12:17 +02:00
Luis R
81d3385c48 Merge pull request #1 from aluini/feature/lang-fr
Add french plugin language
2021-04-30 15:11:38 +02:00
Luis R
6c7d669a43 Merge branch 'master' into feature/lang-fr 2021-04-30 15:08:46 +02:00
Luis Riegger
8ae17f6a4a Merge branch 'master' of https://github.com/himbeles/homebridge-kobold 2021-04-30 15:00:55 +02:00
Luis Riegger
13846a9322 update changelog and bump version to 0.8.2 2021-04-30 15:00:52 +02:00
Arne Blumentritt
ae7138e245 Fixed homebridge 1.3 warning because of additional parameter in setter callback 2021-04-30 14:52:26 +02:00
Arne Blumentritt
16140dc71e Fixed #61 Warnings in homebridge 1.3 because of illegal characteristic values 2021-04-30 14:51:47 +02:00
Luis R
310ed41e28 Update npm.yml 2021-04-30 14:41:40 +02:00
Luis R
b9e0e64390 Create Github Action for publishing to npm 2021-04-30 14:29:01 +02:00
Alexandre Luini
0376dc09d5 add french plugin language 2021-03-10 16:49:40 +01:00
Luis Riegger
4b6002f686 bump npm version 2021-02-07 15:31:31 +01:00
Luis Riegger
c847481487 Include Include Robot name in Homekit battery service name 2021-02-07 15:27:57 +01:00
Luis Riegger
cc99ecef0d add german plugin language 2021-02-07 11:15:08 +01:00
Luis Riegger
721db866ff remove _config.yml 2021-02-07 10:25:02 +01:00
Luis R
6821adc1e3 Update README.md 2020-12-13 22:51:05 +01:00
Luis R
df52bd103f Update README.md 2020-12-13 22:49:48 +01:00
Luis Riegger
28cdabe47c correct typos in config.schema.json 2020-12-13 22:20:01 +01:00
Luis Riegger
6fd2e46bff Update README 2020-12-13 18:33:13 +01:00
Luis Riegger
a99359f6ac adapt for node-kobold-control api 2020-12-13 18:31:44 +01:00
Luis Riegger
49e3fdf191 adapt package dependency 2020-12-13 10:23:59 +01:00
Luis Riegger
295c9b01a1 Further adapt README.md, Licence and Package 2020-12-12 15:52:26 +01:00
Luis Riegger
eecf7f21f8 Adapt README.md 2020-12-12 15:42:06 +01:00
Arne
5ff3951668 Update README.md 2020-10-08 12:10:43 +02:00
Arne
b83d30cfad Update README.md 2020-03-14 00:33:45 +01:00
Arne
b53ef4ff2f Update README.md 2020-03-14 00:32:48 +01:00
Arne
d8ca308338 Updated changelog 2019-10-19 17:33:58 +02:00
Arne
75180b27cb Bumped version to v0.7.2 2019-10-19 17:31:38 +02:00
Arne
b8971b64a9 Updated readme 2019-10-18 10:02:55 +02:00
Arne
32e47d0a8f Fixed launch error with multiple robots 2019-10-16 22:32:53 +02:00
Arne
f0dd89353e Bumped version to v0.7.1 2019-10-15 09:33:32 +02:00
Arne
d809be5b03 Fixed robot without floorplan cannot start cleaning 2019-10-14 22:33:08 +02:00
Arne
6e5b0d522c Fixed robot not shown before setting up a floor plan 2019-10-14 12:47:52 +02:00
Arne
9af5fe8a26 Bumped version to v0.7.0 2019-10-12 11:29:10 +02:00
Arne
61b66444ac Fixed missing callback when sending to another room 2019-10-12 11:28:09 +02:00
Arne
7197d78f33 Fixed a bug in spot cleaning 2019-10-11 12:17:24 +02:00
Arne
fa25b0d6a5 Bumped version to v0.7.0-beta.10 2019-09-26 09:08:35 +02:00
Arne
b7c2f82173 Bumped version to v0.7.8-beta.9 2019-09-26 08:59:35 +02:00
Arne
09e19d38cf Refactored zone cleaning and improved debugging 2019-09-26 08:59:17 +02:00
Arne
500ae64e0f Renamed hidden config option (backwards compatible) 2019-09-25 09:57:23 +02:00
Arne
12dd6d4676 Renamed spot repeat button 2019-09-24 20:32:09 +02:00
Arne
5402123340 Bumped version to v0.7.0-beta.8 and enabled find me 2019-09-24 20:09:16 +02:00
Arne
e75686e665 Bumped version to v0.7.0-beta.7 2019-09-24 20:06:20 +02:00
Arne
bef37d88c8 Fixed a bug that crashed the plugin on devices with zone cleaning
Fixed a bug in the update cache timing
2019-09-24 20:06:04 +02:00
Arne
af54046927 Fixed a bug that caused the cleaning of all rooms at once 2019-09-23 12:12:18 +02:00
Arne
1a4308ac40 Bumped version to v0.7.0-beta.6 2019-09-23 11:48:50 +02:00
Arne
46ba5e5f30 Added spot cleaning function with individual spot size (D7) 2019-09-23 11:48:35 +02:00
Arne
b8fa1db8ae Merge pull request #23 from nicoh88/patch-1
Added support for spot cleaning, added repeat and 4x4 mode for spot cleaning
2019-09-23 09:31:33 +02:00
Arne
0e92f09079 Merge branch 'master' into patch-1 2019-09-23 09:12:25 +02:00
Arne
73bc399d64 Bumped version to v0.7.0-beta.5 2019-09-22 22:30:29 +02:00
Arne
e49b4af85a Bumped version to v0.7.0-beta.4 2019-09-22 22:16:58 +02:00
Arne
139f415a42 Prepared find me function and fixed room cleaning exception 2019-09-22 22:16:43 +02:00
Arne
c789598019 Bumped version to v0.7.0-beta.3 2019-09-22 16:08:02 +02:00
Arne
c6def8c0fc Fixed some issues and added more device info 2019-09-22 16:07:35 +02:00
Arne
114ff70d60 Moved update logic to platform to reduce server requests for multiple rooms 2019-09-21 23:43:44 +02:00
Arne
7594843bb3 Fixed boundary issues 2019-09-21 14:47:41 +02:00
Arne
0ac882414a Added feature to start cleaning of a new room. 2019-09-21 14:28:31 +02:00
Arne
6a57eaa57e Bumped version to 0.7.0-beta.0 2019-09-21 14:25:49 +02:00
Arne
8822670f9b Fixed boundary cleaning 2019-09-19 12:10:17 +02:00
Arne
150b8973ee Changed coffee button 2019-09-19 11:26:42 +02:00
Arne
ca2af3968c Updated debug lib 2019-09-19 11:26:27 +02:00
Arne
6f19a189b2 Reworked room cleaning (WIP) 2019-09-19 11:25:36 +02:00
Arne
5139929bce Formatting only 2019-09-19 09:37:16 +02:00
Arne
a3b64b7c53 Added option to buy me a coffee 2019-09-17 22:27:06 +02:00
Arne
0338580c0a Version 0.6.3 2019-08-14 23:45:19 +02:00
Arne
54f303394e Fixed homebridge crash when 2 zones have the same name 2019-08-14 23:43:03 +02:00
Arne
c43a666378 Fixed homebridge crash when homebridge has no internet connection or the neato servers are offline 2019-08-14 23:06:03 +02:00
Arne
20c54a02e8 Fixed homebridge crash when robot has a map without zones 2019-08-14 22:48:23 +02:00
Arne
1436e4b342 Fixed homebridge startup failed when robot does not support mapping 2019-07-31 10:05:59 +02:00
Arne
db8305bbee Fixed homebridge startup failed when robot does not support zone cleaning 2019-07-20 22:37:27 +02:00
Arne
f795781d2a Code cleanup 2019-07-20 22:27:16 +02:00
Arne
3cfde323a8 Added gitignore 2019-07-20 22:20:31 +02:00
Arne
4a97487400 Added contributor and bumped version to v0.6.0 2019-07-14 23:35:09 +02:00
Arne
5ea9dde49c Merge pull request #28 from az0uz/feature/zone_cleaning
Support for zone cleaning
2019-07-14 23:32:43 +02:00
Arne
8ef24c9d40 Update package.json 2019-07-10 10:27:11 +02:00
Antoine de Maleprade
979dc40ccf fixed uuid reset when restarting homebridge, removed logs 2019-05-06 20:11:04 -07:00
Antoine de Maleprade
7d824ee0b9 Working zone cleaning, implemented multiple accesories to have a room per switch in homekit 2019-04-28 17:35:49 -07:00
Antoine de Maleprade
b87e49e12f fix: zone services discovery 2019-04-27 22:52:49 -07:00
Antoine de Maleprade
afe7d690ef Added zone cleaning 2019-04-27 22:06:07 -07:00
Arne
442b91c347 Versionbump to v0.5.2 2019-04-09 12:46:08 +02:00
Arne
55020c005b Merge pull request #26 from btutal/master
Add Schema for Config
2019-04-09 12:43:31 +02:00
Berkay
7c72aabb25 Add Schema for Config
Add schema file for homebridge-config-ui-x and similar plugins
2019-04-08 22:26:30 +02:00
Nico Hartung
769712405a Update README.md for new spot cleaning 2019-01-03 23:25:38 +01:00
Nico Hartung
7b70d8f076 Added support for spot cleaning, added repeat and 4x4 mode for spot cleaning
Repeat and 4x4 mode are not persistent - node-botvac api not supported. After a reboot of homebridge set it to off/false - use it for spot cleaning in compination with homekit scenes or automations.
2019-01-03 23:23:14 +01:00
Arne
e1754e02ed Fixed console log in node-botvac 2018-11-17 13:27:33 +01:00
Arne
0b864eb229 Updated readme 2018-11-14 22:42:38 +01:00
Arne
1d1fa121a4 Updated readme 2018-11-14 18:24:52 +01:00
Arne
6c6bb1c204 Updated readme and changelog 2018-11-14 18:22:27 +01:00
Arne
577e62ea32 Improved refresh interval 2018-11-13 21:47:39 +01:00
Arne
fbec31602f NoGo Lines and options sync
ADDED
- NoGo lines button
- Extra care navigation button
- Syncing cleaning options from last run

CHANGED
- Goto dock button is now always off
- Better error handling
- More detailed debug messages
- Updated node-botvac dependency to 0.1.6

REMOVED
- Extra care navigation option parameter (is now a button)
2018-11-11 15:53:50 +01:00
13 changed files with 1370 additions and 373 deletions

19
.github/workflows/npm.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: npm
on:
workflow_dispatch:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2
with:
node-version: '12.x'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

75
.gitignore vendored Normal file
View File

@@ -0,0 +1,75 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/dist-e2e
/tmp
/out-tsc
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
*.iml
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/libpeerconnection.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
package-lock.json

View File

@@ -62,4 +62,74 @@
## 0.4.7
* Fixed an exception when no robot is associated with the account
* Fixed an exception when no robot is associated with the account
## 0.5.0
* Added noGo lines button
* Added extra care navigation button
* Added syncing cleaning options from last run
* Added option to disable background state update completely
* Changed goto dock button is now always off
* Changed error handling
* Changed debug messages
* Updated node-botvac dependency to 0.1.6
* Removed extra care navigation option parameter (is now a button)
## 0.5.1
* Updated node-botvac dependency to 0.1.7
## 0.5.2
* Added schema file for use with homebridge-config-ui-x
## 0.6.0
* Added support for zone cleaning
## 0.6.1
* Fixed homebridge startup failed when robot does not support zone cleaning
## 0.6.2
* Fixed homebridge startup failed when robot does not support mapping
## 0.6.3
* Fixed homebridge crash when robot has a map without zones
* Fixed homebridge crash when homebridge has no internet connection or the neato servers are offline
* Fixed homebridge crash when 2 zones have the same name
## 0.7.0
* Added find me function
* Added spot cleaning function with individual spot size and repeat option
* Added model and firmware information to homekit
* Added logic to be able to change the currently cleaned room
* Improved number of requests when having multiple rooms
* Fixed room switches not taking eco and extraCare mode into account
* Fixed room switches not supporting pause/resume
## 0.7.1
* Fixed robot not shown before setting up a floor plan
## 0.7.2
* Fixed homebridge crash with multiple robots per account
## 0.8.0
* Add German plugin language (for example, this gives you a "Sauge Küche" Siri command for a zone called "Küche")
* Added possibility to toggle between languages (English/German) in Homebridge UI Plugin Settings
## 0.8.1
* Include Robot name in Homekit battery service name
## 0.8.2
* Eliminate warnings on Homebridge >= 1.3.0 (77945f8 and 877c3d7 on `naofireblade/homebridge-neato`)
## 0.8.3
* Add French plugin language (for example, this gives you a "Aspirer la cuisine" Siri command for a zone called "La cuisine")
## 0.8.4
* Link to token getter tool in homebridge UI

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Arne Blumentritt
Copyright (c) 2020 Luis Riegger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

145
README.md
View File

@@ -1,82 +1,131 @@
# homebridge-neato
[![npm](https://img.shields.io/npm/v/homebridge-neato.svg?style=flat-square)](https://www.npmjs.com/package/homebridge-neato)
[![npm](https://img.shields.io/npm/dt/homebridge-neato.svg?style=flat-square)](https://www.npmjs.com/package/homebridge-neato)
[![GitHub last commit](https://img.shields.io/github/last-commit/naofireblade/homebridge-neato.svg?style=flat-square)](https://github.com/naofireblade/homebridge-neato)
[![Latest NPM release](https://img.shields.io/npm/v/homebridge-kobold.svg)](https://www.npmjs.com/package/homebridge-kobold)
[![NPM Downloads](https://img.shields.io/npm/dt/homebridge-kobold.svg)](https://www.npmjs.com/package/homebridge-kobold?activeTab=versions)
This is a plugin for [homebridge](https://github.com/nfarina/homebridge) to control your [Neato](https://www.neatorobotics.com/) vacuum robot. You can download it via [npm](https://www.npmjs.com/package/homebridge-neato).
Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues).
# homebridge-kobold
If you update from a previous version 0.3.x you have to adapt your config (plugin is now a platform).
This is a plugin for [homebridge](https://github.com/nfarina/homebridge) to control your [Vorwerk Kobold](https://kobold.vorwerk.de/saugroboter/) VR300 vacuum robot. You can download it via [npm](https://www.npmjs.com/package/homebridge-kobold).
It is based on a fork of naofireblade's [homebridge-neato](https://github.com/naofireblade/homebridge-neato), merged with the oAuth authentication mechanism from nicoh88's [homebridge-vorwerk](https://github.com/nicoh88/homebridge-vorwerk).
The interaction with the Server is handled by the underlying [node-kobold-control](https://github.com/himbeles/node-kobold-control) module.
## Features
- Start and pause cleaning
- Return to dock\*
- Enable and disable schedule
- Enable and disable eco mode
- Get battery info
- Get dock info
- Periodic refresh of robot state
- Support for multiple robots
- Extra care navigation
- House Cleaning
- Eco mode
- Extra care navigation
- Nogo lines
- Zone cleaning <sup>[1](#change-room)</sup>
- Spot cleaning
- Individual spot size <sup>[2](#eve)</sup>
- Clean twice <sup>[2](#eve)</sup>
- Return to dock
- Find the robot
- Schedule (de)activation
- Robot information
- Battery level
- Charging state
- Dock occupancy
- Model and firmware version
- Automatic or periodic refresh of robot state
- Multiple robots
- German, English or French Language Setting
> <b name="change-room">2</b> You can send the robot from one room to another as well. He will return to the base, wait there some seconds and then starts cleaning the next room.
> <b name="eve">3</b> You need a third party app like eve to access these features.
\* Available after some seconds of cleaning.
## Installation
1. Install homebridge using: `npm install -g homebridge`
2. Install this plugin using: `npm install -g homebridge-neato`
3. If you don't have a Neato account yet create one [here](https://www.neatorobotics.com/create-account/).
4. Update your configuration file. See the sample below.
2. Install this plugin using: `npm install -g homebridge-kobold`
3. Update your configuration file. See the sample below.
## Configuration
Add the following information to your config file. Change the values for email and password.
Add the following information to your config file. Adapt the value for `token`.
### Simple
```json
"platforms": [
{
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword"
}
{
"platform": "KoboldVacuumRobot",
"token": "YourToken",
"language": "de"
}
]
```
You can get a token using the GUI tool [Kobold Token Getter](https://github.com/himbeles/kobold-token-get) or using the following two curl commands:
```bash
# This will trigger the email sending
curl -X "POST" "https://mykobold.eu.auth0.com/passwordless/start" \
-H 'Content-Type: application/json' \
-d '{
"send": "code",
"email": "ENTER_YOUR_EMAIL_HERE",
"client_id": "KY4YbVAvtgB7lp8vIbWQ7zLk3hssZlhR",
"connection": "email"
}'
```
==== wait for the email to be received ====
```bash
# this will generate a token using the numbers you received via email
# replace the value of otp 123456 with the value you received from the email
curl -X "POST" "https://mykobold.eu.auth0.com/oauth/token" \
-H 'Content-Type: application/json' \
-d '{
"prompt": "login",
"grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp",
"scope": "openid email profile read:current_user",
"locale": "en",
"otp": "123456",
"source": "vorwerk_auth0",
"platform": "ios",
"audience": "https://mykobold.eu.auth0.com/userinfo",
"username": "ENTER_YOUR_EMAIL_HERE",
"client_id": "KY4YbVAvtgB7lp8vIbWQ7zLk3hssZlhR",
"realm": "email",
"country_code": "DE"
}'
```
From the output, you want to copy the `id_token` value.
The `language` can be `de` for German, `en` for English, or `fr` for French.
### Advanced
The following config contains advanced optional settings that are off when not specified.
Below are explanations for advanced parameters to adjust the plugin to your needs. All parameters are *optional*.
The parameter **refresh** sets in what interval (seconds) changes of the robot state will be pushed to homekit. The minimum refresh time is 60 seconds. You need this only when you set up rules based on the robot state and start him outside of homekit (e.g. with the Neato app).
**refresh**
Timer for periodic refresh of robot state. The default is `auto`. The options are:
`auto` Updates the robot state when a cleaning was started via homekit so that you can activate automations based on a successful cleaning.
`120` Or any other time in seconds (minimum `60`) is required if you want to receive robot state updates after starting the cleaning from outside of homekit (e.g. neato app or schedule).
`0` Disables background updates completely.
The parameter **extraCareNavigation** sets if supporting models (currently Neato D3 and D5) should take extra care of your furniture while cleaning.
The parameter **disabled** accepts a list of switches/sensors that can be disabled in the neato homekit plugin (e.g. dock, dockstate, eco, schedule).
**hidden**
List of plugin features that you don't want to use in homekit (e.g. `dock`, `dockstate`, `eco`, `nogolines`, `extracare`, `schedule`, `find`, `spot`).
```json
"platforms": [
{
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword",
"refresh": "120",
"extraCareNavigation": true,
"disabled": ["dock", "dockstate", "eco"]
}
{
"platform": "KoboldVacuumRobot",
"token": "YourToken",
"refresh": "120",
"hidden": ["dock", "dockstate", "eco", "nogolines", "extracare", "schedule", "find", "spot"],
"language": "de"
}
]
```
## Tested robots
- BotVac Connected (Firmware 2.2.0)
- BotVac D3 Connected
- BotVac D5 Connected (Firmware 4.0.0-1632)
If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin.
## Contributors
Many thanks go to
- [ghulands](https://github.com/ghulands) for finding and fixing a bug when no robot is associated with the neato account
- Vorwerk Kobold VR300

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

@@ -0,0 +1,726 @@
const debug = require('debug')('homebridge-kobold');
const colors = require('colors');
const CustomUUID = {
SpotCleanWidth: 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E',
SpotCleanHeight: 'CA282DB2-62BF-4325-A1BE-F8BB5478781A',
SpotCleanRepeat: '1E79C603-63B8-4E6A-9CE1-D31D67981831'
};
let Service,
Characteristic,
SpotWidthCharacteristic,
SpotHeightCharacteristic,
SpotRepeatCharacteristic;
module.exports = function (_Service, _Characteristic)
{
Service = _Service;
Characteristic = _Characteristic;
SpotWidthCharacteristic = require('../characteristics/spotWidth')(Characteristic, CustomUUID);
SpotHeightCharacteristic = require('../characteristics/spotHeight')(Characteristic, CustomUUID);
SpotRepeatCharacteristic = require('../characteristics/spotRepeat')(Characteristic, CustomUUID);
return KoboldVacuumRobotAccessory;
};
function KoboldVacuumRobotAccessory(platform, robotObject)
{
this.platform = platform;
this.log = platform.log;
this.refresh = platform.refresh;
this.hiddenServices = platform.hiddenServices;
this.nextRoom = platform.nextRoom;
this.robotObject = robotObject;
this.robot = robotObject.device;
this.meta = robotObject.meta;
this.spotPlusFeatures = ((typeof robotObject.availableServices.spotCleaning !== 'undefined') && robotObject.availableServices.spotCleaning.includes("basic"));
this.boundary = (typeof robotObject.boundary === 'undefined') ? null : robotObject.boundary;
this.dict = {
'en': {
"clean": "Clean",
"clean the": "Clean the",
"goToDock": "Go to Dock",
"dockState": "Dock",
"eco": "Eco Mode",
"noGoLines": "NoGo Lines",
"extraCare": "Extra Care",
"schedule": "Schedule",
"findMe": "Find me",
"cleanSpot": "Clean Spot",
"battery": "Battery"
},
'de': {
"clean": "Sauge",
"clean the": "Sauge",
"goToDock": "Zur Basis",
"dockState": "In der Basis",
"eco": "Eco Modus",
"noGoLines": "NoGo Linien",
"extraCare": "Extra Care",
"schedule": "Zeitplan",
"findMe": "Finde mich",
"cleanSpot": "Spot Reinigung",
"battery": "Batterie"
},
'fr': {
"clean": "Aspirer",
"clean the": "Aspirer",
"goToDock": "Retour à la base",
"dockState": "Sur la base",
"eco": "Eco mode",
"noGoLines": "Lignes NoGo",
"extraCare": "Extra Care",
"schedule": "Planifier",
"findMe": "Me retrouver",
"cleanSpot": "Nettoyage local",
"battery": "Batterie"
}
}[this.platform.language]
if (this.boundary == null)
{
this.name = this.robot.name;
}
else
{
// if boundary name already exists
if (platform.boundaryNames.includes(this.boundary.name))
{
let lastChar = this.boundary.name.slice(-1);
// boundary name already contains a count number
if (!isNaN(lastChar))
{
// Increment existing count number
this.boundary.name = this.boundary.name.slice(0, -1) + (parseInt(lastChar) + 1);
}
else
{
// Add a new count number
this.boundary.name = this.boundary.name + " 2";
}
}
platform.boundaryNames.push(this.boundary.name);
this.name = this.robot.name + ' - ' + this.boundary.name;
}
this.batteryService = new Service.BatteryService(this.name + " " + this.dict["battery"], "battery");
if (this.boundary == null)
{
this.cleanService = new Service.Switch(this.name + " " + this.dict["clean"], "clean");
this.goToDockService = new Service.Switch(this.name + " " + this.dict["goToDock"], "goToDock");
this.dockStateService = new Service.OccupancySensor(this.name + " " + this.dict["dockState"], "dockState");
this.ecoService = new Service.Switch(this.name + " " + this.dict["eco"], "eco");
this.noGoLinesService = new Service.Switch(this.name + " " + this.dict["noGoLines"], "noGoLines");
this.extraCareService = new Service.Switch(this.name + " " + this.dict["extraCare"], "extraCare");
this.scheduleService = new Service.Switch(this.name + " " + this.dict["schedule"], "schedule");
this.findMeService = new Service.Switch(this.name + " " + this.dict["findMe"], "findMe");
this.spotCleanService = new Service.Switch(this.name + " " + this.dict["cleanSpot"], "cleanSpot");
this.spotCleanService.addCharacteristic(SpotRepeatCharacteristic);
if (this.spotPlusFeatures)
{
this.spotCleanService.addCharacteristic(SpotWidthCharacteristic);
this.spotCleanService.addCharacteristic(SpotHeightCharacteristic);
}
}
else
{
const splitName = this.boundary.name.split(' ');
let serviceName = this.dict["clean the"] + " " + this.boundary.name;
if (splitName.length >= 2 && splitName[splitName.length - 2].match(/[']s$/g))
{
serviceName = this.dict["clean"] + " " + this.boundary.name;
}
this.cleanService = new Service.Switch(serviceName, "cleanBoundary:" + this.boundary.id);
this.cleanService = new Service.Switch(serviceName + "1", "cleanBoundary:" + this.boundary.id)
}
this.log("Added cleaning device named: " + this.name);
}
KoboldVacuumRobotAccessory.prototype = {
identify: function (callback)
{
this.robot.getState((error, result) =>
{
if (error)
{
this.log.error("Error getting robot information: " + error + ": " + result);
}
else
{
this.log("### Robot information ###");
this.log(result);
}
callback();
});
},
getServices: function ()
{
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Vorwerk Deutschland Stiftung & Co. KG")
.setCharacteristic(Characteristic.Model, this.meta.modelName)
.setCharacteristic(Characteristic.SerialNumber, this.robot._serial)
.setCharacteristic(Characteristic.FirmwareRevision, this.meta.firmware)
.setCharacteristic(Characteristic.Name, this.robot.name + (this.boundary == null ? '' : ' - ' + this.boundary.name));
this.cleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.cleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
this.services = [this.informationService, this.cleanService];
if (this.boundary == null)
{
this.batteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.batteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
this.services.push(this.batteryService);
this.goToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.goToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
this.dockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
this.ecoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.ecoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
this.noGoLinesService.getCharacteristic(Characteristic.On).on('set', this.setNoGoLines.bind(this));
this.noGoLinesService.getCharacteristic(Characteristic.On).on('get', this.getNoGoLines.bind(this));
this.extraCareService.getCharacteristic(Characteristic.On).on('set', this.setExtraCare.bind(this));
this.extraCareService.getCharacteristic(Characteristic.On).on('get', this.getExtraCare.bind(this));
this.scheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this));
this.scheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this));
this.findMeService.getCharacteristic(Characteristic.On).on('set', this.setFindMe.bind(this));
this.findMeService.getCharacteristic(Characteristic.On).on('get', this.getFindMe.bind(this));
this.spotCleanService.getCharacteristic(Characteristic.On).on('set', this.setSpotClean.bind(this));
this.spotCleanService.getCharacteristic(Characteristic.On).on('get', this.getSpotClean.bind(this));
this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).on('set', this.setSpotRepeat.bind(this));
this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).on('get', this.getSpotRepeat.bind(this));
if (this.spotPlusFeatures)
{
this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).on('set', this.setSpotWidth.bind(this));
this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).on('get', this.getSpotWidth.bind(this));
this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).on('set', this.setSpotHeight.bind(this));
this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).on('get', this.getSpotHeight.bind(this));
}
if (this.hiddenServices.indexOf('spot') === -1)
{
this.services.push(this.spotCleanService);
}
// Add optional services
if (this.hiddenServices.indexOf('dock') === -1)
this.services.push(this.goToDockService);
if (this.hiddenServices.indexOf('dockstate') === -1)
this.services.push(this.dockStateService);
if (this.hiddenServices.indexOf('eco') === -1)
this.services.push(this.ecoService);
if (this.hiddenServices.indexOf('nogolines') === -1)
this.services.push(this.noGoLinesService);
if (this.hiddenServices.indexOf('extracare') === -1)
this.services.push(this.extraCareService);
if (this.hiddenServices.indexOf('schedule') === -1)
this.services.push(this.scheduleService);
if (this.hiddenServices.indexOf('find') === -1)
this.services.push(this.findMeService);
}
return this.services;
},
getClean: function (callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
let cleaning;
if (this.boundary == null)
{
cleaning = this.robot.canPause;
}
else
{
cleaning = this.robot.canPause && (this.robot.cleaningBoundaryId === this.boundary.id)
}
debug(this.name + ": Cleaning is " + (cleaning ? 'ON'.brightGreen : 'OFF'.red));
callback(false, cleaning);
});
},
setClean: function (on, callback)
{
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Clean " + (this.boundary ? JSON.stringify(this.boundary) : ''));
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
// Start
if (on)
{
// No room given or same room
if (this.boundary == null || this.robot.cleaningBoundaryId === this.boundary.id)
{
// Resume cleaning
if (this.robot.canResume)
{
debug(this.name + ": ## Resume cleaning");
this.robot.resumeCleaning((error) =>
{
callback(error);
});
}
// Start cleaning
else if (this.robot.canStart)
{
debug(this.name + ": ## Start cleaning");
this.clean(callback);
}
// Cannot start
else
{
debug(this.name + ": Cannot start, maybe already cleaning (expected)");
callback();
}
}
// Different room given
else
{
// Return to dock
if (this.robot.canPause || this.robot.canResume)
{
debug(this.name + ": ## Returning to dock to start cleaning of new room");
this.setGoToDock(true, (error, result) =>
{
this.nextRoom = this.boundary.id;
callback();
});
}
// Start new cleaning of new room
else
{
debug(this.name + ": ## Start cleaning of new room");
this.clean(callback);
}
}
}
// Stop
else
{
if (this.robot.canPause)
{
debug(this.name + ": ## Pause cleaning");
this.robot.pauseCleaning((error) =>
{
callback(error);
});
}
else
{
debug(this.name + ": Already paused");
callback();
}
}
});
},
clean: function (callback, spot)
{
// Start automatic update while cleaning
if (this.refresh === 'auto')
{
setTimeout(() =>
{
this.platform.updateRobotTimer(this.robot._serial);
}, 60 * 1000);
}
let eco = this.robotObject.mainAccessory.ecoService.getCharacteristic(Characteristic.On).value;
let extraCare = this.robotObject.mainAccessory.extraCareService.getCharacteristic(Characteristic.On).value;
let nogoLines = this.robotObject.mainAccessory.noGoLinesService.getCharacteristic(Characteristic.On).value;
let room = (this.boundary == null) ? '' : this.boundary.name;
debug(this.name + ": ## Start cleaning (" + (room !== '' ? room + " " : '') + "eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ", spot: " + JSON.stringify(spot) + ")");
// Normal cleaning
if (this.boundary == null && (typeof spot === 'undefined'))
{
this.robot.startCleaning(eco, extraCare ? 2 : 1, nogoLines, (error, result) =>
{
if (error)
{
this.log.error("Cannot start cleaning. " + error + ": " + JSON.stringify(result));
}
callback(error);
});
}
// Room cleaning
else if (room !== '')
{
this.robot.startCleaningBoundary(eco, extraCare, this.boundary.id, (error, result) =>
{
if (error)
{
this.log.error("Cannot start room cleaning. " + error + ": " + JSON.stringify(result));
}
callback(error);
});
}
// Spot cleaning
else
{
this.robot.startSpotCleaning(eco, spot.width, spot.height, spot.repeat, extraCare ? 2 : 1, (error, result) =>
{
if (error)
{
this.log.error("Cannot start spot cleaning. " + error + ": " + JSON.stringify(result));
}
callback(error);
});
}
},
getGoToDock: function (callback)
{
callback(false, false);
},
setGoToDock: function (on, callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
if (this.robot.canPause)
{
debug(this.name + ": ## Pause cleaning to go to dock");
this.robot.pauseCleaning((error, result) =>
{
setTimeout(() =>
{
debug(this.name + ": ## Go to dock");
this.robot.sendToBase(() =>
{
callback();
});
}, 1000);
});
}
else if (this.robot.canGoToBase)
{
debug(this.name + ": ## Go to dock");
this.robot.sendToBase(() =>
{
callback();
});
}
else
{
this.log.warn(this.name + ": Can't go to dock at the moment");
callback();
}
}
else
{
callback();
}
});
},
getEco: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Eco Mode is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.eco);
});
},
setEco: function (on, callback)
{
this.robot.eco = on;
debug(this.name + ": " + (on ? "Enabled ".red : "Disabled".red) + " Eco Mode ");
callback();
},
getNoGoLines: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": NoGoLine is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.noGoLines ? 1 : 0);
});
},
setNoGoLines: function (on, callback)
{
this.robot.noGoLines = on;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " NoGoLine ");
callback();
},
getExtraCare: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Care Nav is " + (this.robot.navigationMode === 2 ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.navigationMode === 2 ? 1 : 0);
});
},
setExtraCare: function (on, callback)
{
this.robot.navigationMode = on ? 2 : 1;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Care Nav ");
callback();
},
getSchedule: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.isScheduleEnabled);
});
},
setSchedule: function (on, callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
debug(this.name + ": " + "Enabled".brightGreen + " Schedule");
this.robot.enableSchedule((error) =>
{
callback(error);
});
}
else
{
debug(this.name + ": " + "Disabled".red + " Schedule");
this.robot.disableSchedule((error) =>
{
callback(error);
});
}
});
},
getFindMe: function (callback)
{
callback(false, false);
},
setFindMe: function (on, callback)
{
if (on)
{
debug(this.name + ": ## Find me");
setTimeout(() =>
{
this.findMeService.setCharacteristic(Characteristic.On, false);
}, 1000);
this.robot.findMe((error) =>
{
callback(error);
});
}
},
getSpotClean: function (callback)
{
callback(false, this.spotCleanService.getCharacteristic(Characteristic.On).value);
},
setSpotClean: function (on, callback)
{
let spot = {
width: this.spotPlusFeatures ? this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).value : null,
height: this.spotPlusFeatures ? this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).value : null,
repeat: this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).value
};
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
// Start
if (on)
{
// Resume cleaning
if (this.robot.canResume)
{
debug(this.name + ": ## Resume (spot) cleaning");
this.robot.resumeCleaning(callback);
}
// Start cleaning
else if (this.robot.canStart)
{
this.clean(callback, spot);
}
// Cannot start
else
{
debug(this.name + ": Cannot start spot cleaning, maybe already cleaning");
callback();
}
}
// Stop
else
{
if (this.robot.canPause)
{
debug(this.name + ": ## Pause cleaning");
this.robot.pauseCleaning((error) =>
{
callback(error);
});
}
else
{
debug(this.name + ": Already paused");
callback();
}
}
});
},
getSpotWidth: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot width is " + this.robot.spotWidth + "cm");
callback(false, this.robot.spotWidth);
});
},
setSpotWidth: function (width, callback)
{
this.robot.spotWidth = width;
debug(this.name + ": Set spot width to " + width + "cm");
callback();
},
getSpotHeight: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot height is " + this.robot.spotHeight + "cm");
callback(false, this.robot.spotHeight);
});
},
setSpotHeight: function (height, callback)
{
this.robot.spotHeight = height;
debug(this.name + ": Set spot height to " + height + "cm");
callback();
},
getSpotRepeat: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot repeat is " + (this.robot.spotRepeat ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.spotRepeat);
});
},
setSpotRepeat: function (on, callback)
{
this.robot.spotRepeat = on;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Spot repeat");
callback();
},
getDock: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": The Dock is " + (this.robot.isDocked ? "OCCUPIED".brightGreen : "NOT OCCUPIED".red));
callback(false, this.robot.isDocked ? 1 : 0);
});
},
getBatteryLevel: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Battery is " + this.robot.charge + "%");
callback(false, this.robot.charge);
});
},
getBatteryChargingState: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Battery is " + (this.robot.isCharging ? "CHARGING".brightGreen : "NOT CHARGING".red));
callback(false, this.robot.isCharging);
});
},
updated: function ()
{
if (this.boundary == null)
{
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
if (this.cleanService.getCharacteristic(Characteristic.On).value !== this.robot.canPause)
{
this.cleanService.setCharacteristic(Characteristic.On, this.robot.canPause);
}
// dock switch is on (dock not seen before) and dock has just been seen -> turn switch off
if (this.goToDockService.getCharacteristic(Characteristic.On).value == true && this.robot.dockHasBeenSeen)
{
this.goToDockService.setCharacteristic(Characteristic.On, false);
}
if (this.scheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled)
{
this.scheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled);
}
// no commands here, values can be updated without problems
this.dockStateService.setCharacteristic(Characteristic.OccupancyDetected, this.robot.isDocked ? 1 : 0);
this.ecoService.setCharacteristic(Characteristic.On, this.robot.eco);
this.noGoLinesService.setCharacteristic(Characteristic.On, this.robot.noGoLines);
this.extraCareService.setCharacteristic(Characteristic.On, this.robot.navigationMode == 2 ? true : false);
this.spotCleanService.setCharacteristic(SpotRepeatCharacteristic, this.robot.spotRepeat);
if (this.spotPlusFeatures)
{
let widthProps = this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).props;
let heightProps = this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).props;
this.spotCleanService.setCharacteristic(SpotWidthCharacteristic,
this.robot.spotWidth >= widthProps.minValue && this.robot.spotWidth <= widthProps.maxValue ? this.robot.spotWidth : widthProps.minValue);
this.spotCleanService.setCharacteristic(SpotHeightCharacteristic,
this.robot.spotHeight >= heightProps.minValue && this.robot.spotHeight <= heightProps.maxValue ? this.robot.spotHeight : heightProps.minValue);
}
}
this.batteryService.setCharacteristic(Characteristic.BatteryLevel, this.robot.charge);
this.batteryService.setCharacteristic(Characteristic.ChargingState, this.robot.isCharging);
// Robot has a next room to clean in queue
if (this.nextRoom != null && this.robot.isDocked)
{
this.clean((error, result) =>
{
this.nextRoom = null;
debug("## Starting cleaning of next room");
});
}
}
};

View File

@@ -0,0 +1,21 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotHeight = function ()
{
Characteristic.call(this, 'Spot ↕', CustomUUID.SpotCleanHeight);
this.setProps({
format: Characteristic.Formats.INT,
unit: 'cm',
maxValue: 400,
minValue: 100,
minStep: 50,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotHeight, Characteristic);
return SpotHeight;
};

View File

@@ -0,0 +1,17 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotRepeat = function ()
{
Characteristic.call(this, 'Spot 2x', CustomUUID.SpotCleanRepeat);
this.setProps({
format: Characteristic.Formats.BOOL,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotRepeat, Characteristic);
return SpotRepeat;
};

View File

@@ -0,0 +1,21 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotWidth = function ()
{
Characteristic.call(this, 'Spot ↔', CustomUUID.SpotCleanWidth);
this.setProps({
format: Characteristic.Formats.INT,
unit: 'cm',
maxValue: 400,
minValue: 100,
minStep: 50,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotWidth, Characteristic);
return SpotWidth;
};

42
config.schema.json Normal file
View File

@@ -0,0 +1,42 @@
{
"pluginAlias": "KoboldVacuumRobot",
"pluginType": "platform",
"headerDisplay": "For Advanced settings like the refresh time interval or disabled switches/sensors. [Check Here](https://github.com/himbeles/homebridge-kobold#readme)",
"schema": {
"type": "object",
"properties": {
"token": {
"title": "token",
"type": "string",
"required": true,
"description": "Your Kobold Token (https://git.io/J3g1b)"
},
"language": {
"title": "language",
"type": "string",
"default": "en",
"oneOf": [
{
"title": "English",
"enum": [
"en"
]
},
{
"title": "German",
"enum": [
"de"
]
},
{
"title": "French",
"enum": [
"fr"
]
}
],
"required": true
}
}
}
}

575
index.js
View File

@@ -1,356 +1,305 @@
"use strict";
var inherits = require('util').inherits,
debug = require('debug')('homebridge-neato'),
botvac = require('node-botvac'),
let inherits = require('util').inherits,
debug = require('debug')('homebridge-kobold'),
control = require('node-kobold-control'),
Service,
Characteristic
Characteristic,
KoboldVacuumRobotAccessory;
module.exports = function (homebridge) {
module.exports = function (homebridge)
{
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerPlatform("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobotPlatform);
}
KoboldVacuumRobotAccessory = require('./accessories/koboldVacuumRobot')(Service, Characteristic);
homebridge.registerPlatform("homebridge-kobold", "KoboldVacuumRobot", KoboldVacuumRobotPlatform);
};
function NeatoVacuumRobotPlatform(log, config) {
function KoboldVacuumRobotPlatform(log, config)
{
this.log = log;
this.serial = "1-3-3-7";
this.email = config['email'];
this.password = config['password'];
this.hiddenServices = ('disabled' in config ? config['disabled'] : '');
this.token = config['token'];
this.language = config['language'];
this.hiddenServices = '';
this.hiddenServices = ('disabled' in config ? config['disabled'] : this.hiddenServices);
this.hiddenServices = ('hidden' in config ? config['hidden'] : this.hiddenServices);
this.careNavigation = ('extraCareNavigation' in config && config['extraCareNavigation'] ? 2 : 1);
debug("Extra Care Navigation: " + this.careNavigation);
// Array of real robots and associated robot accessories (incl rooms)
this.robots = [];
this.nextRoom = null;
// default off
this.refresh = ('refresh' in config ? parseInt(config['refresh']) : 0);
// must be integer and positive
this.refresh = (typeof this.refresh !=='number' || (this.refresh%1)!==0 || this.refresh < 0) ? 0 : this.refresh;
// minimum 60s
this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh;
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 Vorwerk servers
if (this.refresh > 0 && this.refresh < 60)
{
this.log.warn("Minimum refresh time is 60 seconds to not overload the Vorwerk 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) {
this.accessories = [];
KoboldVacuumRobotPlatform.prototype = {
accessories: function (callback)
{
debug("Get robots");
let accessories = [];
this.boundaryNames = [];
let that = this;
this.robots = this.getRobots(function () {
for (var i = 0; i < that.robots.length; i++) {
that.log("Found robot #" + (i+1) + ": " + that.robots[i].name);
var robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that);
that.accessories.push(robotAccessory);
}
callback(that.accessories);
this.getRobots(() =>
{
// // MOCK MULTIPLE ROBOTS START
// let client = new control.Client();
// client.authorize(this.token, (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
this.robots.forEach((robot, i) =>
{
this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial.substring(0, 9) + "XXXXXXXXXXXX\"");
let mainAccessory = new KoboldVacuumRobotAccessory(this, robot);
accessories.push(mainAccessory);
robot.mainAccessory = mainAccessory;
robot.roomAccessories = [];
// Start Update Intervall
this.updateRobotTimer(robot.device._serial);
// // MOCK ZONE CLEANING START
// robot.boundary = {name: "Testroom", id: "1"};
// let roomAccessory = new KoboldVacuumRobotAccessory(this, robot);
// accessories.push(roomAccessory);
// robot.roomAccessories.push(roomAccessory);
// // MOCK ZONE CLEANING END
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 KoboldVacuumRobotAccessory(this, robot);
accessories.push(roomAccessory);
robot.roomAccessories.push(roomAccessory);
}
})
}
})
}
});
callback(accessories);
// // MOCK MULTIPLE ROBOTS START
// });
// });
// });
// // MOCK MULTIPLE ROBOTS END
});
},
getRobots: function(callback) {
debug("Get all robots");
let client = new botvac.Client();
let that = this;
client.authorize(this.email, this.password, false, function (error) {
if (error) {
that.log(error);
that.log.error("Can't log on to neato cloud. Please check your credentials.");
getRobots: function (callback)
{
debug("Loading your robots");
let client = new control.Client();
// Login
client.authorize(this.token, (error) =>
{
if (error)
{
this.log.error("Can't log on to Vorwerk cloud. Please check your internet connection and your token. Try again later if the Vorwerk servers have issues: " + error);
callback();
}
else {
client.getRobots(function (error, robots) {
if (error) {
that.log(error);
that.log.error("Successful login but can't connect to your neato robot.");
else
{
// Get all robots
client.getRobots((error, robots) =>
{
if (error)
{
this.log.error("Successful login but can't connect to your Vorwerk robot: " + error);
callback();
}
else {
if (robots.length === 0) {
that.log.error("Successful login but no robots associated with your account.");
that.robots = [];
callback();
}
else {
that.robots = robots;
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 loadedRobots = 0;
robots.forEach((robot) =>
{
// Get additional information for the robot
robot.getState((error, state) =>
{
if (error)
{
this.log.error("Error getting robot meta information: " + error + ": " + state);
callback();
}
else
{
// Get all maps for each robot
robot.getPersistentMaps((error, maps) =>
{
if (error)
{
this.log.error("Error updating persistent maps: " + error + ": " + maps);
callback();
}
// Robot has no maps
else if (maps.length === 0)
{
robot.maps = [];
this.robots.push({device: robot, meta: state.meta, availableServices: state.availableServices});
loadedRobots++;
if (loadedRobots === robots.length)
{
callback();
}
}
// Robot has maps
else
{
robot.maps = maps;
let loadedMaps = 0;
robot.maps.forEach((map) =>
{
// 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();
}
}
})
});
}
});
}
});
});
}
});
}
});
}
}
function NeatoVacuumRobotAccessory(robot, platform) {
this.platform = platform;
this.log = platform.log;
this.refresh = platform.refresh;
this.careNavigation = platform.careNavigation;
this.hiddenServices = platform.hiddenServices;
this.robot = robot;
this.name = robot.name;
this.lastUpdate = null;
this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean");
this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock");
this.vacuumRobotDockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState");
this.vacuumRobotEcoService = new Service.Switch(this.name + " Eco Mode", "eco");
this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule");
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
this.updateRobotTimer();
}
NeatoVacuumRobotAccessory.prototype = {
identify: function (callback) {
let that = this;
this.updateRobot(function() {
// 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;
callback();
});
},
getServices: function () {
debug(this.robot._serial);
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name)
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
.setCharacteristic(Characteristic.Model, "Coming soon")
.setCharacteristic(Characteristic.SerialNumber, this.robot._serial);
updateRobot: function (serial, callback)
{
let robot = this.getRobot(serial);
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
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 = [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotBatteryService];
if (this.hiddenServices.indexOf('dock') === -1)
this.services.push(this.vacuumRobotGoToDockService);
if (this.hiddenServices.indexOf('dockstate') === -1)
this.services.push(this.vacuumRobotDockStateService);
if (this.hiddenServices.indexOf('eco') === -1)
this.services.push(this.vacuumRobotEcoService);
if (this.hiddenServices.indexOf('schedule') === -1)
this.services.push(this.vacuumRobotScheduleService);
return this.services;
},
setClean: function (on, callback) {
let that = this;
this.updateRobot(function (error, result) {
if (on) {
if (that.robot.canResume || that.robot.canStart) {
// wait for robot to start and then start a short timer to recognize when he can go to dock or is finished
setTimeout(function() {
clearTimeout(that.timer);
that.updateRobotTimer();
}, 10000);
if (that.robot.canResume) {
debug(that.name + ": Resume cleaning");
that.robot.resumeCleaning(callback);
}
else {
debug(that.name + ": Start cleaning (" + that.careNavigation + ")");
that.robot.startCleaning(that.robot.eco, that.careNavigation, callback);
}
}
else {
debug(that.name + ": Cant start, maybe already cleaning");
callback();
}
}
else {
if (that.robot.canPause) {
debug(that.name + ": Pause cleaning");
that.robot.pauseCleaning(callback);
}
else {
debug(that.name + ": Already stopped");
callback();
}
}
});
},
setGoToDock: function (on, callback) {
let that = this;
this.updateRobot(function (error, result) {
if (on) {
if (that.robot.canPause) {
debug(that.name + ": Pause cleaning to go to dock");
that.robot.pauseCleaning(function (error, result) {
setTimeout(function() {
debug("Go to dock");
that.robot.sendToBase(callback);
}, 1000);
});
}
else if (that.robot.canGoToBase)
{
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
}
else {
debug(that.name + ": Can't go to dock at the moment");
callback();
}
} else {
callback();
}
});
},
setEco: function (on, callback) {
debug(this.name + ": " + (on ? "Enable eco mode" : "Disable eco mode"));
this.robot.eco = on;
callback();
},
setSchedule: function (on, callback) {
let that = this;
this.updateRobot(function (error, result) {
if (on) {
debug(that.name + ": Enable schedule");
that.robot.enableSchedule(callback);
}
else {
debug(that.name + ": Disable schedule");
that.robot.disableSchedule(callback);
}
});
},
getClean: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Is cleaning: " + that.robot.canPause);
callback(false, that.robot.canPause);
});
},
getGoToDock: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Can go to dock: " + that.robot.dockHasBeenSeen);
callback(false, !that.robot.dockHasBeenSeen);
});
},
getDock: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Is docked: " + that.robot.isDocked);
callback(false, that.robot.isDocked ? 1 : 0);
});
},
getEco: function(callback) {
// dont load eco here, because we cant save the eco state on the robot
callback(false, this.robot.eco);
},
getSchedule: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Schedule: " + that.robot.isScheduleEnabled);
callback(false, that.robot.isScheduleEnabled);
});
},
getBatteryLevel: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Battery: " + that.robot.charge);
callback(false, that.robot.charge);
});
},
getBatteryChargingState: function(callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Is charging: " + that.robot.isCharging);
callback(false, that.robot.isCharging);
});
},
updateRobot: function(callback) {
let that = this;
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) {
debug(this.name + ": Update (cached)");
// Data is up to date
if (typeof (robot.lastUpdate) !== 'undefined' && new Date() - robot.lastUpdate < 2000)
{
callback();
}
else {
debug(this.name + ": Update (online)");
this.robot.getState(function (error, result) {
if (error) {
that.log.error("Error while updating robot state.");
that.log.error(error);
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);
}
that.lastUpdate = new Date();
callback();
});
}
},
updateRobotTimer: function() {
let that = this;
debug(this.name + ": Timer called");
this.updateRobot(function (error, result) {
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
if (that.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== that.robot.canPause) {
that.vacuumRobotCleanService.setCharacteristic(Characteristic.On, that.robot.canPause);
getRobot(serial)
{
let result;
this.robots.forEach(function (robot)
{
if (robot.device._serial === serial)
{
result = robot;
}
});
return result;
},
// dock switch is on (dock not seen before) and dock has just been seen -> turn switch off
if (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value == true && that.robot.dockHasBeenSeen) {
that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false);
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);
}
if (that.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== that.robot.isScheduleEnabled) {
that.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, that.robot.isScheduleEnabled);
// 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 commands here, values can be updated without problems
that.vacuumRobotDockStateService.setCharacteristic(Characteristic.OccupancyDetected, that.robot.isDocked ? 1 : 0);
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge);
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging);
// dont update eco, because we cant write that value onto the robot and dont want it to be overwritten in our plugin
if (that.robot.canPause) {
debug(that.name + ": Timer set (cleaning): 30s");
that.timer = setTimeout(that.updateRobotTimer.bind(that), 30 * 1000);
}
else if (that.refresh != 0) {
debug(that.name + ": Timer set (user): " + that.refresh + "s");
that.timer = setTimeout(that.updateRobotTimer.bind(that), that.refresh * 1000);
}
else {
debug(that.name + ": Timer stopped");
// No refresh
else
{
debug(robot.device.name + ": ++ Stopped background updates");
}
});
},
}
};

View File

@@ -1,26 +1,35 @@
{
"name": "homebridge-neato",
"version": "0.4.7",
"description": "A Neato vacuum robot plugin for homebridge.",
"name": "homebridge-kobold",
"version": "0.8.4",
"description": "A Vorwerk Kobold vacuum robot plugin for homebridge.",
"license": "MIT",
"keywords": [
"homebridge-plugin",
"neato",
"botvac"
"vorwerk",
"kobold"
],
"engines": {
"node": ">=0.12.0",
"homebridge": ">=0.2.0"
},
"author": {
"name": "Arne Blumentritt"
"name": "Luis R.",
"url2": "https://github.com/himbeles"
},
"contributors": [
{
"name": "Alexandre L.",
"url": "https://github.com/aluini"
}
],
"repository": {
"type": "git",
"url": "git://github.com/naofireblade/homebridge-neato.git"
"url": "git://github.com/himbeles/homebridge-kobold.git"
},
"dependencies": {
"node-botvac": ">=0.1.5",
"debug": "^2.2.0"
"colors": "^1.4.0",
"debug": "^4.1.1",
"node-kobold-control": ">=0.4.2",
"uuid": "^3.3.2"
}
}