├── .DS_Store
├── .eslintrc
├── .github
├── .DS_Store
├── ISSUE_TEMPLATE
│ ├── add-supported-device.yml
│ ├── bug-report.yaml
│ ├── config.yml
│ ├── feature-request.yaml
│ └── support-request.yaml
└── workflows
│ ├── add-model.js
│ ├── add-supported-device.yml
│ ├── build.yml
│ ├── publish.yml
│ └── typedoc.yml
├── .gitignore
├── .npmignore
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Supported Devices.md
├── config.schema.json
├── docs
├── .nojekyll
├── assets
│ ├── highlight.css
│ ├── main.js
│ ├── search.js
│ └── style.css
├── classes
│ ├── accessories_Accessory.default.html
│ ├── accessories_V1.default.html
│ ├── accessories_V2.default.html
│ ├── accessories_V3.default.html
│ └── platform.iRobotPlatform.html
├── enums
│ └── accessories_Accessory.ActiveIdentifier.html
├── functions
│ └── CustomCharacteristics.default.html
├── index.html
├── modules.html
├── modules
│ ├── CustomCharacteristics.html
│ ├── accessories_Accessory.html
│ ├── accessories_V1.html
│ ├── accessories_V2.html
│ ├── accessories_V3.html
│ ├── index.html
│ ├── platform.html
│ └── settings.html
├── types
│ ├── settings.Config.html
│ ├── settings.Context.html
│ ├── settings.Device.html
│ └── settings.V1Mission.html
└── variables
│ ├── accessories_Accessory.ActiveIdentifierPretty.html
│ ├── settings.PLATFORM_NAME.html
│ └── settings.PLUGIN_NAME.html
├── homebridge-ui
├── public
│ ├── index.html
│ ├── index.ts
│ ├── note.txt
│ └── tsconfig.json
├── server.ts
└── tsconfig.json
├── nodemon.json
├── package-lock.json
├── package.json
├── src (Legacy)
├── V1
│ ├── dorita980.d.ts
│ ├── getRoombas.ts
│ ├── platform.ts
│ ├── platformAccessory.ts
│ └── scripts
│ │ ├── getRoombaCredentials.js
│ │ └── getRoombaIP.js
├── V2
│ ├── RoomManager.ts
│ ├── RoombaController.ts
│ ├── getRoombas.ts
│ ├── platform.ts
│ └── platformAccessory.ts
├── index.ts
└── settings.ts
├── src
├── CustomCharacteristics.ts
├── accessories
│ ├── Accessory.ts
│ ├── V1.ts
│ ├── V2.ts
│ └── V3.ts
├── index.ts
├── platform.ts
└── settings.ts
└── tsconfig.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloomkd46/homebridge-iRobot/ef0173b027004984561cb004642f5fe30758933d/.DS_Store
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:@typescript-eslint/eslint-recommended",
6 | "plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module"
11 | },
12 | "ignorePatterns": [
13 | "dist",
14 | "*.js"
15 | ],
16 | "rules": {
17 | "quotes": [
18 | "warn",
19 | "single"
20 | ],
21 | "indent": [
22 | "warn",
23 | 2,
24 | {
25 | "SwitchCase": 1
26 | }
27 | ],
28 | "semi": [
29 | "off"
30 | ],
31 | "comma-dangle": [
32 | "warn",
33 | "always-multiline"
34 | ],
35 | "dot-notation": "off",
36 | "eqeqeq": "warn",
37 | "curly": [
38 | "warn",
39 | "all"
40 | ],
41 | "brace-style": [
42 | "warn"
43 | ],
44 | "prefer-arrow-callback": [
45 | "warn"
46 | ],
47 | "max-len": [
48 | "warn",
49 | 140
50 | ],
51 | "no-console": [
52 | "warn"
53 | ], // use the provided Homebridge log method instead
54 | "no-non-null-assertion": [
55 | "off"
56 | ],
57 | "comma-spacing": [
58 | "error"
59 | ],
60 | "no-multi-spaces": [
61 | "warn",
62 | {
63 | "ignoreEOLComments": true
64 | }
65 | ],
66 | "no-trailing-spaces": [
67 | "warn"
68 | ],
69 | "lines-between-class-members": [
70 | "warn",
71 | "always",
72 | {
73 | "exceptAfterSingleLine": true
74 | }
75 | ],
76 | "@typescript-eslint/explicit-function-return-type": "off",
77 | "@typescript-eslint/no-non-null-assertion": "off",
78 | "@typescript-eslint/explicit-module-boundary-types": "off",
79 | "@typescript-eslint/semi": [
80 | "warn"
81 | ],
82 | "@typescript-eslint/member-delimiter-style": [
83 | "warn",
84 | {
85 | "singleline": {
86 | "requireLast": true
87 | }
88 | }
89 | ]
90 | }
91 | }
--------------------------------------------------------------------------------
/.github/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloomkd46/homebridge-iRobot/ef0173b027004984561cb004642f5fe30758933d/.github/.DS_Store
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/add-supported-device.yml:
--------------------------------------------------------------------------------
1 | name: Add Supported Device
2 | description: inform me of a supported device not currently known
3 | title: "Add Supported Device: "
4 | labels: update-devices
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this Supported Device Reguest!
10 |
11 | - type: input
12 | attributes:
13 | label: "Roomba Model:"
14 | description: "Without Any +'s"
15 | placeholder: i7
16 | validations:
17 | required: true
18 |
19 | - type: dropdown
20 | id: supported
21 | validations:
22 | required: true
23 | attributes:
24 | label: Did It Work
25 | options:
26 | - "Yes"
27 | - "No"
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: create a report to help us improve
3 | title: "Bug Report: "
4 | labels: [bug]
5 | assignees:
6 | - bloomkd46
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | Thanks for taking the time to fill out this bug report!
12 |
13 | - type: textarea
14 | id: what-happened
15 | attributes:
16 | label: "Describe The Bug:"
17 | placeholder: A clear and concise description of what the bug is.
18 | validations:
19 | required: true
20 |
21 | - type: textarea
22 | id: how-happend
23 | attributes:
24 | label: "To Reproduce:"
25 | placeholder: Steps to reproduce the behavior.
26 | validations:
27 | required: true
28 |
29 | - type: textarea
30 | id: should-happend
31 | attributes:
32 | label: "Expected behavior:"
33 | placeholder: A clear and concise description of what you expected to happen.
34 | validations:
35 | required: true
36 |
37 | - type: textarea
38 | id: logs
39 | attributes:
40 | label: "Logs:"
41 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
42 | placeholder: Show the Homebridge logs here. Replace any sensitive information with *.
43 | render: shell
44 | validations:
45 | required: true
46 |
47 | - type: textarea
48 | id: config
49 | attributes:
50 | label: "Config:"
51 | description: Please copy and paste your config. This will be automatically formatted into code, so no need for backticks.
52 | placeholder: Show the Homebridge config here. Replace any sensitive information with *.
53 | render: shell
54 | validations:
55 | required: true
56 |
57 | - type: input
58 | id: node-version
59 | attributes:
60 | label: "Node Version:"
61 | placeholder: node -v
62 | validations:
63 | required: true
64 |
65 | - type: input
66 | id: npm-version
67 | attributes:
68 | label: "NPM Version:"
69 | placeholder: npm -v
70 | validations:
71 | required: true
72 |
73 | - type: input
74 | id: homebridge-version
75 | attributes:
76 | label: "Homebridge Version:"
77 | placeholder: Homebridge -V
78 | validations:
79 | required: true
80 |
81 | - type: input
82 | id: plugin-version
83 | attributes:
84 | label: "Plugin Version:"
85 | placeholder: npm list -g homebridge-irobot
86 | validations:
87 | required: true
88 |
89 | - type: dropdown
90 | id: os
91 | attributes:
92 | label: "Operating System:"
93 | multiple: false
94 | options:
95 | - Raspberian
96 | - Ubuntu
97 | - Debian
98 | - Windows
99 | - MacOS
100 | - Docker
101 | - Other
102 | validations:
103 | required: true
104 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: suggest an idea for this project
3 | title: "Feature Request: "
4 | labels: [enhancement]
5 | assignees:
6 | - bloomkd46
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | Thanks for taking the time to fill out this Feature Request!
12 |
13 | - type: textarea
14 | id: what-happened
15 | attributes:
16 | label: "Is your feature request related to a problem? Please describe:"
17 | placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
18 | validations:
19 | required: true
20 |
21 | - type: textarea
22 | id: should-happend
23 | attributes:
24 | label: "Describe the solution you'd like:"
25 | placeholder: A clear and concise description of what you want to happen.
26 | validations:
27 | required: true
28 |
29 | - type: textarea
30 | id: alternatives
31 | attributes:
32 | label: "Describe alternatives you've considered:"
33 | placeholder: A clear and concise description of any alternative solutions or features you've considered.
34 | validations:
35 | required: false
36 |
37 | - type: textarea
38 | id: additional
39 | attributes:
40 | label: "Additional context:"
41 | placeholder: Add any other context or screenshots about the feature request here.
42 | render: shell
43 | validations:
44 | required: false
45 |
46 | - type: input
47 | id: node-version
48 | attributes:
49 | label: "Node Version (optional):"
50 | placeholder: node -v
51 | validations:
52 | required: false
53 |
54 | - type: input
55 | id: npm-version
56 | attributes:
57 | label: "NPM Version (optional):"
58 | placeholder: npm -v
59 | validations:
60 | required: false
61 |
62 | - type: input
63 | id: homebridge-version
64 | attributes:
65 | label: "Homebridge Version (optional):"
66 | placeholder: Homebridge -V
67 | validations:
68 | required: false
69 |
70 | - type: input
71 | id: plugin-version
72 | attributes:
73 | label: "Plugin Version:"
74 | placeholder: npm list -g homebridge-irobot
75 | validations:
76 | required: true
77 |
78 | - type: dropdown
79 | id: os
80 | attributes:
81 | label: "Operating System (optional):"
82 | multiple: false
83 | options:
84 | - Raspberian
85 | - Ubuntu
86 | - Debian
87 | - Windows
88 | - MacOS
89 | - Docker
90 | - Other
91 | validations:
92 | required: false
93 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support-request.yaml:
--------------------------------------------------------------------------------
1 | name: Support Request
2 | description: Need Help?
3 | title: "Support Request: "
4 | labels: "question, help wanted"
5 | assignees:
6 | - bloomkd46
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | Thanks for taking the time to fill out this Support Request!
12 |
13 | - type: textarea
14 | id: what-happened
15 | attributes:
16 | label: "Describe Your Problem:"
17 | placeholder: A clear and concise description of what your problem is.
18 | validations:
19 | required: true
20 |
21 | - type: textarea
22 | id: should-happend
23 | attributes:
24 | label: "Expected behavior:"
25 | placeholder: A clear and concise description of what you expected to happen.
26 | validations:
27 | required: false
28 |
29 | - type: textarea
30 | id: logs
31 | attributes:
32 | label: "Logs:"
33 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
34 | placeholder: Show the Homebridge logs here. Replace any sensitive information with *.
35 | render: shell
36 | validations:
37 | required: true
38 |
39 | - type: textarea
40 | id: config
41 | attributes:
42 | label: "Config:"
43 | description: Please copy and paste your config. This will be automatically formatted into code, so no need for backticks.
44 | placeholder: Show the Homebridge config here. Replace any sensitive information with *.
45 | render: shell
46 | validations:
47 | required: true
48 |
49 | - type: input
50 | id: node-version
51 | attributes:
52 | label: "Node Version:"
53 | placeholder: node -v
54 | validations:
55 | required: true
56 |
57 | - type: input
58 | id: npm-version
59 | attributes:
60 | label: "NPM Version:"
61 | placeholder: npm -v
62 | validations:
63 | required: true
64 |
65 | - type: input
66 | id: homebridge-version
67 | attributes:
68 | label: "Homebridge Version:"
69 | placeholder: Homebridge -V
70 | validations:
71 | required: true
72 |
73 | - type: input
74 | id: plugin-version
75 | attributes:
76 | label: "Plugin Version:"
77 | placeholder: npm list -g homebridge-irobot
78 | validations:
79 | required: true
80 |
81 | - type: dropdown
82 | id: os
83 | attributes:
84 | label: "Operating System:"
85 | multiple: false
86 | options:
87 | - Raspberian
88 | - Ubuntu
89 | - Debian
90 | - Windows
91 | - MacOS
92 | - Docker
93 | - Other
94 | validations:
95 | required: true
--------------------------------------------------------------------------------
/.github/workflows/add-model.js:
--------------------------------------------------------------------------------
1 | const eventPayload = require(process.env.GITHUB_EVENT_PATH);
2 | const device = require('../../device.json');
3 | const user = eventPayload.sender.login;
4 | const [roomba_model, supported] = Object.values(device);
5 | var fs = require('fs');
6 | var lineNumber = 36;
7 | var data = fs.readFileSync('README.md').toString().split("\n");
8 | data.splice(lineNumber, 0, ("| " + roomba_model + " | " + supported + " | [" + user + "](https://github.com/" + user + ") |"));
9 | var text = data.join("\n");
10 |
11 | fs.writeFile('README.md', text, function (err) {
12 | if (err) return console.log(err);
13 | });
14 |
--------------------------------------------------------------------------------
/.github/workflows/add-supported-device.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Add Supported Device To Readme
4 | on:
5 | issues:
6 | types: labeled
7 | concurrency: 'main'
8 | jobs:
9 | update_devices:
10 | if: github.event.label.name == 'update-devices'
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: '16'
19 |
20 | - uses: stefanbuck/github-issue-praser@v2
21 | id: issue-parser
22 | with:
23 | template-path: .github/ISSUE_TEMPLATE/add-supported-device.yml
24 |
25 | - run: echo '${{ steps.issue-parser.outputs.jsonString }}' > device.json
26 |
27 | - run: cat device.json
28 |
29 | - run: node .github/workflows/add-model.js
30 |
31 | - name: Commit changes
32 | shell: bash
33 | run: |
34 | git config --global user.email "action@github.com"
35 | git config --global user.name "github-actions"
36 | git add README.md
37 | git commit -m 'Added Device To Supported Devices Table'
38 | git push
39 |
40 | - uses: peter-evans/close-issue@v1
41 | with:
42 | comment: Thank you for your input! Check out the README to see your device show up.
43 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Lint
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | paths: [ src/**, .github/workflows/build.yml ]
7 | pull_request:
8 | branches: [ master ]
9 | paths: [ src/** ]
10 | workflow_dispatch:
11 |
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | # the Node.js versions to build on
20 | node-version: [12.x, 13.x, 14.x, 15.x, 16.x]
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 |
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v2
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 |
30 | - name: Install dependencies
31 | run: npm install
32 |
33 | - name: Build the project
34 | run: npm run build
35 | env:
36 | CI: true
37 |
38 | - name: Commit files if necessary
39 | id: commit
40 | run: |
41 | git config --local user.email "action@github.com"
42 | git config --local user.name "github-actions"
43 | git add --all
44 | if [ -z "$(git status --porcelain)" ]; then
45 | echo "::set-output name=push::false"
46 | else
47 | git commit -m "Built Project" -a
48 | echo "::set-output name=push::true"
49 | #git pull
50 | fi
51 |
52 | - name: Push changes
53 | if: steps.commit.outputs.push == 'true' && matrix.node-version == '16.x' && github.event_name == 'push'
54 | uses: ad-m/github-push-action@master
55 | with:
56 | github_token: ${{ secrets.GITHUB_TOKEN }}
57 |
58 | lint:
59 | runs-on: ubuntu-latest
60 |
61 | strategy:
62 | matrix:
63 | # the Node.js versions to build on
64 | node-version: [12.x, 13.x, 14.x, 15.x, 16.x]
65 |
66 | steps:
67 | - uses: actions/checkout@v2
68 |
69 | - name: Use Node.js ${{ matrix.node-version }}
70 | uses: actions/setup-node@v2
71 | with:
72 | node-version: ${{ matrix.node-version }}
73 |
74 | - name: Install dependencies
75 | run: npm install
76 |
77 | - name: Lint the project
78 | run: npm run lint
79 |
80 | - name: Commit files if necessary
81 | id: commit
82 | run: |
83 | git config --local user.email "action@github.com"
84 | git config --local user.name "github-actions"
85 | git add --all
86 | if [ -z "$(git status --porcelain)" ]; then
87 | echo "::set-output name=push::false"
88 | else
89 | git commit -m "Linted Project" -a
90 | echo "::set-output name=push::true"
91 | #git pull
92 | fi
93 |
94 | - name: Push changes
95 | if: steps.commit.outputs.push == 'true' && matrix.node-version == '16.x' && github.event_name == 'push'
96 | uses: ad-m/github-push-action@master
97 | with:
98 | github_token: ${{ secrets.GITHUB_TOKEN }}
99 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Package
5 | on:
6 | release:
7 | types: [published]
8 |
9 | jobs:
10 | publish:
11 | runs-on: ubuntu-latest
12 | #permissions:
13 | #contents: read
14 | #packages: write
15 | steps:
16 | - uses: actions/checkout@v2
17 | #with:
18 | #persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
19 | #fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
20 |
21 | - name: Setup node file to publish to npm
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: '16.x'
25 | registry-url: 'https://registry.npmjs.org'
26 |
27 | - name: Update changelog
28 | run: |
29 | x=`printf "### ${{ github.event.release.name }} \n"; echo "${{ github.event.release.body }}"; printf '\n\n'; cat CHANGELOG.md`
30 | echo "$x" > CHANGELOG.md
31 |
32 | - name: Commit files
33 | #if: false == true
34 | id: commit
35 | run: |
36 | git config --local user.email "action@github.com"
37 | git config --local user.name "github-actions"
38 | git add --all
39 | if [ -z "$(git status --porcelain)" ]; then
40 | echo "::set-output name=push::false"
41 | else
42 | git commit -m "Updated Changelog" -a
43 | echo "::set-output name=push::true"
44 | fi
45 |
46 | - name: Update Package Version
47 | run: |
48 | git config --local user.email "action@github.com"
49 | git config --local user.name "github-actions"
50 | git add --all
51 | npm version ${{ github.event.release.tag_name }}
52 | git tag -d v${{ github.event.release.tag_name }}
53 |
54 | - name: Push changes
55 | #if: steps.commit.outputs.push == 'true'
56 | uses: ad-m/github-push-action@master
57 | with:
58 | github_token: ${{ secrets.GITHUB_TOKEN }}
59 |
60 | - name: Install Dependencies
61 | run: npm install
62 |
63 | - name: Publish Beta to npm
64 | if: github.event.release.prerelease == true
65 | run: npm publish --tag=beta
66 | env:
67 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
68 |
69 | - name: Publish to npm
70 | if: github.event.release.prerelease == false
71 | run: npm publish
72 | env:
73 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
74 |
--------------------------------------------------------------------------------
/.github/workflows/typedoc.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 | name: Typedoc Generator
3 |
4 | # Controls when the workflow will run
5 | on:
6 | # Triggers the workflow on push or pull request events but only for the main branch
7 | push:
8 | branches: [ master ]
9 | paths: [ src/** ]
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
15 | jobs:
16 | # This workflow contains a single job called "build"
17 | Typedoc:
18 | # The type of runner that the job will run on
19 | runs-on: ubuntu-latest
20 |
21 | # Steps represent a sequence of tasks that will be executed as part of the job
22 | steps:
23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24 | - uses: actions/checkout@v2
25 |
26 | - name: Setup node
27 | uses: actions/setup-node@v2
28 | with:
29 | node-version: '16.x'
30 |
31 | - name: Remove Old Documentation
32 | run: rm -r docs
33 |
34 | - name: Re-Create Docs Folder
35 | run: mkdir docs
36 |
37 | - name: Install Dependencies
38 | run: npm install && npm install typedoc --save-dev
39 | - name: Generate Javadoc
40 | run: npx typedoc --out docs --entryPointStrategy expand ./src
41 |
42 | #- name: Re-inject Theme
43 | #run: 'echo "theme: jekyll-theme-tactile" > docs/_config.yml'
44 |
45 | - name: Commit files
46 | id: commit
47 | shell: bash
48 | run: |
49 | git config --local user.email "action@github.com"
50 | git config --local user.name "github-actions"
51 | git add --all
52 |
53 | if [ -z "$(git status --porcelain)" ]; then
54 |
55 | echo "::set-output name=push::false"
56 |
57 | else
58 |
59 | git commit -m "Updated API Documentation Using Typedoc" -a
60 | echo "::set-output name=push::true"
61 |
62 | fi
63 |
64 | - name: Push changes
65 | if: steps.commit.outputs.push == 'true'
66 | uses: ad-m/github-push-action@master
67 | with:
68 | github_token: ${{ secrets.GITHUB_TOKEN }}
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore compiled code
2 | dist
3 | homebridge-ui/**/*.js
4 | homebridge-ui/**/*.js.map
5 | homebridge-ui/**/*.d.ts
6 |
7 | # ------------- Defaults ------------- #
8 |
9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | lerna-debug.log*
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 | *.lcov
32 |
33 | # nyc test coverage
34 | .nyc_output
35 |
36 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
37 | .grunt
38 |
39 | # Bower dependency directory (https://bower.io/)
40 | bower_components
41 |
42 | # node-waf configuration
43 | .lock-wscript
44 |
45 | # Compiled binary addons (https://nodejs.org/api/addons.html)
46 | build/Release
47 |
48 | # Dependency directories
49 | node_modules/
50 | jspm_packages/
51 | testing/
52 |
53 | # Snowpack dependency directory (https://snowpack.dev/)
54 | web_modules/
55 |
56 | # TypeScript cache
57 | *.tsbuildinfo
58 |
59 | # Optional npm cache directory
60 | .npm
61 |
62 | # Optional eslint cache
63 | .eslintcache
64 |
65 | # Microbundle cache
66 | .rpt2_cache/
67 | .rts2_cache_cjs/
68 | .rts2_cache_es/
69 | .rts2_cache_umd/
70 |
71 | # Optional REPL history
72 | .node_repl_history
73 |
74 | # Output of 'npm pack'
75 | *.tgz
76 |
77 | # Yarn Integrity file
78 | .yarn-integrity
79 |
80 | # dotenv environment variables file
81 | .env
82 | .env.test
83 |
84 | # parcel-bundler cache (https://parceljs.org/)
85 | .cache
86 | .parcel-cache
87 |
88 | # Next.js build output
89 | .next
90 |
91 | # Nuxt.js build / generate output
92 | .nuxt
93 | dist
94 |
95 | # Gatsby files
96 | .cache/
97 | # Comment in the public line in if your project uses Gatsby and not Next.js
98 | # https://nextjs.org/blog/next-9-1#public-directory-support
99 | # public
100 |
101 | # vuepress build output
102 | .vuepress/dist
103 |
104 | # Serverless directories
105 | .serverless/
106 |
107 | # FuseBox cache
108 | .fusebox/
109 |
110 | # DynamoDB Local files
111 | .dynamodb/
112 |
113 | # TernJS port file
114 | .tern-port
115 |
116 | # Stores VSCode versions used for testing VSCode extensions
117 | .vscode-test
118 |
119 | # yarn v2
120 |
121 | .yarn/cache
122 | .yarn/unplugged
123 | .yarn/build-state.yml
124 | .pnp.*
125 |
126 | # test Files
127 | **/*test.*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Ignore source code
2 | src
3 |
4 | # Ignore Documentation
5 | docs
6 |
7 | # ------------- Defaults ------------- #
8 |
9 | # gitHub actions
10 | .github
11 |
12 | # eslint
13 | .eslintrc
14 |
15 | # typescript
16 | tsconfig.json
17 |
18 | # vscode
19 | .vscode
20 |
21 | # nodemon
22 | nodemon.json
23 |
24 | # Logs
25 | logs
26 | *.log
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 | lerna-debug.log*
31 |
32 | # Diagnostic reports (https://nodejs.org/api/report.html)
33 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
34 |
35 | # Runtime data
36 | pids
37 | *.pid
38 | *.seed
39 | *.pid.lock
40 |
41 | # Directory for instrumented libs generated by jscoverage/JSCover
42 | lib-cov
43 |
44 | # Coverage directory used by tools like istanbul
45 | coverage
46 | *.lcov
47 |
48 | # nyc test coverage
49 | .nyc_output
50 |
51 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
52 | .grunt
53 |
54 | # Bower dependency directory (https://bower.io/)
55 | bower_components
56 |
57 | # node-waf configuration
58 | .lock-wscript
59 |
60 | # Compiled binary addons (https://nodejs.org/api/addons.html)
61 | build/Release
62 |
63 | # Dependency directories
64 | node_modules/
65 | jspm_packages/
66 |
67 | # Snowpack dependency directory (https://snowpack.dev/)
68 | web_modules/
69 |
70 | # TypeScript cache
71 | *.tsbuildinfo
72 |
73 | # Optional npm cache directory
74 | .npm
75 |
76 | # Optional eslint cache
77 | .eslintcache
78 |
79 | # Microbundle cache
80 | .rpt2_cache/
81 | .rts2_cache_cjs/
82 | .rts2_cache_es/
83 | .rts2_cache_umd/
84 |
85 | # Optional REPL history
86 | .node_repl_history
87 |
88 | # Output of 'npm pack'
89 | *.tgz
90 |
91 | # Yarn Integrity file
92 | .yarn-integrity
93 |
94 | # dotenv environment variables file
95 | .env
96 | .env.test
97 |
98 | # parcel-bundler cache (https://parceljs.org/)
99 | .cache
100 | .parcel-cache
101 |
102 | # Next.js build output
103 | .next
104 |
105 | # Nuxt.js build / generate output
106 | .nuxt
107 | dist
108 |
109 | # Gatsby files
110 | .cache/
111 | # Comment in the public line in if your project uses Gatsby and not Next.js
112 | # https://nextjs.org/blog/next-9-1#public-directory-support
113 | # public
114 |
115 | # vuepress build output
116 | .vuepress/dist
117 |
118 | # Serverless directories
119 | .serverless/
120 |
121 | # FuseBox cache
122 | .fusebox/
123 |
124 | # DynamoDB Local files
125 | .dynamodb/
126 |
127 | # TernJS port file
128 | .tern-port
129 |
130 | # Stores VSCode versions used for testing VSCode extensions
131 | .vscode-test
132 |
133 | # yarn v2
134 |
135 | .yarn/cache
136 | .yarn/unplugged
137 | .yarn/build-state.yml
138 | .pnp.*
139 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.eol": "\n",
3 | "editor.codeActionsOnSave": {
4 | "source.fixAll.eslint": true
5 | },
6 | "editor.rulers": [
7 | 140
8 | ],
9 | "eslint.enable": true,
10 | "cSpell.words": [
11 | "blid"
12 | ]
13 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 4.0.0 Beta 11
2 | * Deprecated v1 support
3 |
4 |
5 | ### 4.0.0 Beta 10
6 | * Fixed connections being reset to 0 at boot being set to early
7 |
8 |
9 | ### 4.0.0-beta.9
10 | * Fixed when connections is increased
11 |
12 |
13 | ### 4.0.0 Beta 7
14 | * Add current connections to accessory context
15 |
16 |
17 |
18 | ### 4.0.0 Beta 6
19 | * Disabled MQTT auto reconnect
20 |
21 |
22 | ### 4.0.0 Beta 5
23 | * Improved connectivity restoration
24 | * Fixed lastState undefined errors
25 |
26 |
27 | ### 4.0.0 Beta 4
28 | * Fixed spinner not stopping if no device found in custom UI
29 |
30 |
31 | ### 4.0.0 Beta 3
32 | * Fixed showIntro undefined error
33 | * Added empty config detection to prevent crash
34 |
35 |
36 | ### 4.0.0 Beta 2
37 | * Fixed server crash
38 |
39 |
40 | ### 4.0.0 Beta 1
41 | * Full Rewrite
42 | * Automatic IP Resolution
43 | * Cloud OR Local Configuration
44 | * More Control
45 | * Better Logging
46 | * Rewrote Custom UI In Typescript
47 | ### TODO:
48 | * Add Specific Region Cleaning
49 | * Add Status Contact Sensors
50 | * Add Full Info To Custom UI
51 | * Better Log Levels
52 | * Add Preferences
53 |
54 | **Full Changelog**: https://github.com/bloomkd46/homebridge-iRobot/compare/3.0.0-beta.16...4.0.0-beta.1
55 |
56 |
57 | ### 3.0.0 Beta 16
58 | * Added 3 second timeout when requesting robot state
59 |
60 |
61 | ### 3.0.0 Beta 15
62 | * Fixed offline status
63 |
64 |
65 | ### 3.0.0 Beta 14
66 | * Hopefully fixed offline status for On characteristic
67 |
68 |
69 | ### 3.0.0 Beta 13
70 | * Fixed offline status
71 | * Fixed Motion and Filter Status Updates
72 |
73 |
74 | ### 3.0.0 Beta 12
75 | * Better logging
76 | * Numerous upgrades and improvements
77 | * Expect 3.0.0 soon
78 |
79 |
80 | ### 3.0.0 Beta 11
81 | * Fully Stable
82 | * Fixed version identification
83 |
84 | ### TODO:
85 | * Add room-by-room support
86 |
87 |
88 | ### 3.0.0 Beta 10
89 | * Fixed conflicting states error
90 |
91 |
92 | ### 3.0.0 Beta 9
93 | * Fixed error when updating switch state
94 |
95 |
96 | ### 3.0.0 Beta 8
97 | * Simplified state parse
98 | * Fixed update event
99 |
100 |
101 | ### 3.0.0 Beta 7
102 | * Hopefully fixed undefined object error
103 |
104 |
105 | ### 3.0.0 Beta 6
106 | * Fixed get mission function on v3 Roomba's
107 | * Fixed update event
108 |
109 |
110 | ### 3.0.0 Beta 5
111 | * Hopefully fixed configuration error
112 |
113 |
114 | ### 3.0.0 Beta 4
115 | * Hopefully fixed connection and disconnection process
116 | * Fixed version identification
117 |
118 |
119 | ### 3.0.0 Beta 3
120 | * Fixed custom ui
121 |
122 |
123 | ### 3.0.0 Beta 2
124 | * Fixed crash if sensors aren't added to config
125 |
126 |
127 | ### 3.0.0 Beta 1
128 | Lots of bug fixes
129 | ## TODO:
130 | * add multi-room support
131 | * Add keep alive option
132 | * Better logging
133 |
134 | # WARNING!
135 | Updating to version 3.0.0 will reset your Roomba’s in HomeKit due to new setup
136 |
137 |
138 | ### 2.1.17 Beta 1
139 | * Whitelisted Bravva jets
140 |
141 |
142 | ### 2.1.16
143 | * This release represents releases 2.1.14 and 2.1.15
144 |
145 |
146 | ### 2.1.15
147 | * Set program to resume robot if job is already active
148 | * Set program to stop Roomba if room-by-room request received
149 | * Added j7 support (hopefully 🤞)
150 |
151 |
152 | ### 2.1.14
153 | * Added the ability to manually configure Roomba's instead of using your iRobot credentials
154 |
155 |
156 | ### 2.1.14 Beta 4
157 | * Improved logging when using manual configuration
158 | * Fixed logic when determining if device supports room-by-room cleaning
159 |
160 |
161 | ### 2.1.14 Beta 3
162 | * Fixed homebridge crash due to logic error when configuring Roomba's
163 |
164 |
165 | ### 2.1.14 Beta 2
166 | * Fixed homebridge crash when reading variable ver
167 |
168 |
169 | ### 2.1.14 Beta 1
170 | * Added support for manually configuring Roomba's instead of entering your Roomba credentials
171 |
172 |
173 | ### 2.1.13
174 | * Fixed how getRoombas.ts handles unconfigured Roombas to address issues #23 and #34
175 |
176 |
177 | ### 2.1.12
178 | * Added software version to custom ui
179 |
180 |
181 | ### 2.1.11
182 | * Fixed Stuck Sensor, Thanks @Ghost108
183 | * Changed on/off logs from debug to info
184 |
185 |
186 | ### 2.1.10
187 | * Fixed crash when starting second IP sweep
188 | * Removed devices if it fails to find IP
189 |
190 |
191 | ### 2.1.9
192 | * Fixed homebridge crash on offline
193 |
194 |
195 | ### 2.1.8
196 | * Fixed typo during Roomba IP discovery. Thanks @rcoletti116
197 |
198 |
199 | ### 2.1.7
200 | * Set Accessory to not responding in HomeKit when Roomba disconnects
201 | * Added Log for when Roomba is stuck
202 | * Made IP search run again after 5 seconds for up to 5 attempts
203 | * Prevented plugin from adding m6's
204 |
205 |
206 | ### 2.1.6
207 | * Removed Log Spam When Reconnecting After Connection Drop
208 | * Made Low Battery Warnings Not Appear If Roomba Is Charging
209 |
210 |
211 | ### 2.1.5
212 | * Removed Broken Status From Device Table In Custom UI
213 |
214 |
215 | ### 2.1.4
216 | * Removed Status From Table Since It Always Says Online
217 |
218 | ### 2.1.3
219 | * Changed Logic For Identifying If Region Is Already Saved
220 | * Added 5 Second Delay Before Reconnecting If The Connection Drops
221 |
222 | ### 2.1.2
223 | * Added Support Page In Custom UI
224 | * (Wiki Links Don't Work Yet)
225 |
226 | ### 2.1.1
227 | * Re-arranged table
228 | * fixed rooms section in table
229 |
230 | ### 2.1.0
231 | * Added custom UI
232 |
233 | ### 2.1.0-beta.3
234 | * Removed Mac Address from devices table
235 |
236 | ### 2.1.0-beta.2
237 | * hid spinner while looking for devices in custom UI
238 |
239 | ### 2.1.0-beta.1
240 | * Fixed menuHome custom UI error
241 |
242 | ### 2.1.0-beta.0
243 | * Started To Work On Custom UI
244 |
245 | ### 2.0.5
246 | * Set log added in version [2.0.2](#202) to debug
247 | * Set on/off logs to info
248 | * Added log for when roomba is stuck
249 |
250 | ### 2.0.4
251 | * fixed typo in room sync functions when adding new room to existing map
252 |
253 | ### 2.0.3
254 | * Fixed error where it wouldn't add new regions
255 |
256 | ### 2.0.2
257 | * Added Log When Updating Homekit Rooms
258 |
259 | ### 2.0.1
260 | * Made disableMultiRoom default to false in config
261 |
262 | ### 2.0.0
263 | * Became A Homebridge Vertified Plugin
264 | * Set Password format to password in Schema
265 |
266 | ### 1.3.2
267 | * Made Roomba execute off action 2 after 5 seconds if state dosent change
268 |
269 | ### 1.3.1
270 | * Prevented plugin from initilizing if it dosent have an email/password
271 |
272 | ### 1.3.0
273 | * Added support for Multiple Rooms
274 | * Made Roomba wait 1 second for scenes when it is turned on
275 |
276 | ### 1.2.0
277 | * Added Room-By-Room Abilities On Models That Support It
278 |
279 | ### 1.1.0
280 | * Added More Configuation options
281 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # homebridge-iRobot
7 |
8 | Homebridge plugin to integrate iRobot roombas into HomeKit
9 |
10 | [](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
11 | [](https://npmcharts.com/compare/homebridge-irobot?log=true&interval=1&minimal=true)
12 |
13 | [](https://www.npmjs.com/package/homebridge-irobot)
14 | [](https://github.com/bloomkd46/homebridge-iRobot/wiki/Beta-Version)
15 |
16 | [](https://github.com/bloomkd46/homebridge-iRobot/actions/workflows/build.yml)
17 | [](/LICENSE)
18 |
19 |
20 |
21 |
22 | ### Plugin Information
23 |
24 | - This plugin allows you to view and control your iRobot roombas within HomeKit. The plugin:
25 | - downloads a device list if your iRobot credentials are supplied
26 | - controls your devices locally
27 | - listens for real-time device updates when controlled externally
28 |
29 | ## Supported Devices
30 | > Don't See Your Device Below?
31 | > Let Me Know If It Worked By Filling Out [This Template](https://github.com/bloomkd46/homebridge-iRobot/issues/new?assignees=bloomkd46&labels=enchancment&template=add-supported-device.yml&title=Supported+Device%3A+)
32 |
33 | | Model | Supported | Reported By |
34 | |-|-|-|
35 | | i3 | Yes | [nilsstreedain](https://github.com/nilsstreedain) |
36 | | e6 | Yes | [Dav97480](https://github.com/Dav97480) |
37 | | Combo 10 Max | No | [c-delouvencourt](https://github.com/c-delouvencourt) |
38 | | i8+ | Yes | [andyleemeuk](https://github.com/andyleemeuk) |
39 | | e5 | Yes | [mylover21](https://github.com/mylover21) |
40 | | 690 | No | [Theplaze](https://github.com/Theplaze) |
41 | | Coombo | No | [morasounds](https://github.com/morasounds) |
42 | | Combo J9+ | Yes | [AcrtlBot](https://github.com/AcrtlBot) |
43 | | J5+ | Yes | [camsky209](https://github.com/camsky209) |
44 | | Braava m6 | No | [camsky209](https://github.com/camsky209) |
45 | | 971 | No | [iamgottem](https://github.com/iamgottem) |
46 | | 691 | No | [Firemanjoe](https://github.com/Firemanjoe) |
47 | | j9+ | Yes | [LakshmyPulickalRajukumar](https://github.com/LakshmyPulickalRajukumar) |
48 | | j9+ | No | [LakshmyPulickalRajukumar](https://github.com/LakshmyPulickalRajukumar) |
49 | | j7 | No | [Belg0](https://github.com/Belg0) |
50 | | i7 | Yes | [sreenath87](https://github.com/sreenath87) |
51 | | 676 | No | [mrath1201](https://github.com/mrath1201) |
52 | | J7 | Yes | [Libar19](https://github.com/Libar19) |
53 | | i6 plus | Yes | [AlSmyth81](https://github.com/AlSmyth81) |
54 | | 980 | No | [ivansemenovv](https://github.com/ivansemenovv) |
55 | | I5+ | Yes | [Ju17091](https://github.com/Ju17091) |
56 | | i5 | Yes | [YiffyC](https://github.com/YiffyC) |
57 | | J7+ | No | [danielepenguin](https://github.com/danielepenguin) |
58 | | Braava jet m6 (6012) | No | [Vaxter](https://github.com/Vaxter) |
59 | | J7 | No | [Viper0580](https://github.com/Viper0580) |
60 | | 989 | No | [czarnyolek](https://github.com/czarnyolek) |
61 | | j8 plus | Yes | [pgorrindo](https://github.com/pgorrindo) |
62 | | 971 | No | [luigicrafter](https://github.com/luigicrafter) |
63 | | 692 | Yes | [Quesabyte](https://github.com/Quesabyte) |
64 | | 900 | No | [meteora1986](https://github.com/meteora1986) |
65 | | 900 | No | [markuzkuz](https://github.com/markuzkuz) |
66 | | i7 Plus | Yes | [tohmc](https://github.com/tohmc) |
67 | | S9 | No | [johnsills1](https://github.com/johnsills1) |
68 | | s9 | Yes | [douginoz](https://github.com/douginoz) |
69 | | i6 | Yes | [webcleef](https://github.com/webcleef) |
70 | | 697 | No | [Funskes](https://github.com/Funskes) |
71 | | 900 | Yes | [WuTangKillaBee](https://github.com/WuTangKillaBee) |
72 | | i6 | No | [webcleef](https://github.com/webcleef) |
73 | | j7 plus | No | [danzika](https://github.com/danzika) |
74 | | 675 | Yes | [djmurray20](https://github.com/djmurray20) |
75 | | 675 | Yes | [janpreet](https://github.com/janpreet) |
76 | | i7 Plus | No | [MEGALITH2022](https://github.com/MEGALITH2022) |
77 | | 527 | Yes | [TonyYuta](https://github.com/TonyYuta) |
78 | | e5 | No | [metalshark](https://github.com/metalshark) |
79 | | i4 | Yes | [BrauCadena](https://github.com/BrauCadena) |
80 | | i4+ | Yes | [SergioBraulioCadenaJuarez](https://github.com/SergioBraulioCadenaJuarez) |
81 | | m6 | No | [zaki-hanafiah](https://github.com/zaki-hanafiah) |
82 | | i3 | Yes | [zaki-hanafiah](https://github.com/zaki-hanafiah) |
83 | | 980 | Yes | [adamengineering](https://github.com/adamengineering) |
84 | | i7 | Yes | [marchein](https://github.com/marchein) |
85 | | s9 | No | [Maximilian2022](https://github.com/Maximilian2022) |
86 | | combo | No | [ExoBiTe](https://github.com/ExoBiTe) |
87 | | 966 | Yes | [Jansne](https://github.com/Jansne) |
88 | | 675 | No | [EddieDSuza](https://github.com/EddieDSuza) |
89 | | Braava jet m6 | No | [JiningLiu](https://github.com/JiningLiu) |
90 | | j7 | Yes | [JiningLiu](https://github.com/JiningLiu) |
91 | | 966 | No | [fheise](https://github.com/fheise) |
92 | | 891 | Yes | [lambert0725](https://github.com/lambert0725) |
93 | | j7 | Yes | [wja731](https://github.com/wja731) |
94 | | 694 | No | [aclerok](https://github.com/aclerok) |
95 | | m6 | No | [waltermarbel](https://github.com/waltermarbel) |
96 | | 976 | Yes | [benov84](https://github.com/benov84) |
97 | | 976 | No | [mbnn](https://github.com/mbnn) |
98 | | 690 | No | [tiger-git-hub](https://github.com/tiger-git-hub) |
99 | | i4 (4150) | Yes | [the1maximus](https://github.com/the1maximus) |
100 | | 890 | No | [GitPuffy](https://github.com/GitPuffy) |
101 | | i7 | Yes | [rtdevnet](https://github.com/rtdevnet) |
102 | | i7 | No | [rtdevnet](https://github.com/rtdevnet) |
103 | | S9 | No | [kip1539](https://github.com/kip1539) |
104 | | 671 | Yes | [Geek-MD](https://github.com/Geek-MD) |
105 | | 980 | Yes | [Drewbacca2](https://github.com/Drewbacca2) |
106 | | e5 | No | [TomF79](https://github.com/TomF79) |
107 | | e5 | Yes | [TomF79](https://github.com/TomF79) |
108 | | 675 | No | [Mkrtichmikem](https://github.com/Mkrtichmikem) |
109 | | j7 | Yes | [jonad2002](https://github.com/jonad2002) |
110 | | i7 | Yes | [Clouder59](https://github.com/Clouder59) |
111 | | 606 | No | [PvdGulik](https://github.com/PvdGulik) |
112 | | m6 | Yes | [ginoledesma](https://github.com/ginoledesma) |
113 | | i3 | No | [rminear68](https://github.com/rminear68) |
114 | | 980 | No | [jeanchrijaz](https://github.com/jeanchrijaz) |
115 | | i9 | Yes | [douginoz](https://github.com/douginoz) |
116 | | 960 | Yes | [NateUT99](https://github.com/NateUT99) |
117 | | 965 | Yes | [bloomkd46](https://github.com/bloomkd46) |
118 | | i8 | Yes | [bloomkd46](https://github.com/bloomkd46) |
119 |
120 |
121 | ## Features:
122 | - [x] Approved By Homebridge
123 | - [x] Custom UI For Viewing Devices
124 | - [x] On/Off Control
125 | - [x] Room-By-Room Control On Models That Support It (Only Tested When Using One Map)
126 | - [x] Auto-Dicovery Of All Devices On Your Acount
127 | - [x] Battery Percent/Charging ifo
128 | - [x] Binfull Detection In The Form Of Filter/Contact/Motion Sensor
129 | - [x] Stuck Sensor
130 |
131 | ### Prerequisites
132 |
133 | - To use this plugin, you will need to already have [Homebridge](https://homebridge.io) (at least v1.3.5) or [HOOBS](https://hoobs.org) (at least v4) installed. Refer to the links for more information and installation instructions.
134 |
135 |
136 | ### Setup
137 |
138 | - [Installation](https://github.com/bloomkd46/homebridge-iRobot/wiki/Installation)
139 | - [Configuration](https://github.com/bloomkd46/homebridge-iRobot/wiki/Configuration)
140 | - [Beta Version](https://github.com/bloomkd46/homebridge-iRobot/wiki/Beta-Version)
141 | - [Node Version](https://github.com/bloomkd46/homebridge-iRobot/wiki/Node-Version)
142 | - [Uninstallation](https://github.com/bloomkd46/homebridge-iRobot/wiki/Uninstallation)
143 | - [Room By Room](https://github.com/bloomkd46/homebridge-iRobot/wiki/Room-By-Room)
144 |
145 | ### Help/About
146 |
147 | - [Common Errors](https://github.com/bloomkd46/homebridge-iRobot/wiki/Common-Errors)
148 | - [Support Request](https://github.com/bloomkd46/homebridge-iRobot/issues/new/choose)
149 | - [Changelog](/CHANGELOG.md)
150 |
151 | ### Credits
152 |
153 | - To the creators/contributors of [Homebridge](https://homebridge.io) who make this plugin possible.
154 | - To [homebridge-Meross](https://github.com/bwp91/homebridge-meross) of which I based this readme, wiki, and homebridge-ui off of
155 | - To [Dorita980](https://github.com/koalazak/dorita980) Who cracked the iRobot API
156 |
157 | ### Disclaimer
158 |
159 | - I am in no way affiliated with iRobot and this plugin is a personal project that I maintain in my free time.
160 | - Use this plugin entirely at your own risk - please see licence for more information.
161 |
--------------------------------------------------------------------------------
/Supported Devices.md:
--------------------------------------------------------------------------------
1 | | Model | Supported |
2 | |---|---|
3 | | i8 | Yes |
4 | | 985 | Yes |
5 | | j7 | No |
6 |
--------------------------------------------------------------------------------
/config.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginAlias": "iRobotPlatform",
3 | "pluginType": "platform",
4 | "singular": true,
5 | "customUi": true,
6 | "headerDisplay": "
For help and support please visit the GitHub Wiki. I hope you find this plugin useful!
",
7 | "schema": {
8 | "type": "object",
9 | "properties": {
10 | "name": {
11 | "title": "Name",
12 | "description": "For logging purposes",
13 | "type": "string",
14 | "default": "iRobot",
15 | "required": true
16 | },
17 | "accessories": {
18 | "type": "array",
19 | "items": {
20 | "type": "object",
21 | "properties": {
22 | "name": {
23 | "type": "string"
24 | },
25 | "blid": {
26 | "type": "string"
27 | },
28 | "password": {
29 | "type": "string"
30 | },
31 | "sw": {
32 | "type": "string"
33 | },
34 | "sku": {
35 | "type": "string"
36 | },
37 | "ipResolution": {
38 | "type": "string"
39 | },
40 | "hostname": {
41 | "type": "string"
42 | },
43 | "ip": {
44 | "type": "string"
45 | }
46 | }
47 | }
48 | },
49 | "logLevel": {
50 | "title": "Log Level",
51 | "type": "number",
52 | "minimum": 0,
53 | "maximum": 4,
54 | "default": 3,
55 | "description": "TODO",
56 | "required": true
57 | },
58 | "autoConnect": {
59 | "title": "Automatically Connect",
60 | "description": "Whether or not to automatically establish a connection to your devices at boot",
61 | "type": "boolean",
62 | "default": true,
63 | "required": false
64 | },
65 | "alwaysShowModes": {
66 | "title": "Always Show Actions",
67 | "description": "Whether or not to always show actions in HomeKit (Not Recommended; Use With Caution)",
68 | "type": "boolean",
69 | "default": false,
70 | "required": false
71 | }
72 | }
73 | },
74 | "layout": [
75 | "name",
76 | "logLevel",
77 | "autoConnect",
78 | "alwaysShowModes"
79 | ]
80 | }
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
--------------------------------------------------------------------------------
/docs/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-code-background: #FFFFFF;
3 | --dark-code-background: #1E1E1E;
4 | }
5 |
6 | @media (prefers-color-scheme: light) { :root {
7 | --code-background: var(--light-code-background);
8 | } }
9 |
10 | @media (prefers-color-scheme: dark) { :root {
11 | --code-background: var(--dark-code-background);
12 | } }
13 |
14 | :root[data-theme='light'] {
15 | --code-background: var(--light-code-background);
16 | }
17 |
18 | :root[data-theme='dark'] {
19 | --code-background: var(--dark-code-background);
20 | }
21 |
22 | pre, code { background: var(--code-background); }
23 |
--------------------------------------------------------------------------------
/docs/functions/CustomCharacteristics.default.html:
--------------------------------------------------------------------------------
1 | default | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
18 |
Function default
19 |
20 |
21 | - default(hap: __module): {
EnergyUsage: any;
PanelStatus: any;
}
22 | -
23 |
28 |
Returns {
EnergyUsage: any;
PanelStatus: any;
}
29 |
30 | -
31 |
EnergyUsage: any
32 | -
33 |
PanelStatus: any
36 |
68 |
70 |
--------------------------------------------------------------------------------
/docs/modules.html:
--------------------------------------------------------------------------------
1 | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
homebridge-irobot
15 |
29 |
58 |
60 |
--------------------------------------------------------------------------------
/docs/modules/CustomCharacteristics.html:
--------------------------------------------------------------------------------
1 | CustomCharacteristics | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
17 |
Module CustomCharacteristics
20 |
22 |
29 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/modules/accessories_V1.html:
--------------------------------------------------------------------------------
1 | accessories/V1 | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
17 |
Module accessories/V1
20 |
22 |
29 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/modules/accessories_V2.html:
--------------------------------------------------------------------------------
1 | accessories/V2 | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
17 |
Module accessories/V2
20 |
22 |
29 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/modules/accessories_V3.html:
--------------------------------------------------------------------------------
1 | accessories/V3 | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
17 |
Module accessories/V3
20 |
22 |
29 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/modules/platform.html:
--------------------------------------------------------------------------------
1 | platform | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
17 |
Module platform
20 |
22 |
29 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/types/settings.Device.html:
--------------------------------------------------------------------------------
1 | Device | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
18 |
Type alias Device
19 |
Device: {
blid: string;
name: string;
password: string;
sku: string;
sw: string;
} & ipInfo
22 |
59 |
61 |
--------------------------------------------------------------------------------
/docs/types/settings.V1Mission.html:
--------------------------------------------------------------------------------
1 | V1Mission | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
18 |
Type alias V1Mission
19 |
V1Mission: Awaited<ReturnType<LocalV1.Local["getMission"]>>["ok"]
22 |
59 |
61 |
--------------------------------------------------------------------------------
/docs/variables/settings.PLATFORM_NAME.html:
--------------------------------------------------------------------------------
1 | PLATFORM_NAME | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
18 |
Variable PLATFORM_NAMEConst
19 |
PLATFORM_NAME: "iRobotPlatform" = 'iRobotPlatform'
20 |
24 |
61 |
63 |
--------------------------------------------------------------------------------
/docs/variables/settings.PLUGIN_NAME.html:
--------------------------------------------------------------------------------
1 | PLUGIN_NAME | homebridge-irobot
2 |
3 |
4 |
5 |
7 |
8 | - Preparing search index...
9 | - The search index is not available
homebridge-irobot
10 |
11 |
12 |
13 |
14 |
18 |
Variable PLUGIN_NAMEConst
19 |
PLUGIN_NAME: "homebridge-irobot" = 'homebridge-irobot'
20 |
24 |
61 |
63 |
--------------------------------------------------------------------------------
/homebridge-ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
Thank you for installing homebridge-iRobot
10 |
You can configure your iRobot on the next page
11 |
12 |
13 |
17 | To configure your devices, use the devices tab
18 |
19 |
20 |
21 |
27 |
28 |
This Wil Permanently Delete The Following:
29 |
30 | - Accessory Config
31 | - Cached Accessory Data
32 | - Accessory Logs
33 |
Are You Sure You Wish To Proceed?
34 |
35 |
39 |
40 |
41 |
42 |
43 |
48 |
67 |
Status: N/A
68 |
69 |
70 |
72 |
73 |
74 |
Please wait
75 |
This may take a few minutes
76 |
77 |
78 |
91 |
--------------------------------------------------------------------------------
/homebridge-ui/public/note.txt:
--------------------------------------------------------------------------------
1 | ' Online'
2 | ' Offline'
--------------------------------------------------------------------------------
/homebridge-ui/public/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "lib": [
5 | "DOM"
6 | ],
7 | "sourceMap": false,
8 | "inlineSourceMap": true,
9 | "inlineSources": true
10 | },
11 | "include": [
12 | "index.ts"
13 | ]
14 | }
--------------------------------------------------------------------------------
/homebridge-ui/server.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { lookup } from 'dns/promises';
4 | import { readFileSync } from 'fs';
5 | import { rm } from 'fs/promises';
6 | import { join } from 'path';
7 |
8 | import { getPassword, getPasswordCloud, getRobotByBlid, getRobotPublicInfo, PublicInfo } from '@bloomkd46/dorita980';
9 | import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils';
10 |
11 |
12 | class PluginUiServer extends HomebridgePluginUiServer {
13 | constructor() {
14 | super();
15 | const storagePath = join(this.homebridgeStoragePath ?? '', 'iRobot');
16 |
17 | this.onRequest('/deleteDevice', async (blid: string) => {
18 | try {
19 | await rm(join(storagePath, `${blid}.log`)).catch(err => console.error(err));
20 | await rm(join(storagePath, `${blid}.cache.json`)).catch(err => console.error(err));
21 | return 'true';
22 | } catch (err) {
23 | return 'false';
24 | }
25 | });
26 |
27 | this.onRequest('/getCache', (blid: string) => {
28 | try {
29 | return JSON.parse(readFileSync(join(storagePath, `${blid}.cache.json`), 'utf-8'));
30 | } catch (err) {
31 | return {};
32 | }
33 | });
34 |
35 | this.onRequest('/getLogs', (blid: string) => {
36 | try {
37 | return readFileSync(join(storagePath, `${blid}.log`), 'utf-8');
38 | } catch (err) {
39 | return `Failed to load logs from ${join(storagePath, `${blid}.log`)}`;
40 | }
41 | });
42 |
43 | this.onRequest('/configureDevices',
44 | async (payload: { email: string; password: string; } | { ip: string; blid?: string; password?: string; }[]) => {
45 | const devices: Device[] = [];
46 | if (Array.isArray(payload)) {
47 | for (const device of payload) {
48 | if (device.blid && device.password) {
49 | const networkInfo: ipInfo = await new Promise(resolve =>
50 | lookup(`iRobot-${device.blid}.local`, 4).then(() => resolve({
51 | ipResolution: 'lookup', hostname: `iRobot-${device.blid}.local`,
52 | })).catch(() => resolve(undefined))) ??
53 | await new Promise(resolve =>
54 | lookup(`Roomba-${device.blid}.local`, 4).then(() => resolve({
55 | ipResolution: 'lookup', hostname: `Roomba-${device.blid}.local`,
56 | })).catch(() => resolve(undefined))) ??
57 | await new Promise(resolve =>
58 | getRobotByBlid(device.blid, (err) => err ? resolve(undefined) : resolve({ ipResolution: 'broadcast' }))) ??
59 | { ipResolution: 'manual', ip: device.ip };
60 |
61 | const publicInfo: PublicInfo = await new Promise(resolve =>
62 | getRobotPublicInfo(device.ip, (err, data) => err ? resolve(undefined) : resolve(data)));
63 | devices.push({
64 | name: publicInfo.robotname,
65 | blid: device.blid,
66 | password: device.password,
67 | sw: publicInfo.sw,
68 | sku: publicInfo.sku,
69 | ...networkInfo,
70 | });
71 | } else {
72 | const deviceInfo = await getPassword(device.ip);
73 | const networkInfo: ipInfo = await new Promise(resolve =>
74 | lookup(`iRobot-${deviceInfo.blid}.local`, 4).then(() => resolve({
75 | ipResolution: 'lookup', hostname: `iRobot-${deviceInfo.blid}.local`,
76 | })).catch(() => resolve(undefined))) ??
77 | await new Promise(resolve =>
78 | lookup(`Roomba-${deviceInfo.blid}.local`, 4).then(() => resolve({
79 | ipResolution: 'lookup', hostname: `Roomba-${deviceInfo.blid}.local`,
80 | })).catch(() => resolve(undefined))) ??
81 | await new Promise(resolve =>
82 | getRobotByBlid(deviceInfo.blid, (err) => err ? resolve(undefined) : resolve({ ipResolution: 'broadcast' }))) ??
83 | { ipResolution: 'manual', ip: device.ip };
84 |
85 | devices.push({
86 | name: deviceInfo.robotname,
87 | blid: deviceInfo.blid,
88 | password: deviceInfo.password,
89 | sw: deviceInfo.sw,
90 | sku: deviceInfo.sku,
91 | ...networkInfo,
92 | });
93 | }
94 | }
95 | payload;
96 | } else {
97 | const email = payload.email;
98 | const password = payload.password;
99 | for (const device of await getPasswordCloud(email, password)) {
100 | const networkInfo: ipInfo = await new Promise(resolve =>
101 | lookup(`iRobot-${device.blid}.local`, 4).then(() => resolve({
102 | ipResolution: 'lookup', hostname: `iRobot-${device.blid}.local`,
103 | })).catch(() => resolve(undefined))) ??
104 | await new Promise(resolve =>
105 | lookup(`Roomba-${device.blid}.local`, 4).then(() => resolve({
106 | ipResolution: 'lookup', hostname: `Roomba-${device.blid}.local`,
107 | })).catch(() => resolve(undefined))) ??
108 | await new Promise(resolve =>
109 | getRobotByBlid(device.blid, (err) => err ? resolve(undefined) : resolve({ ipResolution: 'broadcast' }))) ??
110 | { ipResolution: 'manual', ip: '' };
111 |
112 | devices.push({
113 | name: device.name,
114 | blid: device.blid,
115 | password: device.password,
116 | sw: device.softwareVer,
117 | sku: device.sku,
118 | ...networkInfo,
119 | });
120 | }
121 | }
122 | return devices;
123 | });
124 |
125 | this.ready();
126 | }
127 | }
128 |
129 | (() => new PluginUiServer())();
130 | type Device = {
131 | name: string;
132 | blid: string;
133 | password: string;
134 | sw: string;
135 | sku: string;
136 | //publicInfo: PublicInfo;
137 | } & ipInfo;
138 | type ipInfo = {
139 | ipResolution: 'manual';
140 | ip: string;
141 | } | {
142 | ipResolution: 'lookup';
143 | hostname: string;
144 | } | {
145 | ipResolution: 'broadcast';
146 | };
--------------------------------------------------------------------------------
/homebridge-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "esModuleInterop": true,
5 | "target": "ES2018", // ~node10
6 | "module": "commonjs",
7 | "skipLibCheck": true,
8 | "sourceMap": true
9 | },
10 | "include": [
11 | "server.ts"
12 | ]
13 | }
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src"
4 | ],
5 | "ext": "ts",
6 | "ignore": [],
7 | "exec": "tsc && homebridge -I -D",
8 | "signal": "SIGTERM",
9 | "env": {
10 | "NODE_OPTIONS": "--trace-warnings"
11 | }
12 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "displayName": "Homebridge iRobot",
3 | "name": "homebridge-irobot",
4 | "version": "4.0.0-beta.11",
5 | "description": "A homebridge plugin for controlling iRobot devices",
6 | "license": "Apache-2.0",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/bloomkd46/homebridge-iRobot.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/bloomkd46/homebridge-iRobot/issues"
13 | },
14 | "engines": {
15 | "node": ">=14.18.1",
16 | "homebridge": ">=1.3.5"
17 | },
18 | "main": "dist/index.js",
19 | "scripts": {
20 | "lint": "eslint src/**.ts --max-warnings=0 --fix",
21 | "watch": "npm run build && npm link && nodemon",
22 | "prebuild": "rimraf ./dist ./homebridge-ui/server.js ./homebridge-ui/public/bundle.js",
23 | "build": "tsc --build && browserify --outfile ./homebridge-ui/public/bundle.js ./homebridge-ui/public/index.js --debug",
24 | "postbuild": "rimraf ./homebridge-ui/server.d.ts ./homebridge-ui/public/index.d.ts ./homebridge-ui/public/index.js ./homebridge-ui/tsconfig.tsbuildinfo ./homebridge-ui/public/tsconfig.tsbuildinfo",
25 | "prepublishOnly": "npm run lint && npm run build"
26 | },
27 | "keywords": [
28 | "homebridge-plugin",
29 | "iRobot",
30 | "roomba",
31 | "homebridge",
32 | "vacuum"
33 | ],
34 | "dependencies": {
35 | "@bloomkd46/dorita980": "^1.2.0",
36 | "@homebridge/plugin-ui-utils": "^0.0.19",
37 | "ping": "^0.4.2"
38 | },
39 | "devDependencies": {
40 | "@types/bootstrap": "^4.6.1",
41 | "@types/node": "^16.18.10",
42 | "@types/ping": "^0.4.1",
43 | "@typescript-eslint/eslint-plugin": "^5.0.0",
44 | "@typescript-eslint/parser": "^5.0.0",
45 | "browserify": "^17.0.0",
46 | "eslint": "^8.0.1",
47 | "homebridge": "^1.3.5",
48 | "nodemon": "^2.0.13",
49 | "rimraf": "^3.0.2",
50 | "typedoc": "^0.23.24",
51 | "typescript": "^4.9.3"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src (Legacy)/V1/dorita980.d.ts:
--------------------------------------------------------------------------------
1 | //declare module 'dorita980' {
2 | export class Local {
3 | /**
4 | * The library send commands directly over wifi to your robot. You dont need an internet connection.
5 | * @param username your roomba's blid
6 | * @param password your roomba's password
7 | * @param ip your roomba's ip address
8 | * @param version (optional) your roomba's protocol version (1-3, defaults to 2)
9 | * @param interval (optional) interval in miliseconds to emith mission event (defaults to 800ms)
10 | */
11 | constructor(username: string, password: string, ip: string, version?: 2 | 3, interval?: number)
12 | /** Emitted on successful Connection. */
13 | on(event: 'connect', listener: () => void): this
14 | /** Emitted after a disconnection. */
15 | on(event: 'close', listener: () => void): this
16 | /** Emitted when the client goes offline. */
17 | on(event: 'offline', listener: () => void): this
18 | /** Emitted every time the Robot publishes a new message to the mqtt bus. */
19 | on(event: 'update', listener: (data: Data) => void): this
20 | /** Emitted every emitIntervalTime milliseconds with the mission data. (util for mapping in models with position reporting) */
21 | on(event: 'mission', listener: (data: cleanMissionStatus) => void): this
22 | /**
23 | * Emitted every time the Robot publish a new message to the mqtt bus.
24 | * Will print the Full robot state!
25 | */
26 | on(event: 'state', listener: (data: unknown) => void): this
27 | /**
28 | * @param event
29 | * @private
30 | */
31 | removeAllListeners(event?: string | symbol): this
32 |
33 | //--------------------------------------------------------------------------------------------------------------------------------------
34 | end(): void
35 |
36 | getTime(): Promise
37 | getBbrun(): Promise
38 | getLangs(): Promise
39 | getSys(): Promise
40 | getWirelessLastStatus(): Promise
41 | getWeek(): Promise
42 | getPreferences(waitForFields?: string[]): this
43 | getRobotState(waitForFields?: string[]): this
44 | getMission(calwaitForFields?: string[]): this
45 | getBasicMission(waitForFields?: string[]): this
46 | getWirelessConfig(): Promise
47 | getWirelessStatus(): Promise
48 | getCloudConfig(): Promise
49 | getSKU(): Promise
50 | start(): Promise<{ 'ok': null }>
51 | clean(): Promise<{ 'ok': null }>
52 | cleanRoom(callback?: (args) => Promise<{ 'ok': null }>): this
53 | pause(): Promise<{ 'ok': null }>
54 | stop(): Promise<{ 'ok': null }>
55 | resume(): Promise<{ 'ok': null }>
56 | dock(): Promise<{ 'ok': null }>
57 | find(): Promise<{ 'ok': null }>
58 | evac(): Promise<{ 'ok': null }>
59 | train(): Promise<{ 'ok': null }>
60 | setWeek(callback?: (args) => Promise<{ 'ok': null }>): this
61 | setPreferences(callback?: (args) => Promise<{ 'ok': null }>): this
62 | setCarpetBoostAuto(): Promise<{ 'ok': null }>
63 | setCarpetBoostPerformance(): Promise<{ 'ok': null }>
64 | setCarpetBoostEco(): Promise<{ 'ok': null }>
65 | setEdgeCleanOn(): Promise<{ 'ok': null }>
66 | setEdgeCleanOff(): Promise<{ 'ok': null }>
67 | setCleaningPassesAuto(): Promise<{ 'ok': null }>
68 | setCleaningPassesOne(): Promise<{ 'ok': null }>
69 | setCleaningPassesTwo(): Promise<{ 'ok': null }>
70 | setAlwaysFinishOn(): Promise<{ 'ok': null }>
71 | setAlwaysFinishOff(): Promise<{ 'ok': null }>
72 | }
73 |
74 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
75 | export interface fullRobotState { }
76 |
77 | interface cleanMissionStatus {
78 | cleanMissionStatus:
79 | {
80 | cycle: string;
81 | phase: string;
82 | expireM: number;
83 | rechrgM: number;
84 | error: number;
85 | notReady: number;
86 | mssnM: number;
87 | sqft: number;
88 | initiator: string;
89 | nMssn: number;
90 | };
91 | pose: { theta: number; point: { x: number; y: number } };
92 | }
93 | interface Data {
94 | state:
95 | {
96 | reported:
97 | {
98 | soundVer: string;
99 | uiSwVer: string;
100 | navSwVer: string;
101 | wifiSwVer: string;
102 | mobilityVer: string;
103 | bootloaderVer: string;
104 | umiVer: string;
105 | softwareVer: string;
106 | };
107 | };
108 | }
109 | //}
--------------------------------------------------------------------------------
/src (Legacy)/V1/getRoombas.ts:
--------------------------------------------------------------------------------
1 | import child_process from 'child_process';
2 | import { Logger, PlatformConfig } from 'homebridge';
3 | export function getRoombas(email: string, password: string, log: Logger, config: PlatformConfig): Robot[] {
4 | //child_process.execSync('chmod -R 755 "' + __dirname + '/scripts"');
5 | let robots: Robot[] = [];
6 |
7 | if(config.manualDiscovery){
8 | log.info('Using manual discovery due to config');
9 | robots = config.roombas || [];
10 | }else{
11 | log.info('Logging into iRobot...');
12 | const Robots = child_process.execFileSync(__dirname + '/scripts/getRoombaCredentials.js', [email, password]).toString();
13 | try{
14 | robots = JSON.parse(Robots);
15 | log.debug(Robots);
16 | }catch(e){
17 | log.error('Faild to login to iRobot, see below for details');
18 | log.error(Robots);
19 | }
20 | }
21 | const badRoombas: number[] = [];
22 | robots.forEach(robot => {
23 | if(robot.autoConfig || !config.autoDiscovery){
24 | log.info('Configuring roomba:', robot.name);
25 | const robotIP = child_process.execFileSync(__dirname + '/scripts/getRoombaIP.js', [robot.blid]).toString();
26 | try{
27 | const robotInfo = JSON.parse(robotIP);
28 | log.debug(robotIP);
29 | robot.ip = robotInfo.ip;
30 | delete robotInfo.ip;
31 | robot.model = getModel(robotInfo.sku);
32 | robot.multiRoom = getMultiRoom(robot.model);
33 | robot.info = robotInfo;
34 | /*if(robotInfo.sku.startsWith('m6')){
35 | badRoombas.push(robots.indexOf(robot));
36 | }*/
37 | }catch(e){
38 | try{
39 | log.error('Failed to configure roomba:', robot.name, 'see below for details');
40 | log.error(robotIP);
41 | } finally{
42 | badRoombas.push(robots.indexOf(robot));
43 | }
44 | }
45 | } else {
46 | log.info('Skipping configuration for roomba:', robot.name, 'due to config');
47 | }
48 | });
49 | for(const roomba of badRoombas){
50 | log.warn('Disabling Unconfigured Roomba:', robots[roomba].name);
51 | try{
52 | robots.splice(roomba);
53 | }catch(e){
54 | log.error('Failed To Disable Unconfigured Roomba:', robots[roomba].name, 'see below for details');
55 | log.error(e as string);
56 | }
57 | }
58 | return robots;
59 |
60 | }
61 | function getModel(sku: string):string{
62 | switch(sku.charAt(0)){
63 | case 'j':
64 | case 'i':
65 | case 's':
66 | return sku.substring(0, 2);
67 | case 'R':
68 | return sku.substring(1, 4);
69 | default:
70 | return sku;
71 | }
72 | }
73 | function getMultiRoom(model:string){
74 | switch(model.charAt(0)){
75 | case 's':
76 | case 'j':
77 | case 'i':
78 | if(parseInt(model.charAt(1)) > 4){
79 | return true;
80 | } else{
81 | return false;
82 | }
83 | default:
84 | return false;
85 | }
86 | }
87 | export interface Robot {
88 | 'name': string;
89 | 'blid': string;
90 | 'password': string;
91 | 'autoConfig'?: boolean;
92 | 'ip': string;
93 | 'model': string;
94 | 'multiRoom': boolean;
95 | 'info': {
96 | 'serialNum'?: string;
97 | 'ver'?: string;
98 | 'hostname'?: string;
99 | 'robotname'?: string;
100 | 'robotid'?: string;
101 | 'mac'?: string;
102 | 'sw': string;
103 | 'sku'?: string;
104 | 'nc'?: number;
105 | 'proto'?: string;
106 | 'cap'?: unknown;
107 | };
108 | }
109 |
--------------------------------------------------------------------------------
/src (Legacy)/V1/platform.ts:
--------------------------------------------------------------------------------
1 | import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2 |
3 | import { PLATFORM_NAME, PLUGIN_NAME } from '../settings';
4 | import { iRobotPlatformAccessory } from './platformAccessory';
5 | import { getRoombas } from './getRoombas';
6 |
7 | /**
8 | * HomebridgePlatform
9 | * This class is the main constructor for your plugin, this is where you should
10 | * parse the user config and discover/register accessories with Homebridge.
11 | */
12 | export class iRobotPlatform implements DynamicPlatformPlugin {
13 | public readonly Service: typeof Service = this.api.hap.Service;
14 | public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
15 |
16 |
17 | // this is used to track restored cached accessories
18 | public readonly accessories: PlatformAccessory[] = [];
19 |
20 | constructor(
21 | public readonly log: Logger,
22 | public readonly config: PlatformConfig,
23 | public readonly api: API,
24 | ) {
25 | this.log.debug('Finished initializing platform:', this.config.name);
26 |
27 | // When this event is fired it means Homebridge has restored all cached accessories from disk.
28 | // Dynamic Platform plugins should only register new accessories after this event was fired,
29 | // in order to ensure they weren't added to homebridge already. This event can also be used
30 | // to start discovery of new accessories.
31 | this.api.on('didFinishLaunching', () => {
32 | log.debug('Executed didFinishLaunching callback');
33 | // run the method to discover / register your devices as accessories
34 | this.discoverDevices();
35 | });
36 | }
37 |
38 | /**
39 | * This function is invoked when homebridge restores cached accessories from disk at startup.
40 | * It should be used to setup event handlers for characteristics and update respective values.
41 | */
42 | configureAccessory(accessory: PlatformAccessory) {
43 | this.log.info('Loading accessory from cache:', accessory.displayName);
44 |
45 | // add the restored accessory to the accessories cache so we can track if it has already been registered
46 | this.accessories.push(accessory);
47 | }
48 |
49 | /**
50 | * This is an example method showing how to register discovered accessories.
51 | * Accessories must only be registered once, previously created accessories
52 | * must not be registered again to prevent "duplicate UUID" errors.
53 | */
54 | discoverDevices() {
55 | if(this.config.email === undefined || this.config.password === undefined){
56 | this.log.warn('No email or password provided. Exiting setup');
57 | return;
58 | }
59 |
60 | // EXAMPLE ONLY
61 | // A real plugin you would discover accessories from the local network, cloud services
62 | // or a user-defined array in the platform config.
63 |
64 | // loop over the discovered devices and register each one if it has not already been registered
65 | for (const device of getRoombas(this.config.email, this.config.password, this.log, this.config)) {
66 | if(this.config.disableMultiRoom){
67 | device.multiRoom = false;
68 | }
69 | // generate a unique id for the accessory this should be generated from
70 | // something globally unique, but constant, for example, the device serial
71 | // number or MAC address
72 | const uuid = this.api.hap.uuid.generate(device.blid);
73 |
74 | // see if an accessory with the same uuid has already been registered and restored from
75 | // the cached devices we stored in the `configureAccessory` method above
76 | const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
77 |
78 | if (existingAccessory) {
79 | // the accessory already exists
80 | this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
81 |
82 | // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
83 | // existingAccessory.context.device = device;
84 | // this.api.updatePlatformAccessories([existingAccessory]);
85 |
86 | // create the accessory handler for the restored accessory
87 | // this is imported from `platformAccessory.ts`
88 | new iRobotPlatformAccessory(this, existingAccessory, device);
89 |
90 | // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.:
91 | // remove platform accessories when no longer present
92 | // this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
93 | // this.log.info('Removing existing accessory from cache:', existingAccessory.displayName);
94 | } else {
95 | if (device.ip === 'undefined') {
96 | return;
97 | }
98 | // the accessory does not yet exist, so we need to create it
99 | this.log.info('Adding new accessory:', device.name);
100 |
101 | // create a new accessory
102 | const accessory = new this.api.platformAccessory(device.name, uuid);
103 |
104 | // store a copy of the device object in the `accessory.context`
105 | // the `context` property can be used to store any data about the accessory you may need
106 | accessory.context.device = device;
107 |
108 | // create the accessory handler for the newly create accessory
109 | // this is imported from `platformAccessory.ts`
110 | new iRobotPlatformAccessory(this, accessory, device);
111 |
112 | // link the accessory to your platform
113 | this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
114 | }
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/src (Legacy)/V1/scripts/getRoombaCredentials.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | const request = require('request');
6 |
7 | if (!process.argv[2] || !process.argv[3]) {
8 | console.log('Usage: npm run get-password-cloud [Gigya API Key]');
9 | process.exit();
10 | }
11 |
12 | const username = process.argv[2];
13 | const password = process.argv[3];
14 | const apiKey = '3_rWtvxmUKwgOzu3AUPTMLnM46lj-LxURGflmu5PcE_sGptTbD-wMeshVbLvYpq01K';
15 |
16 | const gigyaLoginOptions = {
17 | 'method': 'POST',
18 | 'uri': 'https://accounts.us1.gigya.com/accounts.login',
19 | 'json': true,
20 | 'qs': {
21 | 'apiKey': apiKey,
22 | 'targetenv': 'mobile',
23 | 'loginID': username,
24 | 'password': password,
25 | 'format': 'json',
26 | 'targetEnv': 'mobile'
27 | },
28 | 'headers': {
29 | 'Connection': 'close'
30 | }
31 | };
32 |
33 | request(gigyaLoginOptions, loginGigyaResponseHandler);
34 |
35 | function loginGigyaResponseHandler (error, response, body) {
36 | if (error) {
37 | console.log('Fatal error login into Gigya API. Please check your credentials or Gigya API Key.');
38 | console.log(error);
39 | process.exit(0);
40 | }
41 |
42 | if (response.statusCode === 401 || response.statusCode === 403) {
43 | console.log('Authentication error. Check your credentials.');
44 | console.log(response);
45 | process.exit(0);
46 | } else if (response.statusCode === 400) {
47 | console.log(response);
48 | process.exit(0);
49 | } else if (response.statusCode === 200) {
50 | if (body && body.statusCode && body.statusCode === 403) {
51 | console.log('Authentication error. Please check your credentials.');
52 | console.log(body);
53 | process.exit(0);
54 | }
55 | if (body && body.statusCode && body.statusCode === 400) {
56 | console.log('Error login into Gigya API.');
57 | console.log(body);
58 | process.exit(0);
59 | }
60 | if (body && body.statusCode && body.statusCode === 200 && body.errorCode === 0 && body.UID && body.UIDSignature && body.signatureTimestamp && body.sessionInfo && body.sessionInfo.sessionToken) {
61 | const iRobotLoginOptions = {
62 | 'method': 'POST',
63 | 'uri': 'https://unauth2.prod.iot.irobotapi.com/v2/login',
64 | 'json': true,
65 | 'body': {
66 | 'app_id': 'ANDROID-C7FB240E-DF34-42D7-AE4E-A8C17079A294',
67 | 'assume_robot_ownership': 0,
68 | 'gigya': {
69 | 'signature': body.UIDSignature,
70 | 'timestamp': body.signatureTimestamp,
71 | 'uid': body.UID
72 | }
73 | },
74 | 'headers': {
75 | 'Connection': 'close'
76 | }
77 | };
78 | request(iRobotLoginOptions, loginIrobotResponseHandler);
79 | } else {
80 | console.log('Error login into iRobot account. Missing fields in login response.');
81 | console.log(body);
82 | process.exit(0);
83 | }
84 | } else {
85 | console.log('Unespected response. Checking again...');
86 | }
87 | }
88 |
89 | function loginIrobotResponseHandler (error, response, body) {
90 | let index = 0;
91 | if (error) {
92 | console.log('Fatal error login into iRobot account. Please check your credentials or API Key.');
93 | console.log(error);
94 | process.exit(0);
95 | }
96 | if (body && body.robots) {
97 | const robotCount = Object.keys(body.robots).length;
98 | //console.log('Found ' + robotCount + ' robot(s)!');
99 | console.log(body.robots);
100 | console.log('[')
101 | Object.keys(body.robots).map(function (r) {
102 | index++;
103 | console.log('{"name": "'+ body.robots[r].name +'", "blid": "'+ r + '", "password": "'+ body.robots[r].password + '", "ip": "'+ body.robots[r].ip + '"}');
104 | /*
105 | console.log('Robot "' + body.robots[r].name + '" (sku: ' + body.robots[r].sku + ' SoftwareVer: ' + body.robots[r].softwareVer + '):');
106 | console.log('BLID=> ' + r);
107 | console.log('Password=> ' + body.robots[r].password + ' <= Yes, all this string.');
108 | console.log('');
109 | */
110 | if (index == robotCount) {
111 | console.log(']');
112 | }else {
113 | console.log(',')
114 | }
115 | });
116 | //console.log('Use this credentials in dorita980 lib :)');
117 | } else {
118 | console.log('Fatal error login into iRobot account. Please check your credentials or API Key.');
119 | console.log(body);
120 | process.exit(0);
121 | }
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/src (Legacy)/V1/scripts/getRoombaIP.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | const dgram = require('dgram');
6 | const child_process = require('child_process');
7 |
8 | if(!process.argv[2]){
9 | console.log('Error: No blid supplied');
10 | process.exit(1);
11 | }
12 |
13 | const blid = process.argv[2];
14 | const attempt = process.argv[3] || 1
15 |
16 | if(attempt>5){
17 | console.log('No Roomba Found With Blid:', blid);
18 | process.exit(0);
19 | }
20 | getRobotIP();
21 |
22 | function getRobotIP () {
23 | const server = dgram.createSocket('udp4');
24 |
25 | server.on('error', (err) => {
26 | console.error(err);
27 | });
28 |
29 | server.on('message', (msg) => {
30 | try {
31 | let parsedMsg = JSON.parse(msg);
32 | if (parsedMsg.hostname && parsedMsg.ip && ((parsedMsg.hostname.split('-')[0] === 'Roomba') || (parsedMsg.hostname.split('-')[0] === 'iRobot'))) {
33 | if(parsedMsg.hostname.split('-')[1] === blid){
34 | server.close();
35 | console.log(JSON.stringify(parsedMsg));
36 | process.exit(0);
37 | }
38 | }
39 | } catch (e) {}
40 | });
41 |
42 | server.on('listening', () => {
43 | setTimeout(()=>{
44 | console.log(child_process.execFileSync(__dirname + '/getRoombaIP.js', [blid, attempt+1]).toString());
45 | process.exit(0);
46 | }, 5000);
47 | });
48 |
49 | server.bind(function () {
50 | const message = new Buffer.from('irobotmcs');
51 | server.setBroadcast(true);
52 | server.send(message, 0, message.length, 5678, '255.255.255.255');
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/src (Legacy)/V2/RoomManager.ts:
--------------------------------------------------------------------------------
1 | import { PlatformAccessory, Service } from 'homebridge';
2 | import { iRobotPlatform } from './platform';
3 | import { RoombaV3 } from './RoombaController';
4 |
5 | class RoomManager {
6 | private logPrefix = '[' + this.accessory.displayName + ']';
7 | constructor(
8 | private readonly accessory: PlatformAccessory,
9 | private readonly platform: iRobotPlatform,
10 | private readonly roomba: RoombaV3,
11 | ) {
12 | //
13 | }
14 |
15 | updateMap(lastCommandFull: LastCommand) {
16 | if (lastCommandFull.pmap_id && lastCommandFull.regions && lastCommandFull.user_pmapv_id) {
17 | const lastCommand: LastCommandMap = {
18 | 'pmap_id': lastCommandFull.pmap_id,
19 | 'regions': lastCommandFull.regions,
20 | 'user_pmapv_id': lastCommandFull.user_pmapv_id,
21 | };
22 | if (this.accessory.context.maps) {
23 | let mapIndex: number | undefined = undefined;
24 | this.accessory.context.maps.find((map: LastCommandMap, index: number) => {
25 | if (map.pmap_id === lastCommand.pmap_id) {
26 | mapIndex = index;
27 | return true;
28 | } else {
29 | return false;
30 | }
31 | });
32 | if (mapIndex) {
33 | for (const region of lastCommand.regions) {
34 | if (!this.accessory.context.maps[mapIndex].contains(region)) {
35 | this.platform.log.info(this.logPrefix, 'Adding new region:', region);
36 | this.accessory.context.maps[mapIndex].push(region);
37 | }
38 | }
39 | } else {
40 | this.platform.log.info(this.logPrefix, 'Adding new map:', lastCommand);
41 | this.accessory.context.maps.push(lastCommand);
42 | }
43 | } else {
44 | this.platform.log.info(this.logPrefix, 'Initiating Room-By-Room Support with map:', lastCommand);
45 | this.accessory.context.maps = [lastCommand];
46 | }
47 | }
48 | }
49 |
50 | StartRoomba(map: LastCommandMap) {
51 | this.roomba.cleanRoom(map);
52 | }
53 |
54 | GetName(region: { region_id: string; type: 'zid' | 'rid' }, map: number): string {
55 | return `map ${map} ${region.type === 'zid' ? 'Zone' : 'Room'} ${region.region_id}`;
56 | }
57 | }
58 | export class switches extends RoomManager {
59 | constructor(
60 | accessory: PlatformAccessory,
61 | platform: iRobotPlatform,
62 | roomba: RoombaV3,
63 | private readonly service: Service,
64 | ) {
65 | super(accessory, platform, roomba);
66 | /*for (const map of accessory.context.maps) {
67 | const index = accessory.context.maps.indexOf(map);
68 | for (const region of map.regions) {
69 | const service = accessory.getService(this.GetName(region, index)) ||
70 | accessory.addService(platform.Service.Switch, this.GetName(region, index), this.GetName(region, index));
71 |
72 | }
73 | }*/
74 | }
75 | }
76 | interface LastCommand {
77 | pmap_id?: string;
78 | regions?: [
79 | { region_id: string; type: 'zid' | 'rid' }
80 | ];
81 | user_pmapv_id?: string;
82 | }
83 | interface LastCommandMap {
84 | pmap_id: string;
85 | regions: [
86 | { region_id: string; type: 'zid' | 'rid' }
87 | ];
88 | user_pmapv_id: string;
89 | }
--------------------------------------------------------------------------------
/src (Legacy)/V2/RoombaController.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from 'homebridge';
2 | import { EventEmitter } from 'stream';
3 |
4 | import { Local } from '@bloomkd46/dorita980';
5 |
6 |
7 | export class RoombaV1 {
8 | public roomba = Local(this.blid, this.password, this.ip, 1);
9 |
10 | constructor(private readonly blid: string, private readonly password: string, private readonly ip: string) {
11 | process.env.ROBOT_CIPHERS = 'AES128-SHA256';
12 | }
13 |
14 | async start() {
15 | await this.roomba.start();
16 | }
17 |
18 | async pause() {
19 | await this.roomba.pause();
20 | }
21 |
22 | async stop() {
23 | await this.roomba.stop();
24 | }
25 |
26 | async resume() {
27 | await this.roomba.resume();
28 | }
29 |
30 | async dock() {
31 | await this.roomba.dock();
32 | }
33 |
34 | async getMission(): Promise {
35 | return new Promise((resolve, reject) => {
36 | this.roomba.getMission().then(mission => {
37 | mission.ok.missionFlags.full = !mission.ok.missionFlags.binRemoved;
38 | mission.ok.missionFlags.present = mission.ok.missionFlags.binPresent;
39 | resolve(Object.assign(mission.ok, mission.ok.missionFlags));
40 | }).catch(err => reject(err));
41 | });
42 | }
43 | }
44 | export interface MissionV1 {
45 | ok: {
46 | //flags: number;
47 | cycle: 'none' | 'clean';
48 | phase: 'charge' | 'stuck' | 'run';
49 | batPct: number;
50 | idle: boolean;
51 | full: boolean;
52 | //binRemoved: boolean;
53 | present: boolean;
54 | //beeping: boolean;
55 | //missionFlags: { idle: boolean; binFull: boolean; binRemoved: boolean; beeping: boolean };
56 | //notReadyMsg: 'Ready';
57 | };
58 | //id: number;
59 |
60 | }
61 |
62 | //------------------------------------------------------------------------------------------------------------------------------------------
63 |
64 | export class RoombaV2 extends EventEmitter {
65 | public roomba?: Local;
66 | private timeout?: NodeJS.Timeout;
67 | private keepAlive = this.refreshInterval === -1;
68 |
69 | constructor(private readonly blid: string, private readonly password: string, private readonly ip: string,
70 | private readonly refreshInterval: number, private readonly log: Logger, private readonly logPrefix: string) {
71 | super();
72 | process.env.ROBOT_CIPHERS = 'AES128-SHA256';
73 | }
74 |
75 | connect(): Promise {
76 | if (this.timeout) {
77 | clearTimeout(this.timeout);
78 | this.timeout = undefined;
79 | }
80 | return new Promise((resolve, reject) => {
81 | if (this.roomba) {
82 | resolve(this.roomba);
83 | } else {
84 | this.log.debug(this.logPrefix, 'Connecting...');
85 | this.roomba = new Local(this.blid, this.password, this.ip, 2);
86 | this.roomba.on('offline', () => {
87 | this.log.debug(this.logPrefix, 'Offline');
88 | this.roomba.end();
89 | reject('Roomba Offline');
90 | }).on('close', () => {
91 | this.log.debug(this.logPrefix, 'Disconnected');
92 | this.roomba = undefined;
93 | }).on('connect', () => {
94 | this.log.debug(this.logPrefix, 'Connected');
95 | resolve(this.roomba);
96 | }).on('state', (state) => {
97 | this.emit('update', Object.assign(state, state.cleanMissionStatus, state.bin));
98 | });
99 | }
100 | });
101 | }
102 |
103 | disconnect(roomba: Local) {
104 | if (this.timeout) {
105 | clearTimeout(this.timeout);
106 | this.timeout = undefined;
107 | }
108 | if (!this.keepAlive) {
109 | this.timeout = setTimeout(() => {
110 | this.log.debug(this.logPrefix, 'Disconnecting...');
111 | roomba.end();
112 | }, this.refreshInterval || 5000);
113 |
114 | }
115 | }
116 |
117 | end() {
118 | this.connect().then((roomba) => {
119 | roomba.end();
120 | });
121 | }
122 |
123 | clean() {
124 | this.connect().then(async (roomba) => {
125 | await roomba.clean();
126 | this.disconnect(roomba);
127 | });
128 | }
129 |
130 | pause() {
131 | this.connect().then(async (roomba) => {
132 | await roomba.pause();
133 | this.disconnect(roomba);
134 | });
135 | }
136 |
137 | stop() {
138 | this.connect().then(async (roomba) => {
139 | await roomba.stop();
140 | this.disconnect(roomba);
141 | });
142 | }
143 |
144 | resume() {
145 | this.connect().then(async (roomba) => {
146 | await roomba.resume();
147 | this.disconnect(roomba);
148 | });
149 | }
150 |
151 | dock() {
152 | this.connect().then(async (roomba) => {
153 | await roomba.dock();
154 | this.disconnect(roomba);
155 | });
156 | }
157 |
158 | find() {
159 | this.connect().then(async (roomba) => {
160 | await roomba.find();
161 | this.disconnect(roomba);
162 | });
163 | }
164 |
165 | async getMission(): Promise {
166 | return new Promise((resolve, reject) => {
167 | setTimeout(() => {
168 | reject('Operation Timed out');
169 | }, 3000);
170 | this.connect().then(async (roomba) => {
171 | roomba.getRobotState(['cleanMissionStatus', 'bin', 'batPct'])
172 | .then(state => resolve(Object.assign(state, state.cleanMissionStatus, state.bin)))
173 | .catch(err => reject(err));
174 | this.disconnect(roomba);
175 | }).catch(err => reject(err));
176 | });
177 | }
178 | }
179 | export interface MissionV2 {
180 | cycle: 'none' | 'clean';
181 | phase: 'charge' | 'stuck' | 'run' | 'hmUsrDock';
182 | batPct: number;
183 | binPresent: boolean;
184 | binFull: boolean;
185 | }
186 | export class RoombaV3 extends EventEmitter {
187 | public roomba?: Local;
188 | private timeout?: NodeJS.Timeout;
189 | private keepAlive = this.refreshInterval === -1;
190 |
191 | constructor(private readonly blid: string, private readonly password: string, private readonly ip: string, private readonly sku: string,
192 | private readonly refreshInterval: number, private readonly log: Logger, private readonly logPrefix: string) {
193 | super();
194 | process.env.ROBOT_CIPHERS = this.sku.startsWith('j') ? 'TLS_AES_256_GCM_SHA384' : 'AES128-SHA256';
195 | }
196 |
197 | connect(): Promise {
198 | if (this.timeout) {
199 | clearTimeout(this.timeout);
200 | this.timeout = undefined;
201 | }
202 | return new Promise((resolve, reject) => {
203 | if (this.roomba) {
204 | resolve(this.roomba);
205 | } else {
206 | this.log.debug('Connecting...');
207 | this.roomba = new Local(this.blid, this.password, this.ip, 2);
208 | this.roomba.on('offline', () => {
209 | this.log.debug(this.logPrefix, 'Offline');
210 | this.roomba.end();
211 | reject('Roomba Offline');
212 | }).on('close', () => {
213 | this.log.debug(this.logPrefix, 'Disconnected');
214 | this.roomba = undefined;
215 | }).on('connect', () => {
216 | this.log.debug(this.logPrefix, 'Connected');
217 | resolve(this.roomba);
218 | }).on('update', (state) => {
219 | this.emit('state', Object.assign(state, state.cleanMissionStatus, state.bin));
220 | });
221 | }
222 | });
223 | }
224 |
225 | disconnect(roomba: Local) {
226 | if (this.timeout) {
227 | clearTimeout(this.timeout);
228 | this.timeout = undefined;
229 | }
230 | if (!this.keepAlive) {
231 | this.timeout = setTimeout(() => {
232 | this.log.debug(this.logPrefix, 'Disconnecting...');
233 | roomba.end();
234 | }, this.refreshInterval || 5000);
235 | }
236 | }
237 |
238 | end() {
239 | this.connect().then((roomba) => {
240 | roomba.end();
241 | });
242 | }
243 |
244 | clean() {
245 | this.connect().then(async (roomba) => {
246 | await roomba.clean();
247 | this.disconnect(roomba);
248 | });
249 | }
250 |
251 | cleanRoom(map) {
252 | map.ordered = 1;
253 | this.connect().then(async (roomba) => {
254 | await roomba.cleanRoom(map);
255 | this.disconnect(roomba);
256 | });
257 | }
258 |
259 | pause() {
260 | this.connect().then(async (roomba) => {
261 | await roomba.pause();
262 | this.disconnect(roomba);
263 | });
264 | }
265 |
266 | stop() {
267 | this.connect().then(async (roomba) => {
268 | await roomba.stop();
269 | this.disconnect(roomba);
270 | });
271 | }
272 |
273 | resume() {
274 | this.connect().then(async (roomba) => {
275 | await roomba.resume();
276 | this.disconnect(roomba);
277 | });
278 | }
279 |
280 | dock() {
281 | this.connect().then(async (roomba) => {
282 | await roomba.dock();
283 | this.disconnect(roomba);
284 | });
285 | }
286 |
287 | find() {
288 | this.connect().then(async (roomba) => {
289 | await roomba.find();
290 | this.disconnect(roomba);
291 | });
292 | }
293 |
294 | async getMission(): Promise {
295 | return new Promise((resolve, reject) => {
296 | setTimeout(() => {
297 | reject('Operation Timed out');
298 | }, 3000);
299 | this.connect().then(async (roomba) => {
300 | roomba.getRobotState(['cleanMissionStatus', 'bin', 'batPct'])
301 | .then(state => resolve(Object.assign(state, state.cleanMissionStatus, state.bin)))
302 | .catch(err => reject(err));
303 | this.disconnect(roomba);
304 | }).catch(err => reject(err));
305 | });
306 | }
307 | }
308 | export interface MissionV3 {
309 | cycle: 'none' | 'clean';
310 | phase: 'charge' | 'stuck' | 'run' | 'hmUsrDock';
311 | batPct: number;
312 | binPresent: boolean;
313 | binFull: boolean;
314 | lastCommand?: {
315 | pmap_id: string | null;
316 | regions: [{
317 | region_id: string; type: 'rid' | 'zid';
318 | },
319 | ] | null;
320 | user_pmapv_id: string | null;
321 | } | null;
322 | }
323 | export interface Map {
324 | ordered: 1;
325 | pmap_id: string;
326 | regions: [
327 | { region_id: string; type: 'rid' | 'zid'; },
328 | ];
329 | user_pmapv_id: string;
330 | }
--------------------------------------------------------------------------------
/src (Legacy)/V2/platform.ts:
--------------------------------------------------------------------------------
1 | import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2 |
3 | import { PLATFORM_NAME, PLUGIN_NAME } from '../settings';
4 | //import { iRobotPlatformAccessory } from '../V1/platformAccessory';
5 | //import { getRoombas } from '../V1/getRoombas';
6 | import { getRoombas } from './getRoombas';
7 | import * as platformAccessory from './platformAccessory';
8 |
9 | /**
10 | * HomebridgePlatform
11 | * This class is the main constructor for your plugin, this is where you should
12 | * parse the user config and discover/register accessories with Homebridge.
13 | */
14 | export class iRobotPlatform implements DynamicPlatformPlugin {
15 | public readonly Service: typeof Service = this.api.hap.Service;
16 | public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
17 |
18 |
19 | // this is used to track restored cached accessories
20 | public readonly accessories: PlatformAccessory[] = [];
21 |
22 | constructor(
23 | public readonly log: Logger,
24 | public readonly config: PlatformConfig,
25 | public readonly api: API,
26 | ) {
27 | this.log.debug('Finished initializing platform:', this.config.name);
28 |
29 | // When this event is fired it means Homebridge has restored all cached accessories from disk.
30 | // Dynamic Platform plugins should only register new accessories after this event was fired,
31 | // in order to ensure they weren't added to homebridge already. This event can also be used
32 | // to start discovery of new accessories.
33 | this.api.on('didFinishLaunching', () => {
34 | log.debug('Executed didFinishLaunching callback');
35 | // run the method to discover / register your devices as accessories
36 | this.discoverDevices();
37 | });
38 | }
39 |
40 | /**
41 | * This function is invoked when homebridge restores cached accessories from disk at startup.
42 | * It should be used to setup event handlers for characteristics and update respective values.
43 | */
44 | configureAccessory(accessory: PlatformAccessory) {
45 | this.log.info('Loading accessory from cache:', accessory.displayName);
46 |
47 | // add the restored accessory to the accessories cache so we can track if it has already been registered
48 | if (accessory.context.pluginVersion === undefined || accessory.context.pluginVersion < 3) {
49 | this.log.warn('Removing Old Accessory:', accessory.displayName);
50 | this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
51 | } else {
52 | this.accessories.push(accessory);
53 | }
54 | }
55 |
56 | /**
57 | * This is an example method showing how to register discovered accessories.
58 | * Accessories must only be registered once, previously created accessories
59 | * must not be registered again to prevent "duplicate UUID" errors.
60 | */
61 | discoverDevices() {
62 | // loop over the discovered devices and register each one if it has not already been registered
63 | this.log.info('Logging into iRobot...');
64 | getRoombas(this.config, this.log).then(devices => {
65 | for (const device of devices) {
66 | //this.log.debug('Configuring device: \n', JSON.stringify(device));
67 | // generate a unique id for the accessory this should be generated from
68 | // something globally unique, but constant, for example, the device serial
69 | // number or MAC address
70 | const uuid = this.api.hap.uuid.generate(device.blid);
71 | //const accessoryType = 'iRobotPlatformAccesoryV'+device.ver;
72 | // see if an accessory with the same uuid has already been registered and restored from
73 | // the cached devices we stored in the `configureAccessory` method above
74 | const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
75 |
76 | if (existingAccessory) {
77 | // the accessory already exists
78 | this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
79 |
80 | // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
81 | // existingAccessory.context.device = device;
82 | // this.api.updatePlatformAccessories([existingAccessory]);
83 |
84 | // create the accessory handler for the restored accessory
85 | // this is imported from `platformAccessory.ts`
86 | //new iRobotPlatformAccessory(this, existingAccessory, device);
87 | //new platformAccessory[accessoryType](this, existingAccessory);
88 | switch (device.swMajor){
89 | case 1:
90 | new platformAccessory.iRobotPlatformAccessoryV1(this, existingAccessory);
91 | break;
92 | case 2:
93 | new platformAccessory.iRobotPlatformAccessoryV2(this, existingAccessory);
94 | break;
95 | case 3:
96 | new platformAccessory.iRobotPlatformAccessoryV3(this, existingAccessory);
97 | break;
98 | }
99 | // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.:
100 | // remove platform accessories when no longer present
101 | // this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
102 | // this.log.info('Removing existing accessory from cache:', existingAccessory.displayName);
103 | } else {
104 | // the accessory does not yet exist, so we need to create it
105 | this.log.info('Adding new accessory:', device.name);
106 |
107 | // create a new accessory
108 | const accessory = new this.api.platformAccessory(device.name, uuid);
109 |
110 | // store a copy of the device object in the `accessory.context`
111 | // the `context` property can be used to store any data about the accessory you may need
112 | accessory.context.device = device;
113 | accessory.context.pluginVersion = 3;
114 | // create the accessory handler for the newly create accessory
115 | // this is imported from `platformAccessory.ts`
116 | //new iRobotPlatformAccessory(this, accessory, device);
117 | //new platformAccessory[accessoryType](this, accessory);
118 | switch (device.ver){
119 | case '1':
120 | new platformAccessory.iRobotPlatformAccessoryV1(this, accessory);
121 | break;
122 | case '2':
123 | new platformAccessory.iRobotPlatformAccessoryV2(this, accessory);
124 | break;
125 | case '3':
126 | new platformAccessory.iRobotPlatformAccessoryV3(this, accessory);
127 | break;
128 | }
129 | // link the accessory to your platform
130 | this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
131 | }
132 | }
133 |
134 | }).catch((error) => {
135 | this.log.error(error);
136 | });
137 | }
138 | }
--------------------------------------------------------------------------------
/src (Legacy)/index.ts:
--------------------------------------------------------------------------------
1 | import { API } from 'homebridge';
2 |
3 | import { PLATFORM_NAME } from './settings';
4 | import { iRobotPlatform } from './V2/platform';
5 |
6 | /**
7 | * This method registers the platform with Homebridge
8 | */
9 | export = (api: API) => {
10 | api.registerPlatform(PLATFORM_NAME, iRobotPlatform);
11 | };
12 |
--------------------------------------------------------------------------------
/src (Legacy)/settings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
3 | */
4 | export const PLATFORM_NAME = 'iRobotPlatform';
5 |
6 | /**
7 | * This must match the name of your plugin as defined the package.json
8 | */
9 | export const PLUGIN_NAME = 'homebridge-irobot';
--------------------------------------------------------------------------------
/src/CustomCharacteristics.ts:
--------------------------------------------------------------------------------
1 | import { Formats, Perms } from 'homebridge';
2 |
3 |
4 | import type { HAP } from 'homebridge';
5 |
6 | export default function CustomCharacteristics(hap: HAP): { EnergyUsage; PanelStatus; } {
7 | const Characteristic = hap.Characteristic;
8 | class EnergyUsage extends Characteristic {
9 | static readonly UUID: string = '00000101-0000-0000-0000-000000000000';
10 | constructor() {
11 | super('Energy Usage', EnergyUsage.UUID, {
12 | format: Formats.FLOAT,
13 | maxValue: 15,
14 | minValue: 0,
15 | minStep: 0.1,
16 | unit: 'Amps',
17 | perms: [Perms.PAIRED_READ, Perms.NOTIFY],
18 | });
19 | this.value = this.getDefaultValue();
20 | }
21 | }
22 | class PanelStatus extends Characteristic {
23 | static readonly UUID: string = '00000102-0000-0000-0000-000000000000';
24 | constructor() {
25 | super('Status', PanelStatus.UUID, {
26 | format: Formats.STRING,
27 | perms: [Perms.PAIRED_READ, Perms.NOTIFY],
28 | });
29 | this.value = this.getDefaultValue();
30 | }
31 | }
32 | return { EnergyUsage, PanelStatus };
33 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { API } from 'homebridge';
2 |
3 | import { iRobotPlatform } from './platform';
4 | import { PLATFORM_NAME } from './settings';
5 |
6 |
7 | /**
8 | * This method registers the platform with Homebridge
9 | */
10 | export = (api: API) => {
11 | api.registerPlatform(PLATFORM_NAME, iRobotPlatform);
12 | };
13 |
--------------------------------------------------------------------------------
/src/platform.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'events';
2 | import fs from 'fs';
3 | import { API, APIEvent, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';
4 | import path from 'path';
5 |
6 | import V2Roomba from './accessories/V2';
7 | import V3Roomba from './accessories/V3';
8 | import CustomCharacteristics from './CustomCharacteristics';
9 | import { Config, Context, PLUGIN_NAME } from './settings';
10 |
11 |
12 | /**
13 | * HomebridgePlatform
14 | * This class is the main constructor for your plugin, this is where you should
15 | * parse the user config and discover/register accessories with Homebridge.
16 | */
17 | export class iRobotPlatform implements DynamicPlatformPlugin {
18 | public readonly Service: typeof Service = this.api.hap.Service;
19 | public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
20 | public readonly CustomCharacteristic = CustomCharacteristics(this.api.hap);
21 |
22 |
23 |
24 | /** this is used to track restored cached accessories */
25 | //private readonly cachedAccessories: PlatformAccessory[] = [];
26 | /** this is used to track which accessories have been restored from the cache */
27 | //private readonly restoredAccessories: PlatformAccessory[] = [];
28 | /** this is used to track which accessories have been added */
29 | //private readonly addedAccessories: PlatformAccessory[] = [];
30 | /** */
31 | private readonly accessories: PlatformAccessory[] = [];
32 | public config: PlatformConfig & Config;
33 | constructor(
34 | public readonly log: Logger,
35 | config: PlatformConfig,
36 | public readonly api: API,
37 | ) {
38 | this.config = config as unknown as PlatformConfig & Config;
39 | (this.api as unknown as EventEmitter).setMaxListeners(0);
40 | this.log.debug('Finished initializing platform:', this.config.name);
41 |
42 | // When this event is fired it means Homebridge has restored all cached accessories from disk.
43 | // Dynamic Platform plugins should only register new accessories after this event was fired,
44 | // in order to ensure they weren't added to homebridge already. This event can also be used
45 | // to start discovery of new accessories.
46 | this.api.on('didFinishLaunching', () => {
47 | log.debug('Executed didFinishLaunching callback');
48 |
49 | const projectDir = path.join(api.user.storagePath(), 'iRobot');
50 | const generalLogPath = path.join(projectDir, 'General.log');
51 | if (!fs.existsSync(projectDir)) {
52 | fs.mkdirSync(projectDir);
53 | }
54 | const date = new Date();
55 | const time = `${('0' + (date.getMonth() + 1)).slice(-2)}/${('0' + date.getDate()).slice(-2)}/${date.getFullYear()}, ` +
56 | `${('0' + (date.getHours() % 12)).slice(-2)}:${('0' + (date.getMinutes())).slice(-2)}:${('0' + (date.getSeconds())).slice(-2)} ` +
57 | `${date.getHours() > 12 ? 'PM' : 'AM'}`;
58 | fs.appendFileSync(generalLogPath, `[${time}] Server Started\n`);
59 | // run the method to discover / register your devices as accessories
60 | this.discoverDevices();
61 | });
62 | this.api.on(APIEvent.SHUTDOWN, () => {
63 | const projectDir = path.join(api.user.storagePath(), 'iRobot');
64 | const generalLogPath = path.join(projectDir, 'General.log');
65 | if (!fs.existsSync(projectDir)) {
66 | fs.mkdirSync(projectDir);
67 | }
68 | const date = new Date();
69 | const time = `${('0' + (date.getMonth() + 1)).slice(-2)}/${('0' + date.getDate()).slice(-2)}/${date.getFullYear()}, ` +
70 | `${('0' + (date.getHours() % 12)).slice(-2)}:${('0' + (date.getMinutes())).slice(-2)}:${('0' + (date.getSeconds())).slice(-2)} ` +
71 | `${date.getHours() > 12 ? 'PM' : 'AM'}`;
72 | fs.appendFileSync(generalLogPath, `[${time}] Server Stopped\n`);
73 |
74 | });
75 | }
76 |
77 | /**
78 | * This function is invoked when homebridge restores cached accessories from disk at startup.
79 | * It should be used to setup event handlers for characteristics and update respective values.
80 | */
81 | configureAccessory(accessory: PlatformAccessory) {
82 | accessory;
83 | /*this.log.info('Loading accessory from cache:', accessory.displayName);
84 |
85 | // add the restored accessory to the accessories cache so we can track if it has already been registered
86 | if (accessory.context.pluginVersion === 4) {
87 | this.cachedAccessories.push(accessory);
88 | }*/
89 | }
90 |
91 | /**
92 | * This is an example method showing how to register discovered accessories.
93 | * Accessories must only be registered once, previously created accessories
94 | * must not be registered again to prevent "duplicate UUID" errors.
95 | */
96 | discoverDevices() {
97 | if (!this.config.accessories) {
98 | this.log.warn('No Accessories Configured');
99 | return;
100 | }
101 | /*this.log.info(
102 | `Loaded ${this.cachedAccessories.length} ${this.cachedAccessories.length === 1 ? 'Accessory' : 'Accessories'} From Cache`,
103 | );*/
104 | // loop over the discovered devices and register each one if it has not already been registered
105 | for (const device of this.config.accessories) {
106 | if (device.ipResolution === 'manual' && !device.ip) {
107 | this.log.error('No IP Address Configured');
108 | this.log.error(JSON.stringify(device, null, 2));
109 | } else {
110 | //this.log.debug('Configuring device: \n', JSON.stringify(device));
111 | // generate a unique id for the accessory this should be generated from
112 | // something globally unique, but constant, for example, the device serial
113 | // number or MAC address
114 | const uuid = this.api.hap.uuid.generate(device.blid);
115 | // see if an accessory with the same uuid has already been registered and restored from
116 | // the cached devices we stored in the `configureAccessory` method above
117 | /*const existingAccessory = this.cachedAccessories.find(accessory => accessory.UUID === uuid);
118 |
119 | if (existingAccessory) {
120 | // the accessory already exists
121 | this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
122 |
123 | // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.:
124 | // existingAccessory.context.device = device;
125 | // this.api.updatePlatformAccessories([existingAccessory]);
126 |
127 | // create the accessory handler for the restored accessory
128 | // this is imported from `platformAccessory.ts`
129 | //new iRobotPlatformAccessory(this, existingAccessory, device);
130 | //new platformAccessory[accessoryType](this, existingAccessory);
131 | switch (JSON.parse(/([\d.-]+)/.exec(device.publicInfo.sw)![0].split('.').shift()!)) {
132 | /*case 1:
133 | new V1Roomba(this, existingAccessory, device);
134 | break;
135 | case 2:
136 | new V2Roomba(this, existingAccessory, device);
137 | break;
138 | case 3:
139 | case 22:
140 | new V3Roomba(this, existingAccessory, device);
141 | break;
142 | }
143 | // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.:
144 | // remove platform accessories when no longer present
145 | // this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
146 | // this.log.info('Removing existing accessory from cache:', existingAccessory.displayName);
147 | } else {*/
148 | // the accessory does not yet exist, so we need to create it
149 | this.log.info('Configuring accessory:', device.name);
150 |
151 | // create a new accessory
152 | const accessory: PlatformAccessory = new this.api.platformAccessory(device.name, uuid);
153 | this.accessories.push(accessory);
154 |
155 | // store a copy of the device object in the `accessory.context`
156 | // the `context` property can be used to store any data about the accessory you may need
157 | accessory.context.device = device;
158 | accessory.context.pluginVersion = 4;
159 | // create the accessory handler for the newly create accessory
160 | // this is imported from `platformAccessory.ts`
161 | //new iRobotPlatformAccessory(this, accessory, device);
162 | //new platformAccessory[accessoryType](this, accessory);
163 | switch (JSON.parse(/([\d.-]+)/.exec(device.sw)![0].split('.').shift()!)) {
164 | /*case 1:
165 | new V1Roomba(this, accessory, device);
166 | break;*/
167 | case 2:
168 | new V2Roomba(this, accessory, device);
169 | break;
170 | case 3:
171 | case 22:
172 | new V3Roomba(this, accessory, device);
173 | break;
174 | }
175 | }
176 | }
177 | /*const accessoriesToRemove = this.cachedAccessories.filter(cachedAccessory =>
178 | !this.restoredAccessories.find(restoredAccessory => restoredAccessory.UUID === cachedAccessory.UUID));
179 | for (const accessory of accessoriesToRemove) {
180 | this.log.warn('Removing Accessory: ', accessory.displayName);
181 | this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
182 | }*/
183 | // link the accessories to your platform
184 | //this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [...this.addedAccessories]);
185 | this.api.publishExternalAccessories(PLUGIN_NAME, this.accessories);
186 | /*this.log.info(
187 | `Restored ${this.restoredAccessories.length} ${this.restoredAccessories.length === 1 ? 'Accessory' : 'Accessories'}`,
188 | );
189 | this.log.info(
190 | `Added ${this.addedAccessories.length} ${this.addedAccessories.length === 1 ? 'Accessory' : 'Accessories'}`,
191 | );
192 | this.log.info(
193 | `Removed ${accessoriesToRemove.length} ${accessoriesToRemove.length === 1 ? 'Accessory' : 'Accessories'}`,
194 | );*/
195 | }
196 | }
--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------
1 | import type { LocalV3, LocalV2, LocalV1 } from '@bloomkd46/dorita980';
2 | import { ActiveIdentifier } from './accessories/Accessory';
3 |
4 |
5 | /**
6 | * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
7 | */
8 | export const PLATFORM_NAME = 'iRobotPlatform';
9 |
10 | /**
11 | * This must match the name of your plugin as defined the package.json
12 | */
13 | export const PLUGIN_NAME = 'homebridge-irobot';
14 |
15 | export type Context = {
16 | device: Device;
17 | connected?: boolean;
18 | offline?: boolean;
19 | logPath?: string;
20 | refreshToken?: string;
21 | pluginVersion?: 4;
22 | ip?: string;
23 | overrides: string[];
24 | //emptyCapable?: boolean;
25 | regions?: {
26 | name: string;
27 | id: string;
28 | type: 'rid' | 'zid';
29 | pmap_id: string;
30 | user_pmapv_id: string;
31 | }[];
32 | lastMode: ActiveIdentifier;
33 | connections: number;
34 | } & (V1 | V2 | V3);
35 | export type V1Mission = Awaited>['ok'];
36 | type V1 = {
37 | lastState: Partial;
38 | version: 1;
39 | };
40 | type V2 = {
41 | lastState: Partial;
42 | version: 2;
43 | };
44 | type V3 = {
45 | lastState: Partial;
46 | version: 3;
47 | };
48 |
49 | export type Config = {
50 | name: string;
51 | accessories: Device[];
52 | logLevel: 0 | 1 | 2 | 3 | 4;
53 | platform: 'iRobotPlatform';
54 | autoConnect?: boolean;
55 | alwaysShowModes?: boolean;
56 | };
57 | export type Device = {
58 | name: string;
59 | blid: string;
60 | password: string;
61 | sw: string;
62 | sku: string;
63 | //publicInfo: PublicInfo;
64 | } & ipInfo;
65 | type ipInfo = {
66 | ipResolution: 'manual';
67 | ip: string;
68 | } | {
69 | ipResolution: 'lookup';
70 | hostname: string;
71 | } | {
72 | ipResolution: 'broadcast';
73 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018", // ~node10
4 | "module": "commonjs",
5 | "lib": [
6 | "es2015",
7 | "es2016",
8 | "es2017",
9 | "es2018"
10 | ],
11 | "declaration": true,
12 | "declarationMap": true,
13 | "sourceMap": true,
14 | "outDir": "./dist",
15 | "rootDir": "./src",
16 | "strict": true,
17 | "esModuleInterop": true,
18 | "noImplicitAny": false,
19 | "skipLibCheck": true,
20 | },
21 | "include": [
22 | "./src"
23 | ],
24 | "exclude": [
25 | "**/*.spec.ts"
26 | ],
27 | "references": [
28 | {
29 | "path": "./homebridge-ui/tsconfig.json"
30 | },
31 | {
32 | "path": "./homebridge-ui/public/tsconfig.json"
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
This is the name of the platform that users will use to register the plugin in the Homebridge config.json
21 |