├── .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 | Homebridge iRobot 3 |

4 | 5 | 6 | # homebridge-iRobot 7 | 8 | Homebridge plugin to integrate iRobot roombas into HomeKit 9 | 10 | [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins) 11 | [![downloads](https://img.shields.io/npm/dt/homebridge-irobot)](https://npmcharts.com/compare/homebridge-irobot?log=true&interval=1&minimal=true) 12 | 13 | [![npm](https://img.shields.io/npm/v/homebridge-irobot/latest?label=latest)](https://www.npmjs.com/package/homebridge-irobot) 14 | [![npm](https://img.shields.io/npm/v/homebridge-irobot/beta?label=beta)](https://github.com/bloomkd46/homebridge-iRobot/wiki/Beta-Version) 15 | 16 | [![build workflow](https://github.com/bloomkd46/homebridge-iRobot/actions/workflows/build.yml/badge.svg)](https://github.com/bloomkd46/homebridge-iRobot/actions/workflows/build.yml) 17 | [![license](https://badgen.net/github/license/bloomkd46/homebridge-irobot)](/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 | 10 |
11 |
12 |
13 |
14 | 18 |

Function default

19 |
20 |
    21 | 22 |
  • 23 |
    24 |

    Parameters

    25 |
      26 |
    • 27 |
      hap: __module
    28 |

    Returns {
        EnergyUsage: any;
        PanelStatus: any;
    }

    29 |
      30 |
    • 31 |
      EnergyUsage: any
    • 32 |
    • 33 |
      PanelStatus: any
36 |
68 |
69 |

Generated using TypeDoc

70 |
-------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 |

homebridge-irobot

15 |
16 |
17 |

Index

18 |
19 |

Modules

20 |
29 |
58 |
59 |

Generated using TypeDoc

60 |
-------------------------------------------------------------------------------- /docs/modules/CustomCharacteristics.html: -------------------------------------------------------------------------------- 1 | CustomCharacteristics | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Module CustomCharacteristics

20 |
21 |
22 |
23 |
24 |

Index

25 |
26 |

Functions

27 |
default 28 |
29 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/modules/accessories_V1.html: -------------------------------------------------------------------------------- 1 | accessories/V1 | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Module accessories/V1

20 |
21 |
22 |
23 |
24 |

Index

25 |
26 |

Classes

27 |
default 28 |
29 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/modules/accessories_V2.html: -------------------------------------------------------------------------------- 1 | accessories/V2 | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Module accessories/V2

20 |
21 |
22 |
23 |
24 |

Index

25 |
26 |

Classes

27 |
default 28 |
29 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/modules/accessories_V3.html: -------------------------------------------------------------------------------- 1 | accessories/V3 | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Module accessories/V3

20 |
21 |
22 |
23 |
24 |

Index

25 |
26 |

Classes

27 |
default 28 |
29 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/modules/platform.html: -------------------------------------------------------------------------------- 1 | platform | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Module platform

20 |
21 |
22 |
23 |
24 |

Index

25 |
26 |

Classes

27 |
29 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/types/settings.Device.html: -------------------------------------------------------------------------------- 1 | Device | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 18 |

Type alias Device

19 |
Device: {
    blid: string;
    name: string;
    password: string;
    sku: string;
    sw: string;
} & ipInfo
22 |
59 |
60 |

Generated using TypeDoc

61 |
-------------------------------------------------------------------------------- /docs/types/settings.V1Mission.html: -------------------------------------------------------------------------------- 1 | V1Mission | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 18 |

Type alias V1Mission

19 |
V1Mission: Awaited<ReturnType<LocalV1.Local["getMission"]>>["ok"]
22 |
59 |
60 |

Generated using TypeDoc

61 |
-------------------------------------------------------------------------------- /docs/variables/settings.PLATFORM_NAME.html: -------------------------------------------------------------------------------- 1 | PLATFORM_NAME | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 18 |

Variable PLATFORM_NAMEConst

19 |
PLATFORM_NAME: "iRobotPlatform" = 'iRobotPlatform'
20 |

This is the name of the platform that users will use to register the plugin in the Homebridge config.json

21 |
24 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /docs/variables/settings.PLUGIN_NAME.html: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME | homebridge-irobot
2 |
3 | 10 |
11 |
12 |
13 |
14 | 18 |

Variable PLUGIN_NAMEConst

19 |
PLUGIN_NAME: "homebridge-irobot" = 'homebridge-irobot'
20 |

This must match the name of your plugin as defined the package.json

21 |
24 |
61 |
62 |

Generated using TypeDoc

63 |
-------------------------------------------------------------------------------- /homebridge-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | homebridge-irobot logo 7 |

8 |
9 |

Thank you for installing homebridge-iRobot

10 |

You can configure your iRobot on the next page

11 | 12 |
13 | 17 | 18 | 42 | 70 | 72 | 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 | } --------------------------------------------------------------------------------