Compare commits
121 Commits
v0.3.0
...
dynamic-pl
Author | SHA1 | Date | |
---|---|---|---|
|
4752c39887 | ||
|
2d35e8b814 | ||
|
109ebbf162 | ||
|
44f550fe92 | ||
|
d9b5c7f572 | ||
|
7a0758f883 | ||
|
94b3cc143f | ||
|
a62972d022 | ||
|
dea0d157eb | ||
|
bc527a60ba | ||
|
653d685148 | ||
|
7ffc2a1a31 | ||
|
96b2e7f3e6 | ||
|
073fa915cf | ||
|
e53bb3d777 | ||
|
552e360f6f | ||
|
7173b2ec9e | ||
|
f7f70ac478 | ||
|
296a81010a | ||
|
b13885bea7 | ||
|
e0bd97ee5d | ||
|
0530ba6c39 | ||
|
eda198ed09 | ||
|
0ed30314df | ||
|
4a97891dfd | ||
|
6f1f078ba4 | ||
|
313c532f87 | ||
|
3af668399b | ||
|
877c3d7d26 | ||
|
77945f8420 | ||
|
5ff3951668 | ||
|
b83d30cfad | ||
|
b53ef4ff2f | ||
|
d8ca308338 | ||
|
75180b27cb | ||
|
b8971b64a9 | ||
|
32e47d0a8f | ||
|
f0dd89353e | ||
|
d809be5b03 | ||
|
6e5b0d522c | ||
|
9af5fe8a26 | ||
|
61b66444ac | ||
|
7197d78f33 | ||
|
fa25b0d6a5 | ||
|
b7c2f82173 | ||
|
09e19d38cf | ||
|
500ae64e0f | ||
|
12dd6d4676 | ||
|
5402123340 | ||
|
e75686e665 | ||
|
bef37d88c8 | ||
|
af54046927 | ||
|
1a4308ac40 | ||
|
46ba5e5f30 | ||
|
b8fa1db8ae | ||
|
0e92f09079 | ||
|
73bc399d64 | ||
|
e49b4af85a | ||
|
139f415a42 | ||
|
c789598019 | ||
|
c6def8c0fc | ||
|
114ff70d60 | ||
|
7594843bb3 | ||
|
0ac882414a | ||
|
6a57eaa57e | ||
|
8822670f9b | ||
|
150b8973ee | ||
|
ca2af3968c | ||
|
6f19a189b2 | ||
|
5139929bce | ||
|
a3b64b7c53 | ||
|
0338580c0a | ||
|
54f303394e | ||
|
c43a666378 | ||
|
20c54a02e8 | ||
|
1436e4b342 | ||
|
db8305bbee | ||
|
f795781d2a | ||
|
3cfde323a8 | ||
|
4a97487400 | ||
|
5ea9dde49c | ||
|
8ef24c9d40 | ||
|
979dc40ccf | ||
|
7d824ee0b9 | ||
|
b87e49e12f | ||
|
afe7d690ef | ||
|
442b91c347 | ||
|
55020c005b | ||
|
7c72aabb25 | ||
|
769712405a | ||
|
7b70d8f076 | ||
|
e1754e02ed | ||
|
0b864eb229 | ||
|
1d1fa121a4 | ||
|
6c6bb1c204 | ||
|
577e62ea32 | ||
|
fbec31602f | ||
|
42fa0399f8 | ||
|
0de549dd41 | ||
|
7fef07df0a | ||
|
9e9dd80547 | ||
|
b7ec93953e | ||
|
ad41c1679d | ||
|
89f6f233a5 | ||
|
88f217e2b1 | ||
|
2fec762498 | ||
|
857af55e99 | ||
|
20e0a9b909 | ||
|
afdff765b0 | ||
|
dcef6653ff | ||
|
82bf19c548 | ||
|
1084bff0ee | ||
|
5aa66835dd | ||
|
08ef90f7b0 | ||
|
bfb03b5d5d | ||
|
56e85c92e0 | ||
|
d6dd94979b | ||
|
6deca89d27 | ||
|
5eb5b9934d | ||
|
9a5c3f942c | ||
|
8f6f3dc72d |
38
.eslintrc
Normal file
38
.eslintrc
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"dist",
|
||||
"api.ts"
|
||||
],
|
||||
"rules": {
|
||||
"quotes": ["warn", "double"],
|
||||
"indent": ["warn", "tab"],
|
||||
"semi": ["off"],
|
||||
"comma-dangle": ["warn", "always-multiline"],
|
||||
"dot-notation": "off",
|
||||
"eqeqeq": "warn",
|
||||
"curly": ["warn", "all"],
|
||||
"prefer-arrow-callback": ["warn"],
|
||||
"max-len": ["warn", 200],
|
||||
"no-console": ["warn"], // use the provided Homebridge log method instead
|
||||
"no-non-null-assertion": ["off"],
|
||||
"comma-spacing": ["error"],
|
||||
"no-multi-spaces": ["warn", { "ignoreEOLComments": true }],
|
||||
"no-trailing-spaces": ["warn"],
|
||||
"lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/semi": ["warn"],
|
||||
"@typescript-eslint/member-delimiter-style": ["warn"]
|
||||
}
|
||||
}
|
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- You must use the issue template below when submitting a bug -->
|
||||
|
||||
**Describe The Bug:**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce:**
|
||||
<!-- Steps to reproduce the behavior. -->
|
||||
|
||||
**Expected behavior:**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Logs:**
|
||||
|
||||
```
|
||||
Show the Homebridge logs here, remove any sensitive information.
|
||||
```
|
||||
|
||||
**Plugin Config:**
|
||||
|
||||
```json
|
||||
Show your Homebridge config.json here, remove any sensitive information.
|
||||
```
|
||||
|
||||
**Screenshots:**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Environment:**
|
||||
|
||||
* **Plugin Version**:
|
||||
* **Homebridge Version**: <!-- homebridge -V -->
|
||||
* **Node.js Version**: <!-- node -v -->
|
||||
* **NPM Version**: <!-- npm -v -->
|
||||
* **Operating System**: <!-- Raspbian / Ubuntu / Debian / Windows / macOS / Docker / hb-service -->
|
||||
|
||||
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'New Feature'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe:**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like:**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered:**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context:**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
|
||||
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
|
38
.github/ISSUE_TEMPLATE/support-request.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/support-request.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Support Request
|
||||
about: Need help?
|
||||
title: ''
|
||||
labels: Question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- You must use the issue template below when submitting a support request -->
|
||||
|
||||
**Describe Your Problem:**
|
||||
<!-- A clear and concise description of what problem you are trying to solve. -->
|
||||
|
||||
**Logs:**
|
||||
|
||||
```
|
||||
Show the Homebridge logs here, remove any sensitive information.
|
||||
```
|
||||
|
||||
**Plugin Config:**
|
||||
|
||||
```json
|
||||
Show your Homebridge config.json here, remove any sensitive information.
|
||||
```
|
||||
|
||||
**Screenshots:**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Environment:**
|
||||
|
||||
* **Plugin Version**:
|
||||
* **Homebridge Version**: <!-- homebridge -V -->
|
||||
* **Node.js Version**: <!-- node -v -->
|
||||
* **NPM Version**: <!-- npm -v -->
|
||||
* **Operating System**: <!-- Raspbian / Ubuntu / Debian / Windows / macOS / Docker / hb-service -->
|
||||
|
||||
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
|
120
.gitignore
vendored
Normal file
120
.gitignore
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
# Ignore compiled code
|
||||
dist
|
||||
|
||||
# ------------- Defaults ------------- #
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
135
.npmignore
Normal file
135
.npmignore
Normal file
@@ -0,0 +1,135 @@
|
||||
# Ignore source code
|
||||
src
|
||||
|
||||
# ------------- Defaults ------------- #
|
||||
|
||||
# gitHub actions
|
||||
.github
|
||||
|
||||
# eslint
|
||||
.eslintrc
|
||||
|
||||
# typescript
|
||||
tsconfig.json
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# nodemon
|
||||
nodemon.json
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
116
CHANGELOG.md
116
CHANGELOG.md
@@ -19,6 +19,120 @@
|
||||
|
||||
* Added periodic refresh of robot state while cleaning
|
||||
* Added optional periodic refresh of robot state while not cleaning
|
||||
* Added error messages when cant login or get robot
|
||||
* Improved go to dock switch to be enabled as soon as possible without manual refresh
|
||||
* Improved switches to indicate the time an action needs to complete
|
||||
* Improved eco mode to not be overwritten by robot state update
|
||||
* Improved eco mode to not be overwritten by robot state update
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* Added support for Neato BotVac D5 Connected
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* Fixed a bug that refresh is not disabled when set to 0
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* Added support for multiple robots
|
||||
* Added log output when user requests accessory identify
|
||||
* Changed plugin to platform instead of single accessory
|
||||
* Removed parameter name from config
|
||||
|
||||
## 0.4.1
|
||||
|
||||
* Added config parameter for extraCareNavigation
|
||||
|
||||
## 0.4.2
|
||||
|
||||
* Added config parameter to disable switches/sensors
|
||||
|
||||
## 0.4.4
|
||||
|
||||
* Fixed config parameter to disable switches/sensors not optional
|
||||
|
||||
## 0.4.5
|
||||
|
||||
* Fixed compatibility with homebridge 0.4.23 (occupancy sensor not working)
|
||||
|
||||
## 0.4.6
|
||||
|
||||
* Added error log while refreshing robot state
|
||||
* Fixed a rare bug where the robot stops after some seconds of cleaning
|
||||
|
||||
## 0.4.7
|
||||
|
||||
* 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.7.3
|
||||
* Fixed warnings since homebridge 1.3.0
|
||||
|
||||
## 1.0.0-beta.4
|
||||
* Added bin full sensor
|
||||
* Added config-ui support for all options
|
||||
* Added config parameter **prefix** to use robot name as prefix for service names
|
||||
* Retrying mechanism if a robot is not available on homebridge launch
|
||||
* Changed service names to not include robot name as prefix by default
|
||||
* Changed background update to use better default intervals (1 minute while cleaning, 30 minutes while idle)
|
||||
* Changed config parameter **refresh**. Renamed to **backgroundUpdate**, unit changed to minute and will only be used during idle
|
||||
* Changed config parameter **hidden**. Renamed to **services**, now takes list of services that should be _visible_. Default are all available services.
|
||||
* Fixed robots no longer disappear or change the room after connection issues with the Neato API
|
||||
* Fixed plugin no longer crashes if non smart robot is assigned in neato account
|
||||
* Fixed options for eco, nogo lines, extra care, spot repeat, spot size are now saved in homebridge and will no longer be overridden by Neato API
|
||||
|
||||
## TODO until 1.0.0 release
|
||||
* Room cleaning
|
96
README.md
96
README.md
@@ -1,50 +1,100 @@
|
||||
# homebridge-neato
|
||||
[](https://www.npmjs.com/package/homebridge-neato)
|
||||
[](https://www.npmjs.com/package/homebridge-neato?activeTab=versions)
|
||||
[](https://github.com/naofireblade/homebridge-neato)
|
||||
|
||||
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).
|
||||
|
||||
If you like this plugin and find it useful, I would be forever grateful for your support:
|
||||
|
||||
<a href="https://www.buymeacoffee.com/naofireblade" target="_blank"><img width="140" src="https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png" alt="Buy Me A Coffee"></a>
|
||||
|
||||
Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues).
|
||||
|
||||
# Features
|
||||
## 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
|
||||
- House Cleaning
|
||||
- Eco mode
|
||||
- Extra care navigation
|
||||
- Nogo lines
|
||||
- Zone cleaning <sup>[1](#d7)</sup><sup>, </sup><sup>[2](#change-room)</sup>
|
||||
- Spot cleaning
|
||||
- Individual spot size <sup>[1](#d7)</sup><sup>, </sup><sup>[3](#eve)</sup>
|
||||
- Clean twice <sup>[3](#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
|
||||
|
||||
\* The robot needs to clean for some seconds before he knows where his dock is. After this time the switch to send him home will be automatically available.
|
||||
> <b name="d7">1</b> Only available on the Neato D7.
|
||||
|
||||
**Hint:** To control the robot with your own commands just set up a scene with the name of your choice.
|
||||
> <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.
|
||||
|
||||
# Installation
|
||||
> <b name="eve">3</b> You need a third party app like eve to access these features.
|
||||
|
||||
|
||||
|
||||
## 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/).
|
||||
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.
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
Add the following information to your config file. Change the values for name, email and password.
|
||||
Add the following information to your config file. Change the values for email and password.
|
||||
|
||||
The parameter **refresh** is optional (default off) and adjusts in what interval changes in 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).
|
||||
### Simple
|
||||
|
||||
```json
|
||||
"accessories": [
|
||||
"platforms": [
|
||||
{
|
||||
"accessory": "NeatoVacuumRobot",
|
||||
"name": "YourRobot",
|
||||
"platform": "NeatoVacuumRobot",
|
||||
"email": "YourEmail",
|
||||
"password": "YourPassword",
|
||||
"refresh": "120"
|
||||
"password": "YourPassword"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
# Tested robots
|
||||
### Advanced
|
||||
|
||||
- BotVac Connected (Firmware 2.2.0)
|
||||
Below are explanations for advanced parameters to adjust the plugin to your needs. All parameters are *optional*.
|
||||
|
||||
If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin.
|
||||
**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.
|
||||
|
||||
**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",
|
||||
"hidden": ["dock", "dockstate", "eco", "nogolines", "extracare", "schedule", "find", "spot"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Tested robots
|
||||
|
||||
The plugin is successfully tested with all Neato Connected Robots.
|
||||
|
||||
## 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
|
||||
- [Berkay](https://github.com/btutal) for adding the schema file to use the plugin with homebridge-config-ui-x
|
||||
- [Antoine de Maleprade](https://github.com/az0uz) for adding the zone cleaning feature
|
||||
- [DJay](https://github.com/DJay-X) for testing out tons of new beta versions
|
||||
|
148
config.schema.json
Normal file
148
config.schema.json
Normal file
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"pluginAlias": "NeatoVacuumRobot",
|
||||
"pluginType": "platform",
|
||||
"singular": true,
|
||||
"headerDisplay": "",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"title": "E-Mail",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"language": {
|
||||
"title": "Services Language",
|
||||
"description": "The displayed language of the registered services (and associated Siri commands)",
|
||||
"type": "string",
|
||||
"default": "en",
|
||||
"oneOf": [
|
||||
{
|
||||
"title": "English",
|
||||
"enum": ["en"]
|
||||
},
|
||||
{
|
||||
"title": "German",
|
||||
"enum": ["de"]
|
||||
},
|
||||
{
|
||||
"title": "French",
|
||||
"enum": ["fr"]
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
"prefix": {
|
||||
"title": "Prefix with Robot Name",
|
||||
"description": "Display the name of the robot in front of every service.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"backgroundUpdate": {
|
||||
"title": "Background Update Interval",
|
||||
"description": "Interval for background updates while the robot is not cleaning (in minutes). During cleaning, the robot will automatically update at a faster rate.",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 30
|
||||
},
|
||||
"services": {
|
||||
"type": "array",
|
||||
"title": "Displayed Services",
|
||||
"description": "The services to be made available for Homekit",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"clean",
|
||||
"cleanZone",
|
||||
"cleanSpot",
|
||||
"goToDock",
|
||||
"dockState",
|
||||
"binFull",
|
||||
"eco",
|
||||
"noGoLines",
|
||||
"extraCare",
|
||||
"schedule",
|
||||
"findMe",
|
||||
"battery"
|
||||
],
|
||||
"enumNames": [
|
||||
"Clean",
|
||||
"Clean Zone",
|
||||
"Clean Spot",
|
||||
"Go to Dock",
|
||||
"Docked State",
|
||||
"Bin Full",
|
||||
"Eco Mode",
|
||||
"NoGo Lines",
|
||||
"Extra Care",
|
||||
"Schedule",
|
||||
"Find me",
|
||||
"Battery"
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
"clean",
|
||||
"cleanZone",
|
||||
"goToDock",
|
||||
"dockState",
|
||||
"binFull",
|
||||
"eco",
|
||||
"noGoLines",
|
||||
"extraCare",
|
||||
"schedule",
|
||||
"findMe",
|
||||
"cleanSpot",
|
||||
"battery"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
{
|
||||
"type": "help",
|
||||
"helpvalue": "<h4>Login</h4>"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"helpvalue": "Enter the credentials of your Neato app. If you don't have a neato account yet, register <a target='_blank' href='https://www.neatorobotics.com/create-account/'>here</a>"
|
||||
},
|
||||
{
|
||||
"type": "flex",
|
||||
"flex-flow": "row wrap",
|
||||
"items": [
|
||||
"email",
|
||||
{
|
||||
"key": "password",
|
||||
"type": "password"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"helpvalue": "<h4>Options</h4>"
|
||||
},
|
||||
"backgroundUpdate",
|
||||
"prefix",
|
||||
"language",
|
||||
|
||||
{
|
||||
"type": "fieldset",
|
||||
"title": "Displayed Services",
|
||||
"description": "<i>Services to be displayed in Homekit</i>",
|
||||
"expandable": true,
|
||||
"items": [
|
||||
{
|
||||
"key": "services",
|
||||
"notitle": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
9
homebridge-neato.iml
Normal file
9
homebridge-neato.iml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
314
index.js
314
index.js
@@ -1,314 +0,0 @@
|
||||
"use strict";
|
||||
var inherits = require('util').inherits,
|
||||
debug = require('debug')('homebridge-neato'),
|
||||
botvac = require('node-botvac'),
|
||||
|
||||
Service,
|
||||
Characteristic,
|
||||
vacuumRobotCleanService,
|
||||
vacuumRobotGoToDockService,
|
||||
vacuumRobotDockStateService,
|
||||
vacuumRobotEcoService,
|
||||
vacuumRobotScheduleService,
|
||||
vacuumRobotBatteryService,
|
||||
refresh,
|
||||
timer
|
||||
|
||||
module.exports = function (homebridge) {
|
||||
Service = homebridge.hap.Service;
|
||||
Characteristic = homebridge.hap.Characteristic;
|
||||
homebridge.registerAccessory("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobot);
|
||||
}
|
||||
|
||||
function NeatoVacuumRobot(log, config) {
|
||||
this.log = log;
|
||||
this.name = config['name'];
|
||||
this.serial = "1-3-3-7";
|
||||
this.email = config['email'];
|
||||
this.password = config['password'];
|
||||
|
||||
// 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 = (0 < this.refresh < 60) ? 60 : this.refresh;
|
||||
|
||||
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.lastUpdate = null;
|
||||
this.robot = null;
|
||||
this.getStateTimer();
|
||||
}
|
||||
|
||||
NeatoVacuumRobot.prototype = {
|
||||
identify: function (callback) {
|
||||
this.log("Identify requested");
|
||||
callback();
|
||||
},
|
||||
|
||||
getServices: function () {
|
||||
this.informationService = new Service.AccessoryInformation();
|
||||
this.informationService
|
||||
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
|
||||
.setCharacteristic(Characteristic.Model, this.name)
|
||||
.setCharacteristic(Characteristic.SerialNumber, this.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));
|
||||
|
||||
return [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotGoToDockService, this.vacuumRobotDockStateService, this.vacuumRobotEcoService,
|
||||
this.vacuumRobotScheduleService, this.vacuumRobotBatteryService];
|
||||
},
|
||||
|
||||
setClean: function (on, callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
if (on) {
|
||||
if (that.robot.canResume || that.robot.canStart) {
|
||||
// wait for robot to start and then disable the old timer and enable it again (with a shorter interval)
|
||||
setTimeout(function() {
|
||||
clearTimeout(that.timer);
|
||||
that.getStateTimer();
|
||||
}, 10000);
|
||||
|
||||
if (that.robot.canResume) {
|
||||
debug("Resume cleaning");
|
||||
that.robot.resumeCleaning(callback);
|
||||
}
|
||||
else {
|
||||
debug("Start cleaning");
|
||||
that.robot.startCleaning(that.robot.eco, callback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("Already cleaning");
|
||||
callback();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (that.robot.canPause) {
|
||||
debug("Pause cleaning");
|
||||
that.robot.pauseCleaning(callback);
|
||||
}
|
||||
else {
|
||||
debug("Already stopped");
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setGoToDock: function (on, callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
if (on) {
|
||||
if (that.robot.canPause) {
|
||||
debug("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("Go to dock");
|
||||
that.robot.sendToBase(callback);
|
||||
}
|
||||
else {
|
||||
debug("Can't go to dock at the moment");
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setEco: function (on, callback) {
|
||||
debug(on ? "Enable eco mode" : "Disable eco mode");
|
||||
this.robot.eco = on;
|
||||
callback();
|
||||
},
|
||||
|
||||
setSchedule: function (on, callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
if (on) {
|
||||
debug("Enable schedule");
|
||||
that.robot.enableSchedule(callback);
|
||||
}
|
||||
else {
|
||||
debug("Disable schedule");
|
||||
that.robot.disableSchedule(callback);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getClean: function(callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
debug("Is cleaning: " + that.robot.canPause);
|
||||
callback(false, that.robot.canPause);
|
||||
});
|
||||
},
|
||||
|
||||
getGoToDock: function(callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
debug("Can go to dock: " + that.robot.dockHasBeenSeen);
|
||||
callback(false, !that.robot.dockHasBeenSeen);
|
||||
});
|
||||
},
|
||||
|
||||
getDock: function(callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
debug("Is docked: " + that.robot.isDocked);
|
||||
debug(that.robot);
|
||||
callback(false, that.robot.isDocked);
|
||||
});
|
||||
},
|
||||
|
||||
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.getStateAndRobot(function (error, result) {
|
||||
debug("Schedule: " + that.robot.isScheduleEnabled);
|
||||
callback(false, that.robot.isScheduleEnabled);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
getBatteryLevel: function(callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
debug("Battery: " + that.robot.charge);
|
||||
callback(false, that.robot.charge);
|
||||
});
|
||||
},
|
||||
|
||||
getBatteryChargingState: function(callback) {
|
||||
let that = this;
|
||||
this.getStateAndRobot(function (error, result) {
|
||||
debug("Is charging: " + that.robot.isCharging);
|
||||
callback(false, that.robot.isCharging);
|
||||
});
|
||||
},
|
||||
|
||||
getStateAndRobot: function(callback) {
|
||||
let that = this;
|
||||
if (this.robot === null)
|
||||
{
|
||||
this.getRobot(function (error, result) {
|
||||
that.getState(callback);
|
||||
});
|
||||
}
|
||||
else {
|
||||
that.getState(callback);
|
||||
}
|
||||
},
|
||||
|
||||
getState: function(callback) {
|
||||
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) {
|
||||
debug("Get state (cached)");
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
debug("Get state (new)");
|
||||
let that = this;
|
||||
this.robot.getState(function (error, result) {
|
||||
that.lastUpdate = new Date();
|
||||
callback();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getStateTimer: function() {
|
||||
debug("Timer called");
|
||||
let that = this;
|
||||
this.getStateAndRobot(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);
|
||||
}
|
||||
|
||||
if (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value !== !that.robot.dockHasBeenSeen) {
|
||||
that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, !that.robot.dockHasBeenSeen);
|
||||
}
|
||||
|
||||
if (that.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== that.robot.isScheduleEnabled) {
|
||||
that.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, that.robot.isScheduleEnabled);
|
||||
}
|
||||
|
||||
// no commands here, values can be updated without problems
|
||||
that.vacuumRobotDockStateService.setCharacteristic(Characteristic.OccupancyDetected, that.robot.isDocked);
|
||||
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("Short timer set: 30s");
|
||||
that.timer = setTimeout(that.getStateTimer.bind(that), 30 * 1000);
|
||||
}
|
||||
else if (that.refresh != 0) {
|
||||
debug("Long timer set: " + that.refresh + "s");
|
||||
that.timer = setTimeout(that.getStateTimer.bind(that), that.refresh * 1000);
|
||||
}
|
||||
else {
|
||||
debug("Disabled timer");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getRobot: function(callback) {
|
||||
debug("Get robot");
|
||||
let client = new botvac.Client();
|
||||
let that = this;
|
||||
client.authorize(this.email, this.password, false, function (error) {
|
||||
if (error) {
|
||||
that.log(error);
|
||||
}
|
||||
else {
|
||||
client.getRobots(function (error, robots) {
|
||||
if (error) {
|
||||
that.log(error);
|
||||
}
|
||||
else {
|
||||
that.robot = robots[0];
|
||||
that.log("Found robot: " + that.robot.name);
|
||||
debug(that.robot);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
12
nodemon.json
Normal file
12
nodemon.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"watch": [
|
||||
"src"
|
||||
],
|
||||
"ext": "ts",
|
||||
"ignore": [],
|
||||
"exec": "tsc && homebridge -I -D",
|
||||
"signal": "SIGTERM",
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--trace-warnings"
|
||||
}
|
||||
}
|
2841
package-lock.json
generated
Normal file
2841
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "homebridge-neato",
|
||||
"version": "0.3.0",
|
||||
"displayName": "Homebridge Neato",
|
||||
"version": "1.0.0-beta.5",
|
||||
"description": "A Neato vacuum robot plugin for homebridge.",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -9,18 +10,65 @@
|
||||
"botvac"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.12.0",
|
||||
"homebridge": ">=0.2.0"
|
||||
"node": ">=10.17.0",
|
||||
"homebridge": ">=1.3.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint src/**.ts --max-warnings=0",
|
||||
"watch": "npm run build && npm link && nodemon",
|
||||
"build": "rimraf ./dist && tsc"
|
||||
},
|
||||
"author": {
|
||||
"name": "Arne Blumentritt"
|
||||
"name": "Arne Blumentritt",
|
||||
"url2": "https://github.com/naofireblade"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "ghulands",
|
||||
"url": "https://github.com/ghulands"
|
||||
},
|
||||
{
|
||||
"name": "Berkay",
|
||||
"url": "https://github.com/btutal"
|
||||
},
|
||||
{
|
||||
"name": "Antoine de Maleprade",
|
||||
"url": "https://github.com/az0uz"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/naofireblade/homebridge-neato.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/naofireblade/homebridge-neato/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-botvac": "^0.1.4",
|
||||
"debug": "^2.2.0"
|
||||
}
|
||||
"colors": "^1.4.0",
|
||||
"debug": "^4.1.1",
|
||||
"node-botvac": "^0.4.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.31",
|
||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||
"@typescript-eslint/parser": "^4.16.1",
|
||||
"eslint": "^7.21.0",
|
||||
"homebridge": "^1.3.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://buymeacoffee.com/naofireblade"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/ArneBlumentritt"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
707
src/accessories/neatoVacuumRobot.ts
Normal file
707
src/accessories/neatoVacuumRobot.ts
Normal file
@@ -0,0 +1,707 @@
|
||||
import {CharacteristicValue, Logger, PlatformAccessory, PlatformAccessoryEvent, PlatformConfig, Service, WithUUID, CharacteristicGetHandler, CharacteristicSetHandler, Characteristic} from 'homebridge';
|
||||
import {HomebridgeNeatoPlatform} from '../homebridgeNeatoPlatform';
|
||||
import spotRepeat from '../characteristics/spotRepeat';
|
||||
import spotWidth from '../characteristics/spotWidth';
|
||||
import spotHeight from '../characteristics/spotHeight';
|
||||
import {Options} from '../models/options';
|
||||
import { RobotService, CleanType } from '../models/services';
|
||||
import { ALL_SERVICES, BACKGROUND_INTERVAL, LOCALE, PREFIX } from '../defaults';
|
||||
import { availableLocales, localize } from '../localization';
|
||||
import { CharacteristicHandler } from '../characteristics/characteristicHandler';
|
||||
|
||||
/**
|
||||
* Platform Accessory
|
||||
* An instance of this class is created for each accessory your platform registers
|
||||
* Each accessory may expose multiple services of different service types.
|
||||
*/
|
||||
export class NeatoVacuumRobotAccessory
|
||||
{
|
||||
// Homebridge
|
||||
private log: Logger;
|
||||
private readonly batteryService?: Service;
|
||||
private readonly cleanService?: Service;
|
||||
private readonly findMeService?: Service;
|
||||
private readonly goToDockService?: Service;
|
||||
private readonly dockStateService?: Service;
|
||||
private readonly binFullService?: Service;
|
||||
private readonly ecoService?: Service;
|
||||
private readonly noGoLinesService?: Service;
|
||||
private readonly extraCareService?: Service;
|
||||
private readonly scheduleService?: Service;
|
||||
private readonly spotCleanService?: Service;
|
||||
private spotPlusFeatures: boolean;
|
||||
|
||||
// Context
|
||||
private robot: any;
|
||||
private readonly options: Options;
|
||||
|
||||
// Config
|
||||
private readonly backgroundUpdateInterval: number;
|
||||
private readonly locale: availableLocales;
|
||||
private readonly prefix: boolean;
|
||||
private readonly availableServices: Set<RobotService>;
|
||||
|
||||
// Transient
|
||||
private isSpotCleaning: boolean;
|
||||
private timer: any;
|
||||
|
||||
/**
|
||||
* These are just used to create a working example
|
||||
* You should implement your own code to track the state of your accessory
|
||||
*/
|
||||
|
||||
constructor(
|
||||
private readonly platform: HomebridgeNeatoPlatform,
|
||||
private readonly accessory: PlatformAccessory,
|
||||
private readonly config: PlatformConfig)
|
||||
{
|
||||
this.log = platform.log;
|
||||
|
||||
this.robot = accessory.context.robot;
|
||||
this.options = accessory.context.options || new Options();
|
||||
this.spotPlusFeatures = false;
|
||||
|
||||
this.backgroundUpdateInterval = NeatoVacuumRobotAccessory.parseBackgroundUpdateInterval(this.config['backgroundUpdate']);
|
||||
this.prefix = this.config['prefix'] || PREFIX;
|
||||
this.locale = this.config['language'] || LOCALE;
|
||||
this.availableServices = new Set(this.config['services']) || ALL_SERVICES;
|
||||
|
||||
this.isSpotCleaning = false;
|
||||
|
||||
// Information
|
||||
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
||||
.setCharacteristic(this.platform.Characteristic.Manufacturer, "Neato Robotics")
|
||||
.setCharacteristic(this.platform.Characteristic.Model, this.robot.meta.modelName)
|
||||
.setCharacteristic(this.platform.Characteristic.SerialNumber, this.robot._serial)
|
||||
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.robot.meta.firmware)
|
||||
.setCharacteristic(this.platform.Characteristic.Name, this.robot.name);
|
||||
|
||||
// Identify
|
||||
this.accessory.on(PlatformAccessoryEvent.IDENTIFY, () => {
|
||||
this.robot.findMe();
|
||||
|
||||
this.robot.getState((error, result) => {
|
||||
this.log.info("[" + this.robot.name + "] Identified");
|
||||
if (error)
|
||||
{
|
||||
this.debug(DebugType.INFO, JSON.stringify("Error: " + error));
|
||||
}
|
||||
this.debug(DebugType.INFO, "Status: " + JSON.stringify(result));
|
||||
this.debug(DebugType.INFO,
|
||||
"Config: Background Update Interval: " + this.backgroundUpdateInterval + ", Prefix: " + this.prefix + ", Enabled services: " + JSON.stringify(this.availableServices));
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
this.getClean,
|
||||
this.setClean,
|
||||
this.getSpotClean,
|
||||
this.setSpotClean,
|
||||
this.getGoToDock,
|
||||
this.setGoToDock,
|
||||
this.getDocked,
|
||||
this.getBinFull,
|
||||
this.getFindMe,
|
||||
this.setFindMe,
|
||||
this.getSchedule,
|
||||
this.setSchedule,
|
||||
this.getEco,
|
||||
this.setEco,
|
||||
this.getNoGoLines,
|
||||
this.setNoGoLines,
|
||||
this.getExtraCare,
|
||||
this.setExtraCare
|
||||
].forEach((f)=>f.bind(this))
|
||||
|
||||
// Services
|
||||
this.cleanService = this.registerService(RobotService.CLEAN, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getClean,
|
||||
setCharacteristicHandler: this.setClean
|
||||
}]);
|
||||
this.spotCleanService = this.registerService(RobotService.CLEAN_SPOT, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getSpotClean,
|
||||
setCharacteristicHandler: this.setSpotClean
|
||||
}]);
|
||||
this.goToDockService = this.registerService(RobotService.GO_TO_DOCK, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getGoToDock,
|
||||
setCharacteristicHandler: this.setGoToDock
|
||||
}]);
|
||||
this.dockStateService = this.registerService(RobotService.DOCKED, this.platform.Service.OccupancySensor, [{
|
||||
characteristic: this.platform.Characteristic.OccupancyDetected.OccupancyDetected,
|
||||
getCharacteristicHandler: this.getDocked,
|
||||
}]);
|
||||
this.binFullService = this.registerService(RobotService.BIN_FULL, this.platform.Service.OccupancySensor, [{
|
||||
characteristic: this.platform.Characteristic.OccupancyDetected.OccupancyDetected,
|
||||
getCharacteristicHandler: this.getBinFull,
|
||||
}]);
|
||||
this.findMeService = this.registerService(RobotService.FIND_ME, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getFindMe,
|
||||
setCharacteristicHandler: this.setFindMe
|
||||
}]);
|
||||
this.scheduleService = this.registerService(RobotService.SCHEDULE, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getSchedule,
|
||||
setCharacteristicHandler: this.setSchedule
|
||||
}]);
|
||||
this.ecoService = this.registerService(RobotService.ECO, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getEco,
|
||||
setCharacteristicHandler: this.setEco
|
||||
}]);
|
||||
this.noGoLinesService = this.registerService(RobotService.NOGO_LINES, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getNoGoLines,
|
||||
setCharacteristicHandler: this.setNoGoLines
|
||||
}]);
|
||||
this.extraCareService = this.registerService(RobotService.EXTRA_CARE, this.platform.Service.Switch, [{
|
||||
characteristic: this.platform.Characteristic.On,
|
||||
getCharacteristicHandler: this.getExtraCare,
|
||||
setCharacteristicHandler: this.setExtraCare
|
||||
}]);
|
||||
this.batteryService = this.registerService(RobotService.BATTERY, this.platform.Service.Battery);
|
||||
|
||||
// This should be the main switch if the accessory is grouped in homekit
|
||||
if (this.cleanService)
|
||||
{
|
||||
this.cleanService.setPrimaryService(true);
|
||||
}
|
||||
|
||||
// Start background update
|
||||
this.updateRobotPeriodically().then(() => {
|
||||
// Add special characteristics to set spot cleaning options
|
||||
this.spotPlusFeatures = ((typeof this.robot.availableServices.spotCleaning !== 'undefined') && this.robot.availableServices.spotCleaning.includes("basic"));
|
||||
this.addSpotCleanCharacteristics();
|
||||
|
||||
// Save/Load options
|
||||
if (!accessory.context.options)
|
||||
{
|
||||
this.options.eco = this.robot.eco;
|
||||
this.options.noGoLines = this.robot.noGoLines;
|
||||
this.options.extraCare = this.robot.navigationMode == 2;
|
||||
this.debug(DebugType.INFO, "Options initially set to eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + this.options.extraCare);
|
||||
accessory.context.options = this.options;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.debug(DebugType.INFO, "Options loaded from cache eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + this.options.extraCare);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addSpotCleanCharacteristics()
|
||||
{
|
||||
// Only add characteristics of service is available ond characteristics are not added yet
|
||||
if (this.spotCleanService != null && !this.options.spotCharacteristics)
|
||||
{
|
||||
this.spotCleanService.addCharacteristic(spotRepeat(this.platform.Characteristic))
|
||||
.onGet(this.getSpotRepeat.bind(this))
|
||||
.onSet(this.setSpotRepeat.bind(this));
|
||||
|
||||
// Add these only if the robot supports them
|
||||
if (this.spotPlusFeatures)
|
||||
{
|
||||
this.spotCleanService.addCharacteristic(spotWidth(this.platform.Characteristic))
|
||||
.onGet(this.getSpotWidth.bind(this))
|
||||
.onSet(this.setSpotWidth.bind(this));
|
||||
this.spotCleanService.addCharacteristic(spotHeight(this.platform.Characteristic))
|
||||
.onGet(this.getSpotHeight.bind(this))
|
||||
.onSet(this.setSpotHeight.bind(this));
|
||||
}
|
||||
this.options.spotCharacteristics = true;
|
||||
}
|
||||
else if (this.spotCleanService == null)
|
||||
{
|
||||
this.options.spotCharacteristics = false;
|
||||
}
|
||||
}
|
||||
|
||||
private registerService(
|
||||
serviceName: RobotService,
|
||||
serviceType: WithUUID<typeof Service>,
|
||||
characteristicHandlers: CharacteristicHandler[] = []
|
||||
) : Service | undefined
|
||||
{
|
||||
const displayName = (this.prefix ? (this.robot.name + " ") : "") + localize(serviceName, this.locale);
|
||||
|
||||
// query existing service by type and subtype
|
||||
const existingService = this.accessory.getServiceById(serviceType, serviceName)
|
||||
|
||||
if (this.availableServices.has(serviceName))
|
||||
{
|
||||
var service : Service
|
||||
if (existingService && existingService.displayName === displayName) {
|
||||
service = existingService
|
||||
} else {
|
||||
if (existingService) {this.accessory.removeService(existingService);} // delete to reset display name in case of locale or prefix change
|
||||
service = this.accessory.addService(serviceType, displayName, serviceName);
|
||||
}
|
||||
characteristicHandlers.forEach(ch => {
|
||||
var char = service.getCharacteristic(ch.characteristic)
|
||||
if (ch.getCharacteristicHandler) {char.onGet(ch.getCharacteristicHandler)}
|
||||
if (ch.setCharacteristicHandler) {char.onSet(ch.setCharacteristicHandler)}
|
||||
});
|
||||
return service
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingService)
|
||||
{
|
||||
this.accessory.removeService(existingService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static parseBackgroundUpdateInterval(configValue: any)
|
||||
{
|
||||
// Parse as number
|
||||
let backgroundUpdateInterval = parseInt(configValue) || BACKGROUND_INTERVAL;
|
||||
|
||||
// must be integer and positive
|
||||
backgroundUpdateInterval = ((backgroundUpdateInterval % 1) !== 0 || backgroundUpdateInterval < 0) ? BACKGROUND_INTERVAL : backgroundUpdateInterval;
|
||||
|
||||
return backgroundUpdateInterval;
|
||||
}
|
||||
|
||||
async getClean(): Promise<CharacteristicValue>
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
return this.robot.canPause && !this.isSpotCleaning;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot get cleaning status: " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async setClean(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set CLEAN HOUSE: " + on);
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
|
||||
// Start
|
||||
if (on)
|
||||
{
|
||||
// Resume cleaning
|
||||
if (this.robot.canResume)
|
||||
{
|
||||
this.debug(DebugType.ACTION, "Resume cleaning");
|
||||
await this.robot.resumeCleaning();
|
||||
}
|
||||
// Start cleaning
|
||||
else if (this.robot.canStart)
|
||||
{
|
||||
await this.clean(CleanType.ALL)
|
||||
}
|
||||
// Cannot start
|
||||
else
|
||||
{
|
||||
this.debug(DebugType.INFO, "Cannot start, maybe already cleaning (expected)");
|
||||
}
|
||||
}
|
||||
// Stop
|
||||
else
|
||||
{
|
||||
if (this.robot.canPause)
|
||||
{
|
||||
this.debug(DebugType.ACTION, "Pause cleaning");
|
||||
await this.robot.pauseCleaning();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.debug(DebugType.INFO, "Already paused");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Error setting cleaning to: " + on + ". " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async getSpotClean(): Promise<CharacteristicValue>
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
return this.robot.canPause && this.isSpotCleaning;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot get spot cleaning status: " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async setSpotClean(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set SPOT CLEAN: " + on);
|
||||
try
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
await this.clean(CleanType.SPOT)
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO stop/pause
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Error setting spot cleaning to: " + on + ". " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
getGoToDock()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
async setGoToDock(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set GO TO DOCK: " + on);
|
||||
if (on)
|
||||
{
|
||||
await this.updateRobot();
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.goToDockService)
|
||||
{
|
||||
this.goToDockService.updateCharacteristic(this.platform.Characteristic.On, false);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
try
|
||||
{
|
||||
if (this.robot.canPause)
|
||||
{
|
||||
this.debug(DebugType.ACTION, "Pause cleaning to go to dock");
|
||||
await this.robot.pauseCleaning();
|
||||
setTimeout(async () => {
|
||||
await this.robot.sendToBase();
|
||||
}, 1000);
|
||||
}
|
||||
else if (this.robot.canGoToBase)
|
||||
{
|
||||
this.debug(DebugType.ACTION, "Going to dock");
|
||||
await this.robot.sendToBase();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.warn("[" + this.robot.name + "] Can't go to dock at the moment");
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Error setting go to dock to: " + on + ". " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getDocked(): Promise<CharacteristicValue>
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
return this.robot.isDocked;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot get docked status: " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async getBinFull(): Promise<CharacteristicValue>
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
return this.robot.isBinFull;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot get bin full status: " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async getSchedule(): Promise<CharacteristicValue>
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.updateRobot();
|
||||
return this.robot.isScheduleEnabled;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot get schedule status: " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
async setSchedule(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set SCHEDULE: " + on);
|
||||
try
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
await this.robot.enableSchedule();
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.robot.disableSchedule();
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Error setting schedule to: " + on + ". " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
getEco()
|
||||
{
|
||||
return this.options.eco;
|
||||
}
|
||||
|
||||
setEco(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set ECO: " + on);
|
||||
this.options.eco = <boolean>on;
|
||||
}
|
||||
|
||||
getExtraCare()
|
||||
{
|
||||
return this.options.extraCare;
|
||||
}
|
||||
|
||||
setExtraCare(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set EXTRA CARE: " + on);
|
||||
this.options.extraCare = <boolean>on;
|
||||
}
|
||||
|
||||
getNoGoLines()
|
||||
{
|
||||
return this.options.noGoLines;
|
||||
}
|
||||
|
||||
setNoGoLines(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set NOGO LINES: " + on);
|
||||
this.options.noGoLines = <boolean>on;
|
||||
}
|
||||
|
||||
getSpotRepeat()
|
||||
{
|
||||
return this.options.spotRepeat;
|
||||
}
|
||||
|
||||
setSpotRepeat(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set SPOT REPEAT: " + on);
|
||||
this.options.spotRepeat = <boolean>on;
|
||||
}
|
||||
|
||||
getSpotWidth()
|
||||
{
|
||||
return this.options.spotWidth;
|
||||
}
|
||||
|
||||
setSpotWidth(length: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set SPOT WIDTH: " + length + " cm");
|
||||
this.options.spotWidth = <number>length;
|
||||
}
|
||||
|
||||
getSpotHeight()
|
||||
{
|
||||
return this.options.spotHeight;
|
||||
}
|
||||
|
||||
setSpotHeight(length: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set SPOT HEIGHT: " + length + " cm");
|
||||
this.options.spotHeight = <number>length;
|
||||
}
|
||||
|
||||
getFindMe()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
async setFindMe(on: CharacteristicValue)
|
||||
{
|
||||
this.debug(DebugType.STATUS, "Set FIND ME: " + on);
|
||||
if (on)
|
||||
{
|
||||
this.debug(DebugType.ACTION, "Find me");
|
||||
setTimeout(() => {
|
||||
if (this.findMeService)
|
||||
{
|
||||
this.findMeService.updateCharacteristic(this.platform.Characteristic.On, false);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
try
|
||||
{
|
||||
await this.robot.findMe();
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error(this.robot.name + " ## Cannot start find me. " + error);
|
||||
throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async clean(cleanType: CleanType)
|
||||
{
|
||||
// Enable shorter background update while cleaning
|
||||
setTimeout(() => {
|
||||
this.updateRobotPeriodically();
|
||||
}, 2 * 60 * 1000);
|
||||
|
||||
this.log.info(
|
||||
"[" + this.robot.name + "] > Start cleaning with options type: " + CleanType[cleanType] + ", eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: "
|
||||
+ this.options.extraCare);
|
||||
|
||||
try
|
||||
{
|
||||
switch (cleanType)
|
||||
{
|
||||
case CleanType.ALL:
|
||||
await this.robot.startCleaning(this.options.eco, this.options.extraCare ? 2 : 1, this.options.noGoLines);
|
||||
break;
|
||||
case CleanType.SPOT:
|
||||
await this.robot.startSpotCleaning(this.options.eco, this.options.spotWidth, this.options.spotHeight, this.options.spotRepeat, this.options.extraCare ? 2 : 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot start cleaning. " + error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateRobot()
|
||||
{
|
||||
// Data is outdated
|
||||
if (typeof (this.robot.lastUpdate) === 'undefined' || new Date().getTime() - this.robot.lastUpdate > 2000)
|
||||
{
|
||||
this.robot.lastUpdate = new Date().getTime();
|
||||
try
|
||||
{
|
||||
this.robot.getState((error, result) => {
|
||||
this.isSpotCleaning = result != null && result.action == 2;
|
||||
|
||||
// Battery
|
||||
this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, this.robot.charge);
|
||||
this.batteryService?.updateCharacteristic(this.platform.Characteristic.ChargingState, this.robot.isCharging);
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Cannot update robot " + this.robot.name + ". Check if robot is online. " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateRobotPeriodically()
|
||||
{
|
||||
this.debug(DebugType.INFO, "Performing background update")
|
||||
|
||||
await this.updateRobot()
|
||||
await this.updateCharacteristics();
|
||||
|
||||
// Clear any other overlapping timers for this robot
|
||||
clearTimeout(this.timer);
|
||||
|
||||
// Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available
|
||||
// this.robot.mainAccessory.updated();
|
||||
// this.robot.roomAccessories.forEach(accessory => {
|
||||
// accessory.updated();
|
||||
// });
|
||||
|
||||
// Periodic refresh interval set in config
|
||||
let interval;
|
||||
if (this.robot.canPause)
|
||||
{
|
||||
interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
interval = this.backgroundUpdateInterval;
|
||||
}
|
||||
|
||||
this.debug(DebugType.INFO, "Background update done. Next update in " + interval + " minute" + (interval == 1 ? "" : "s") + ((this.robot.canPause) ? ", robot is currently cleaning." : "."));
|
||||
this.timer = setTimeout(this.updateRobotPeriodically.bind(this), interval * 60 * 1000);
|
||||
}
|
||||
|
||||
async updateCharacteristics()
|
||||
{
|
||||
if (this.cleanService)
|
||||
{
|
||||
this.cleanService.updateCharacteristic(this.platform.Characteristic.On, await this.getClean());
|
||||
}
|
||||
if (this.spotCleanService)
|
||||
{
|
||||
this.spotCleanService.updateCharacteristic(this.platform.Characteristic.On, await this.getSpotClean());
|
||||
}
|
||||
if (this.goToDockService)
|
||||
{
|
||||
this.goToDockService.updateCharacteristic(this.platform.Characteristic.On, await this.getGoToDock());
|
||||
}
|
||||
if (this.dockStateService)
|
||||
{
|
||||
this.dockStateService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getDocked());
|
||||
}
|
||||
if (this.binFullService)
|
||||
{
|
||||
this.binFullService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getBinFull());
|
||||
}
|
||||
if (this.scheduleService)
|
||||
{
|
||||
this.scheduleService.updateCharacteristic(this.platform.Characteristic.On, await this.getSchedule());
|
||||
}
|
||||
}
|
||||
|
||||
private debug(debugType: DebugType, message: String)
|
||||
{
|
||||
switch (debugType)
|
||||
{
|
||||
case DebugType.ACTION:
|
||||
this.log.debug("[" + this.robot.name + "] > " + message);
|
||||
break;
|
||||
case DebugType.STATUS:
|
||||
this.log.debug("[" + this.robot.name + "] " + message);
|
||||
break;
|
||||
case DebugType.INFO:
|
||||
this.log.debug("[" + this.robot.name + "] " + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum DebugType
|
||||
{
|
||||
ACTION,
|
||||
STATUS,
|
||||
INFO
|
||||
}
|
37
src/accessories/room.ts
Normal file
37
src/accessories/room.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// import {CharacteristicValue, Logger, PlatformAccessory, PlatformConfig, Service} from 'homebridge';
|
||||
// import {HomebridgeNeatoPlatform} from '../homebridgeNeatoPlatform';
|
||||
//
|
||||
// const debug = require('debug')('my-app:my-module');
|
||||
//
|
||||
// /**
|
||||
// * Platform Accessory
|
||||
// * An instance of this class is created for each accessory your platform registers
|
||||
// * Each accessory may expose multiple services of different service types.
|
||||
// */
|
||||
// export class Room
|
||||
// {
|
||||
//
|
||||
// private robot: any;
|
||||
// private log: Logger;
|
||||
// private readonly refresh: any;
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * These are just used to create a working example
|
||||
// * You should implement your own code to track the state of your accessory
|
||||
// */
|
||||
//
|
||||
// constructor(
|
||||
// private readonly platform: HomebridgeNeatoPlatform,
|
||||
// private readonly accessory: PlatformAccessory,
|
||||
// private readonly isNew: Boolean,
|
||||
// private readonly config: PlatformConfig)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// async setCleanRoom()
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// }
|
7
src/characteristics/characteristicHandler.ts
Normal file
7
src/characteristics/characteristicHandler.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Characteristic, CharacteristicGetHandler, CharacteristicSetHandler, WithUUID } from "homebridge";
|
||||
|
||||
export declare interface CharacteristicHandler{
|
||||
characteristic: WithUUID<new () => Characteristic>
|
||||
getCharacteristicHandler?: CharacteristicGetHandler,
|
||||
setCharacteristicHandler?: CharacteristicSetHandler
|
||||
}
|
19
src/characteristics/spotHeight.ts
Normal file
19
src/characteristics/spotHeight.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Characteristic, WithUUID } from 'homebridge';
|
||||
import { Formats, Perms } from 'homebridge';
|
||||
|
||||
export default function spotHeight(CustomCharacteristic: typeof Characteristic): WithUUID<new () => Characteristic> {
|
||||
return class SpotHeight extends CustomCharacteristic {
|
||||
static readonly UUID = 'CA282DB2-62BF-4325-A1BE-F8BB5478781A';
|
||||
|
||||
constructor() {
|
||||
super('Spot ↕', SpotHeight.UUID, {
|
||||
format: Formats.INT,
|
||||
unit: 'cm',
|
||||
maxValue: 400,
|
||||
minValue: 100,
|
||||
minStep: 50,
|
||||
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
15
src/characteristics/spotRepeat.ts
Normal file
15
src/characteristics/spotRepeat.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Characteristic, WithUUID } from 'homebridge';
|
||||
import { Formats, Perms } from 'homebridge';
|
||||
|
||||
export default function spotRepeat(CustomCharacteristic: typeof Characteristic): WithUUID<new () => Characteristic> {
|
||||
return class SpotRepeat extends CustomCharacteristic {
|
||||
static readonly UUID = '1E79C603-63B8-4E6A-9CE1-D31D67981831';
|
||||
|
||||
constructor() {
|
||||
super('Spot 2x', SpotRepeat.UUID, {
|
||||
format: Formats.BOOL,
|
||||
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
19
src/characteristics/spotWidth.ts
Normal file
19
src/characteristics/spotWidth.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Characteristic, WithUUID } from 'homebridge';
|
||||
import { Formats, Perms } from 'homebridge';
|
||||
|
||||
export default function spotWidth(CustomCharacteristic: typeof Characteristic): WithUUID<new () => Characteristic> {
|
||||
return class SpotWidth extends CustomCharacteristic {
|
||||
static readonly UUID = 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E';
|
||||
|
||||
constructor() {
|
||||
super('Spot ↔', SpotWidth.UUID, {
|
||||
format: Formats.INT,
|
||||
unit: 'cm',
|
||||
maxValue: 400,
|
||||
minValue: 100,
|
||||
minStep: 50,
|
||||
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
6
src/defaults.ts
Normal file
6
src/defaults.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { RobotService } from "./models/services";
|
||||
|
||||
export const BACKGROUND_INTERVAL = 30;
|
||||
export const PREFIX = false;
|
||||
export const ALL_SERVICES = new Set(Object.values(RobotService));
|
||||
export const LOCALE = "en"
|
219
src/homebridgeNeatoPlatform.ts
Normal file
219
src/homebridgeNeatoPlatform.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import {API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service} from "homebridge";
|
||||
import NeatoApi from "node-botvac";
|
||||
import {PLATFORM_NAME, PLUGIN_NAME} from "./settings";
|
||||
import {NeatoVacuumRobotAccessory} from "./accessories/NeatoVacuumRobot";
|
||||
|
||||
/**
|
||||
* HomebridgePlatform
|
||||
* This class is the main constructor for your plugin, this is where you should
|
||||
* parse the user config and discover/register accessories with Homebridge.
|
||||
*/
|
||||
export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin
|
||||
{
|
||||
public readonly Service: typeof Service = this.api.hap.Service;
|
||||
public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
|
||||
|
||||
// this is used to track restored cached accessories
|
||||
public readonly cachedRobotAccessories: PlatformAccessory[] = [];
|
||||
|
||||
constructor(
|
||||
public readonly log: Logger,
|
||||
public readonly config: PlatformConfig,
|
||||
public readonly api: API)
|
||||
{
|
||||
this.api.on("didFinishLaunching", () => {
|
||||
this.discoverRobots();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
||||
* It should be used to setup event handlers for characteristics and update respective values.
|
||||
*/
|
||||
configureAccessory(accessory: PlatformAccessory)
|
||||
{
|
||||
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
||||
this.cachedRobotAccessories.push(accessory);
|
||||
}
|
||||
|
||||
|
||||
discoverRobots()
|
||||
{
|
||||
const client = new NeatoApi.Client();
|
||||
|
||||
try
|
||||
{
|
||||
// Login
|
||||
client.authorize((this.config)["email"], (this.config)["password"], false, (error) => {
|
||||
if (error)
|
||||
{
|
||||
this.log.error("Cannot connect to neato server. No new robots will be found and existing robots will be unresponsive. Retrying in 5 minutes.");
|
||||
this.log.error("Error: " + error);
|
||||
|
||||
setTimeout(() => {
|
||||
this.discoverRobots();
|
||||
}, 5 * 60 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all robots from account
|
||||
client.getRobots((error, robots) => {
|
||||
if (error)
|
||||
{
|
||||
this.log.error("Successful login but can't list the robots in your neato robots. Retrying in 5 minutes.");
|
||||
this.log.error("Error: " + error);
|
||||
|
||||
setTimeout(() => {
|
||||
this.discoverRobots();
|
||||
}, 5 * 60 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Neato robots in account
|
||||
if (robots.length === 0)
|
||||
{
|
||||
this.log.error("Neato account has no robots. Did you add your robot here: https://neatorobotics.com/my-neato/ ?");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.info("Neato account has " + robots.length + " robot" + (robots.length === 1 ? "" : "s"));
|
||||
}
|
||||
|
||||
// Neato robots in cache
|
||||
this.log.debug("Plugin Cache has " + this.cachedRobotAccessories.length + " robot" + (this.cachedRobotAccessories.length === 1 ? "" : "s"));
|
||||
for (let cachedRobot of this.cachedRobotAccessories)
|
||||
{
|
||||
let accountRobot = robots.find(robot => this.api.hap.uuid.generate(robot._serial) === cachedRobot.UUID);
|
||||
if (accountRobot)
|
||||
{
|
||||
this.log.debug("[" + cachedRobot.displayName + "] Cached robot found in Neato account.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.error("[" + cachedRobot.displayName + "] Cached robot not found in Neato account. Robot will now be removed from homebridge.");
|
||||
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [cachedRobot]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add / Update homebridge accessories with robot information from neato. This must be done for new and existing robots to reflect changes in the name, firmware, pluginconfig etc.
|
||||
for (let robot of robots)
|
||||
{
|
||||
// Check if robot already exists as an accessory
|
||||
const uuid = this.api.hap.uuid.generate(robot._serial);
|
||||
const cachedRobot = this.cachedRobotAccessories.find(accessory => accessory.UUID === uuid);
|
||||
|
||||
if (cachedRobot)
|
||||
{
|
||||
this.log.debug("[" + robot.name + "] Connecting to cached robot and updating information.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.debug("[" + robot.name + "] Connecting to new robot and updating information.");
|
||||
}
|
||||
|
||||
robot.getState((error, state) => {
|
||||
if (error)
|
||||
{
|
||||
this.log.error("[" + robot.name + "] Cannot connect to robot. Is the robot connected to the internet? Retrying in 5 minutes.");
|
||||
this.log.error("Error: " + error);
|
||||
setTimeout(() => {
|
||||
this.discoverRobots();
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
robot.meta = state.meta;
|
||||
robot.availableServices = state.availableServices;
|
||||
|
||||
// Update existing robot accessor
|
||||
if (cachedRobot)
|
||||
{
|
||||
// TODO update maps
|
||||
|
||||
cachedRobot.context.robot = robot;
|
||||
this.api.updatePlatformAccessories([cachedRobot]);
|
||||
new NeatoVacuumRobotAccessory(this, cachedRobot, this.config);
|
||||
this.log.info("[" + robot.name + "] Successfully loaded robot from cache");
|
||||
}
|
||||
// Create new robot accessory
|
||||
else
|
||||
{
|
||||
// TODO get maps
|
||||
|
||||
const newRobot = new this.api.platformAccessory(robot.name, uuid);
|
||||
newRobot.context.robot = robot;
|
||||
new NeatoVacuumRobotAccessory(this, newRobot, this.config);
|
||||
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [newRobot]);
|
||||
this.log.info("[" + robot.name + "] Successfully created as new robot");
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("[" + robot.name + "] Creating accessory failed. Error: " + error);
|
||||
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
// // 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.robotAccessories.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.robotAccessories.push({device: robot, meta: state.meta, availableServices: state.availableServices});
|
||||
// loadedRobots++;
|
||||
// if (loadedRobots === robots.length)
|
||||
// {
|
||||
// callback();
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.log.error("Can't log on to neato cloud. Please check your internet connection and your credentials. Try again later if the neato servers have issues. Error: " + error);
|
||||
}
|
||||
}
|
||||
}
|
12
src/index.ts
Normal file
12
src/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {API} from "homebridge";
|
||||
|
||||
import {PLATFORM_NAME} from "./settings";
|
||||
import {HomebridgeNeatoPlatform} from "./homebridgeNeatoPlatform";
|
||||
|
||||
/**
|
||||
* This method registers the platform with Homebridge
|
||||
*/
|
||||
export = (api: API) =>
|
||||
{
|
||||
api.registerPlatform(PLATFORM_NAME, HomebridgeNeatoPlatform);
|
||||
};
|
57
src/localization.ts
Normal file
57
src/localization.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export enum availableLocales {
|
||||
EN = "en",
|
||||
DE = "de",
|
||||
FR = "fr",
|
||||
}
|
||||
|
||||
const localizationDicts = {
|
||||
'en': {
|
||||
"clean": "Clean",
|
||||
"cleanZone": "Clean Zone",
|
||||
"cleanThe": "Clean the",
|
||||
"goToDock": "Go to Dock",
|
||||
"dockState": "Docked",
|
||||
"binFull": "Bin Full",
|
||||
"eco": "Eco Mode",
|
||||
"noGoLines": "NoGo Lines",
|
||||
"extraCare": "Extra Care",
|
||||
"schedule": "Schedule",
|
||||
"findMe": "Find me",
|
||||
"cleanSpot": "Clean Spot",
|
||||
"battery": "Battery"
|
||||
},
|
||||
'de': {
|
||||
"clean": "Sauge",
|
||||
"cleanZone": "Sauge Zone",
|
||||
"cleanThe": "Sauge",
|
||||
"goToDock": "Zur Basis",
|
||||
"dockState": "In der Basis",
|
||||
"binFull": "Behälter voll",
|
||||
"eco": "Eco Modus",
|
||||
"noGoLines": "NoGo Linien",
|
||||
"extraCare": "Extra Care",
|
||||
"schedule": "Zeitplan",
|
||||
"findMe": "Finde mich",
|
||||
"cleanSpot": "Spot Reinigung",
|
||||
"battery": "Batterie"
|
||||
},
|
||||
'fr': {
|
||||
"clean": "Aspirer",
|
||||
"cleanZone": "Aspirer Zone",
|
||||
"cleanThe": "Aspirer",
|
||||
"goToDock": "Retour à la base",
|
||||
"dockState": "Sur la base",
|
||||
"binFull": "Conteneur plein",
|
||||
"eco": "Eco mode",
|
||||
"noGoLines": "Lignes NoGo",
|
||||
"extraCare": "Extra Care",
|
||||
"schedule": "Planifier",
|
||||
"findMe": "Me retrouver",
|
||||
"cleanSpot": "Nettoyage local",
|
||||
"battery": "Batterie"
|
||||
}
|
||||
}
|
||||
|
||||
export function localize(label: string, locale: availableLocales) : string {
|
||||
return localizationDicts[locale][label] ?? label
|
||||
}
|
21
src/models/options.ts
Normal file
21
src/models/options.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export class Options
|
||||
{
|
||||
public eco: boolean;
|
||||
public extraCare: boolean;
|
||||
public noGoLines: boolean;
|
||||
public spotCharacteristics: boolean;
|
||||
public spotRepeat: boolean;
|
||||
public spotWidth: number;
|
||||
public spotHeight: number;
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.eco = false;
|
||||
this.extraCare = false;
|
||||
this.noGoLines = false;
|
||||
this.spotCharacteristics = false;
|
||||
this.spotRepeat = false;
|
||||
this.spotWidth = 200;
|
||||
this.spotHeight = 200;
|
||||
}
|
||||
}
|
19
src/models/services.ts
Normal file
19
src/models/services.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export enum CleanType {
|
||||
ALL,
|
||||
SPOT,
|
||||
}
|
||||
|
||||
export enum RobotService {
|
||||
CLEAN = "clean",
|
||||
CLEAN_SPOT = "cleanSpot",
|
||||
CLEAN_ZONE = "cleanZone",
|
||||
GO_TO_DOCK = "goToDock",
|
||||
DOCKED = "dockState",
|
||||
BIN_FULL = "binFull",
|
||||
FIND_ME = "findMe",
|
||||
SCHEDULE = "schedule",
|
||||
ECO = "eco",
|
||||
NOGO_LINES = "noGoLines",
|
||||
EXTRA_CARE = "extraCare",
|
||||
BATTERY = "battery",
|
||||
}
|
9
src/settings.ts
Normal file
9
src/settings.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This is the name of the platform that users will use to register the plugin in the Homebridge config.json
|
||||
*/
|
||||
export const PLATFORM_NAME = "NeatoVacuumRobot";
|
||||
|
||||
/**
|
||||
* This must match the name of your plugin as defined the package.json
|
||||
*/
|
||||
export const PLUGIN_NAME = "homebridge-neato";
|
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018", // ~node10
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es2017",
|
||||
"es2018"
|
||||
],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": [
|
||||
"src/"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user