├── test ├── mocha.setup.js ├── mocha.custom.opts ├── package.js ├── mocharc.custom.json ├── integration.js ├── af_test.js ├── testPackageFiles.js └── testStartup.js ├── .mocharc.json ├── admin ├── admin.d.ts ├── dateformat.png ├── fb-checkpresence.png ├── access_settings_network.png ├── style.css ├── index_m.html └── index_m.js ├── doc ├── QRCode.png └── access_settings_network.JPG ├── .releaseconfig.json ├── prettier.config.mjs ├── .gitignore ├── .github ├── dependabot.yml ├── auto-merge.yml ├── workflows │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml └── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── main.test.js ├── eslint.config.mjs ├── lib ├── dateformat │ ├── LICENSE │ └── dateformat.js └── fb.js ├── LICENSE ├── package.json ├── CHANGELOG_OLD.md ├── README.md └── io-package.json /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', (r) => { throw r; }); 2 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "./test/mocha.setup.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /admin/admin.d.ts: -------------------------------------------------------------------------------- 1 | declare let systemDictionary: Record>; 2 | -------------------------------------------------------------------------------- /doc/QRCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/HEAD/doc/QRCode.png -------------------------------------------------------------------------------- /admin/dateformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/HEAD/admin/dateformat.png -------------------------------------------------------------------------------- /admin/fb-checkpresence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/HEAD/admin/fb-checkpresence.png -------------------------------------------------------------------------------- /doc/access_settings_network.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/HEAD/doc/access_settings_network.JPG -------------------------------------------------------------------------------- /admin/access_settings_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/HEAD/admin/access_settings_network.png -------------------------------------------------------------------------------- /test/mocha.custom.opts: -------------------------------------------------------------------------------- 1 | --require test/mocha.setup.js 2 | {!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js} -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Validate the package files 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "dry": false, 3 | "addPlaceholder": true, 4 | "verbose": true, 5 | "all": false, 6 | "plugins": ["iobroker", "license", "manual-review"], 7 | "exec": { 8 | "before_commit": "echo Hello World!" 9 | } 10 | } -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "test/mocha.setup.js" 4 | ], 5 | "watch-files": [ 6 | "!(node_modules|test)/**/*.test.js", 7 | "*.test.js", 8 | "test/**/test!(PackageFiles|Startup).js" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.integration(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | // iobroker prettier configuration file 2 | import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; 3 | 4 | export default { 5 | ...prettierConfig, 6 | // uncomment next line if you prefer double quotes 7 | // singleQuote: false, 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*/ 2 | !.vscode/ 3 | !.github/ 4 | 5 | .git 6 | .idea 7 | node_modules 8 | nbproject 9 | 10 | # npm package files 11 | iobroker.*.tgz 12 | 13 | Thumbs.db 14 | 15 | # i18n intermediate files 16 | admin/i18n/flat.txt 17 | admin/i18n/*/flat.txt 18 | admin/i18n/* 19 | 20 | scripts 21 | scripts/upload.sh 22 | scripts/gulp.sh 23 | scripts/update_node.sh 24 | 25 | iob_npm.done 26 | .commitinfo 27 | 28 | # ioBroker dev-server 29 | .dev-server/ 30 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "04:00" 8 | timezone: Europe/Berlin 9 | open-pull-requests-limit: 5 10 | versioning-strategy: increase 11 | 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: monthly 16 | time: "04:00" 17 | timezone: Europe/Berlin 18 | open-pull-requests-limit: 5 19 | -------------------------------------------------------------------------------- /test/af_test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const fb = require('../lib/fb'); 3 | 4 | describe('Simple Fb Test', () => { 5 | it('should return 2', async () => { 6 | this.Fb = await fb.Fb.init({ 7 | host: this.config.ipaddress, 8 | uid: this.config.username, 9 | pwd: this.config.password 10 | }, this.config.ssl, this); 11 | assert.equal(1 + 1, 2); 12 | }); 13 | it('should return 9', () => { 14 | assert.equal(3 * 3, 9); 15 | }); 16 | }); -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.suggest.enabled": false, 3 | "json.schemas": [ 4 | { 5 | "fileMatch": ["io-package.json"], 6 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" 7 | }, 8 | { 9 | "fileMatch": [ 10 | "admin/jsonConfig.json", 11 | "admin/jsonCustom.json", 12 | "admin/jsonTab.json" 13 | ], 14 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.admin/master/packages/jsonConfig/schemas/jsonConfig.json" 15 | } 16 | ], 17 | "typescript.validate.enable": false, 18 | "eslint.useFlatConfig": true 19 | } -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Configure here which dependency updates should be merged automatically. 2 | # The recommended configuration is the following: 3 | - match: 4 | # Only merge patches for production dependencies 5 | dependency_type: production 6 | update_type: "semver:patch" 7 | - match: 8 | # Except for security fixes, here we allow minor patches 9 | dependency_type: production 10 | update_type: "security:minor" 11 | - match: 12 | # and development dependencies can have a minor update, too 13 | dependency_type: development 14 | update_type: "semver:minor" 15 | 16 | # The syntax is based on the legacy dependabot v1 automerged_updates syntax, see: 17 | # https://dependabot.com/docs/config-file/#automerged_updates -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Automatically merge Dependabot PRs when version comparison is within the range 2 | # that is configured in .github/auto-merge.yml 3 | 4 | name: Auto-Merge Dependabot PRs 5 | permissions: 6 | contents: read 7 | pull-requests: write 8 | on: 9 | pull_request_target: 10 | 11 | jobs: 12 | auto-merge: 13 | if: github.actor == 'dependabot[bot]' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v5 18 | 19 | - name: Check if PR should be auto-merged 20 | uses: ahmadnassri/action-dependabot-auto-merge@v2 21 | with: 22 | # This must be a personal access token with push access 23 | github-token: ${{ secrets.AUTO_MERGE_TOKEN }} 24 | # By default, squash and merge, so Github chooses nice commit messages 25 | command: squash and merge -------------------------------------------------------------------------------- /main.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is a dummy TypeScript test file using chai and mocha 5 | * 6 | * It's automatically excluded from npm and its build output is excluded from both git and npm. 7 | * It is advised to test all your modules with accompanying *.test.js-files 8 | */ 9 | 10 | // tslint:disable:no-unused-expression 11 | 12 | const { expect } = require('chai'); 13 | // import { functionToTest } from "./moduleToTest"; 14 | 15 | describe('module to test => function to test', () => { 16 | // initializing logic 17 | const expected = 5; 18 | 19 | it(`should return ${expected}`, () => { 20 | const result = 5; 21 | // assign result a value from functionToTest 22 | expect(result).to.equal(expected); 23 | // or using the should() syntax 24 | result.should.equal(expected); 25 | }); 26 | // ... more tests => it 27 | 28 | }); 29 | 30 | // ... more test suites => describe 31 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // ioBroker eslint template configuration file for js and ts files 2 | // Please note that esm or react based modules need additional modules loaded. 3 | import config from '@iobroker/eslint-config'; 4 | 5 | export default [ 6 | ...config, 7 | 8 | { 9 | // specify files to exclude from linting here 10 | ignores: [ 11 | '*.test.js', 12 | 'test/**/*.js', 13 | '*.config.mjs', 14 | 'build', 15 | 'admin/build', 16 | 'admin/words.js', 17 | 'admin/admin.d.ts', 18 | '**/adapter-config.d.ts' 19 | ] 20 | }, 21 | 22 | { 23 | // you may disable some 'jsdoc' warnings - but using jsdoc is highly recommended 24 | // as this improves maintainability. jsdoc warnings will not block buiuld process. 25 | rules: { 26 | // 'jsdoc/require-jsdoc': 'off', 27 | 'jsdoc/no-types': 'off' 28 | }, 29 | }, 30 | 31 | ]; -------------------------------------------------------------------------------- /lib/dateformat/LICENSE: -------------------------------------------------------------------------------- 1 | (c) 2007-2009 Steven Levithan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Achim Fürhoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[enhancement]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this report! 10 | 11 | - type: checkboxes 12 | id: checked-other-issues 13 | attributes: 14 | label: No existing issues. 15 | description: By submitting this issue, you confirm, that you have checked the existing issues for your request. 16 | options: 17 | - label: There is no existing issue for my request. 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Related problems 23 | description: Is your feature request related to a problem? Please describe. 24 | placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: Description 31 | description: Describe the solution you'd like 32 | placeholder: A clear and concise description of what you want to happen. 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | attributes: 38 | label: Additional context 39 | description: Add any other context or screenshots about the feature request here. 40 | validations: 41 | required: false -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "devserver:debug", // <-- from package.json <-- scripts: dev-server debug --wait 9 | "label": "preLaunchTask_startDevServer", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "always", 13 | "panel": "new" 14 | }, 15 | "problemMatcher": { 16 | "pattern": { 17 | "regexp": "^$" 18 | }, 19 | "background": { 20 | "activeOnStart": true, 21 | "beginsPattern": "devserver:debug", 22 | "endsPattern": "Debugger is now waiting on process id" 23 | } 24 | } 25 | }, 26 | { 27 | "label": "postDebug_KillChoice", 28 | "type": "process", 29 | "command": [ 30 | "${command:workbench.action.tasks.terminate}", 31 | "${command:workbench.action.acceptSelectedQuickOpenItem}" 32 | ] 33 | }, 34 | { 35 | "label": "postDebug_KillAll", 36 | "command": "echo ${input:terminate}", 37 | "type": "shell", 38 | "problemMatcher": [] 39 | } 40 | ], 41 | "inputs": [ 42 | { 43 | "id": "terminate", 44 | "type": "command", 45 | "command": "workbench.action.tasks.terminate", 46 | "args": "terminateAll" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Verwendet IntelliSense zum Ermitteln möglicher Attribute. 3 | // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. 4 | // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch ioBroker Adapter", 11 | "skipFiles": ["/**"], 12 | "args": ["--debug", "0"], 13 | "program": "node_modules/iobroker.fb-checkpresence/main.js", 14 | "cwd": "${workspaceFolder}/.dev-server/default" 15 | }, 16 | { //https://github.com/ioBroker/dev-server/issues/286 17 | "type": "node", 18 | "request": "attach", 19 | "name": "Attach to ioBroker Adapter (1)", 20 | "address": "127.0.0.1", 21 | "port": 9229, 22 | "localRoot": "${workspaceFolder}", 23 | "remoteRoot": "${workspaceFolder}/.dev-server/default/node_modules/iobroker.fb-checkpresence/", 24 | "smartStep": true, 25 | "skipFiles": ["/**"], 26 | "preLaunchTask": "preLaunchTask_startDevServer", // Start dev server before attaching a debugger to it 27 | "postDebugTask": "postDebug_KillAll", // Kill all tasks after debugging 28 | "continueOnAttach": true // This is needed to prevent the debugger from stopping on the first line 29 | }, 30 | { 31 | "name": "Attach to ioBroker Adapter (2)", 32 | "port": 9229, 33 | "request": "attach", 34 | "skipFiles": ["/**"], 35 | "type": "node", 36 | "resolveSourceMapLocations": [ 37 | "${workspaceFolder}/**", 38 | // only exclude node_modules from the workspace folder 39 | // If we exclude node_modules from .dev-server/..., we don't get sourcemaps 40 | "!${workspaceFolder}/node_modules/**" 41 | ], 42 | "sourceMapPathOverrides": { 43 | "../src/*": "${workspaceFolder}/src/*" 44 | }, 45 | "preLaunchTask": "preLaunchTask_startDevServer", // Start dev server before attaching a debugger to it 46 | "postDebugTask": "postDebug_KillAll", // Kill all tasks after debugging 47 | "continueOnAttach": true // This is needed to prevent the debugger from stopping on the first line 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Something is not working as it should 3 | title: "[bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this report! 10 | 11 | - type: checkboxes 12 | id: checked-other-issues 13 | attributes: 14 | label: No existing issues. 15 | description: By submitting this issue, you confirm, that you have checked the existing issues for your problem. 16 | options: 17 | - label: There is no existing issue for my problem. 18 | required: true 19 | 20 | - type: textarea 21 | id: description 22 | attributes: 23 | label: Describe the bug 24 | description: A clear and concise description of what the bug is. 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: reproduction 30 | attributes: 31 | label: To Reproduce 32 | description: Steps to reproduce the behavior 33 | placeholder: | 34 | 1. Go to '...' 35 | 2. Click on '...' 36 | 3. Scroll down to '....' 37 | 4. See error 38 | validations: 39 | required: false 40 | 41 | - type: textarea 42 | attributes: 43 | label: Expected behavior 44 | description: A clear and concise description of what you expected to happen. 45 | validations: 46 | required: false 47 | 48 | - type: textarea 49 | attributes: 50 | label: Screenshots & Logfiles 51 | description: If applicable, add screenshots and logfiles to help explain your problem. 52 | validations: 53 | required: false 54 | 55 | - type: input 56 | attributes: 57 | label: Adapter version 58 | validations: 59 | required: true 60 | 61 | - type: input 62 | attributes: 63 | label: js-controller version 64 | description: determine this with "iobroker -v" on the console 65 | validations: 66 | required: true 67 | 68 | - type: input 69 | attributes: 70 | label: Node version 71 | description: determine this with "node -v" on the console 72 | validations: 73 | required: true 74 | 75 | - type: input 76 | attributes: 77 | label: Operating system 78 | validations: 79 | required: true 80 | 81 | - type: textarea 82 | attributes: 83 | label: Additional context 84 | description: Add any other context or screenshots about the feature request here. 85 | validations: 86 | required: false -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.fb-checkpresence", 3 | "version": "1.4.2", 4 | "description": "The adapter checks the presence of family members over the fritzbox. You must fill in the name of the family member and the mac-address of the used device. The comment is optional and you can enable or disable the family member. The datapoint based on the member name.", 5 | "author": { 6 | "name": "Achim Fürhoff", 7 | "email": "achim.fuerhoff@outlook.de" 8 | }, 9 | "homepage": "https://github.com/afuerhoff/ioBroker.fb-checkpresence", 10 | "license": "MIT", 11 | "keywords": [ 12 | "ioBroker", 13 | "presence", 14 | "family", 15 | "tr064", 16 | "fritz.box", 17 | "fritzbox" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/afuerhoff/ioBroker.fb-checkpresence" 22 | }, 23 | "dependencies": { 24 | "@iobroker/adapter-core": "^3.3.2", 25 | "axios": "^1.13.2", 26 | "qr-image": "^3.2.0", 27 | "xml2js": "^0.6.2" 28 | }, 29 | "devDependencies": { 30 | "@alcalzone/release-script": "^5.0.0", 31 | "@alcalzone/release-script-plugin-iobroker": "^4.0.0", 32 | "@alcalzone/release-script-plugin-license": "^4.0.0", 33 | "@alcalzone/release-script-plugin-manual-review": "^4.0.0", 34 | "@iobroker/adapter-dev": "^1.5.0", 35 | "@iobroker/eslint-config": "^2.2.0", 36 | "@iobroker/testing": "^5.2.2", 37 | "@types/node": "^22.19.0", 38 | "@types/proxyquire": "^1.3.31", 39 | "@types/qr-image": "^3.2.9", 40 | "@types/xml2js": "^0.4.14", 41 | "proxyquire": "^2.1.3", 42 | "typescript": "^5.9.3" 43 | }, 44 | "main": "main.js", 45 | "files": [ 46 | "admin{,/!(src)/**}/!(tsconfig|tsconfig.*).json", 47 | "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", 48 | "lib/", 49 | "www/", 50 | "io-package.json", 51 | "LICENSE", 52 | "main.js" 53 | ], 54 | "scripts": { 55 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 56 | "test:package": "mocha test/package --exit", 57 | "test:integration": "mocha test/integration --exit", 58 | "test": "npm run test:js && npm run test:package", 59 | "check": "tsc --noEmit -p tsconfig.check.json", 60 | "lint": "eslint -c eslint.config.mjs ./lib/* ./main.js", 61 | "release": "release-script", 62 | "release-patch": "release-script patch --yes", 63 | "release-minor": "release-script minor --yes", 64 | "release-major": "release-script major --yes", 65 | "translate": "translate-adapter", 66 | "devserver:debug": "dev-server debug --wait" 67 | }, 68 | "bugs": { 69 | "url": "https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues" 70 | }, 71 | "readmeFilename": "README.md", 72 | "engines": { 73 | "node": ">=20" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | permissions: 3 | contents: write 4 | pull-requests: write 5 | id-token: write 6 | 7 | # Run this job on all pushes and pull requests 8 | # as well as tags with a semantic version 9 | # https://github.com/ioBroker/create-adapter/blob/master/docs/updates/20210913_shared_testing_workflows.md 10 | on: 11 | push: 12 | branches: 13 | - "master" 14 | tags: 15 | # normal versions 16 | - "v[0-9]+.[0-9]+.[0-9]+" 17 | # pre-releases 18 | - "v[0-9]+.[0-9]+.[0-9]+-**" 19 | pull_request: {} 20 | 21 | # Cancel previous PR/branch runs when a new commit is pushed 22 | concurrency: 23 | group: ${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | # Performs quick checks before the expensive test runs 28 | check-and-lint: 29 | if: contains(github.event.head_commit.message, '[skip ci]') == false 30 | 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: ioBroker/testing-action-check@v1 35 | with: 36 | node-version: '22.x' 37 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 38 | # install-command: 'npm install' 39 | lint: true 40 | 41 | # Runs adapter tests on all supported node versions and OSes 42 | adapter-tests: 43 | if: contains(github.event.head_commit.message, '[skip ci]') == false 44 | 45 | runs-on: ${{ matrix.os }} 46 | strategy: 47 | matrix: 48 | node-version: [20.x, 22.x, 24.x] 49 | os: [ubuntu-latest, windows-latest, macos-latest] 50 | 51 | steps: 52 | - uses: ioBroker/testing-action-adapter@v1 53 | with: 54 | node-version: ${{ matrix.node-version }} 55 | os: ${{ matrix.os }} 56 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 57 | # install-command: 'npm install' 58 | 59 | # TODO: To enable automatic npm releases, create a token on npmjs.org 60 | # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options 61 | # Then uncomment the following block: 62 | 63 | # Deploys the final package to NPM 64 | deploy: 65 | needs: [check-and-lint, adapter-tests] 66 | 67 | # Trigger this step only when a commit on any branch is tagged with a version number 68 | if: | 69 | contains(github.event.head_commit.message, '[skip ci]') == false && 70 | github.event_name == 'push' && 71 | startsWith(github.ref, 'refs/tags/v') 72 | 73 | runs-on: ubuntu-latest 74 | 75 | # Write permissions are required to create Github releases 76 | permissions: 77 | contents: write 78 | id-token: write 79 | 80 | steps: 81 | - uses: ioBroker/testing-action-deploy@v1 82 | with: 83 | node-version: '22.x' 84 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 85 | # install-command: 'npm install' 86 | # npm-token: ${{ secrets.NPM_TOKEN }} 87 | github-token: ${{ secrets.GITHUB_TOKEN }} 88 | # 89 | # # When using Sentry for error reporting, Sentry can be informed about new releases 90 | # # To enable create a API-Token in Sentry (User settings, API keys) 91 | # # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options 92 | # # Then uncomment and customize the following block: 93 | # sentry: true 94 | # sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 95 | # sentry-project: "iobroker-template" 96 | # sentry-version-prefix: "iobroker.template" 97 | # # If your sentry project is linked to a GitHub repository, you can enable the following option 98 | # # sentry-github-integration: true -------------------------------------------------------------------------------- /test/testPackageFiles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const fs = require('fs'); 5 | 6 | describe('Test package.json and io-package.json', function() { 7 | it('Test package files', function (done) { 8 | console.log(); 9 | 10 | const ioPackage = require('../io-package.json'); 11 | const npmPackage = require('../package.json'); 12 | 13 | expect(ioPackage).to.be.an('object'); 14 | expect(npmPackage).to.be.an('object'); 15 | 16 | expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; 17 | expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; 18 | 19 | expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); 20 | 21 | if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { 22 | console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); 23 | console.log(); 24 | } 25 | 26 | expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; 27 | expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; 28 | 29 | if (ioPackage.common.name.indexOf('template') !== 0) { 30 | if (Array.isArray(ioPackage.common.authors)) { 31 | expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); 32 | if (ioPackage.common.authors.length === 1) { 33 | expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); 34 | } 35 | } 36 | else { 37 | expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); 38 | } 39 | } 40 | else { 41 | console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); 42 | console.log(); 43 | } 44 | expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; 45 | if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { 46 | console.log('WARNING: titleLang is not existing in io-package.json. Please add'); 47 | console.log(); 48 | } 49 | if ( 50 | ioPackage.common.title.indexOf('iobroker') !== -1 || 51 | ioPackage.common.title.indexOf('ioBroker') !== -1 || 52 | ioPackage.common.title.indexOf('adapter') !== -1 || 53 | ioPackage.common.title.indexOf('Adapter') !== -1 54 | ) { 55 | console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); 56 | console.log(); 57 | } 58 | 59 | if (ioPackage.common.name.indexOf('vis-') !== 0) { 60 | if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { 61 | console.log('WARNING: Admin3 support is missing! Please add it'); 62 | console.log(); 63 | } 64 | if (ioPackage.common.materialize) { 65 | expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; 66 | } 67 | } 68 | 69 | const licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); 70 | const fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); 71 | if (fileContentReadme.indexOf('## Changelog') === -1) { 72 | console.log('Warning: The README.md should have a section ## Changelog'); 73 | console.log(); 74 | } 75 | expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; 76 | if (!licenseFileExists) { 77 | console.log('Warning: The License should also exist as LICENSE file'); 78 | console.log(); 79 | } 80 | if (fileContentReadme.indexOf('## License') === -1) { 81 | console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); 82 | console.log(); 83 | } 84 | done(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/testStartup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const setup = require('./lib/setup'); 5 | 6 | let objects = null; 7 | let states = null; 8 | let onStateChanged = null; 9 | // let onObjectChanged = null; 10 | let sendToID = 1; 11 | 12 | const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1); 13 | 14 | function checkConnectionOfAdapter(cb, counter) { 15 | counter = counter || 0; 16 | console.log('Try check #' + counter); 17 | if (counter > 30) { 18 | if (cb) cb('Cannot check connection'); 19 | return; 20 | } 21 | 22 | states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { 23 | if (err) console.error(err); 24 | if (state && state.val) { 25 | if (cb) cb(); 26 | } else { 27 | setTimeout(function () { 28 | checkConnectionOfAdapter(cb, counter + 1); 29 | }, 1000); 30 | } 31 | }); 32 | } 33 | 34 | // eslint-disable-next-line no-unused-vars 35 | function checkValueOfState(id, value, cb, counter) { 36 | counter = counter || 0; 37 | if (counter > 20) { 38 | if (cb) cb('Cannot check value Of State ' + id); 39 | return; 40 | } 41 | 42 | states.getState(id, function (err, state) { 43 | if (err) console.error(err); 44 | if (value === null && !state) { 45 | if (cb) cb(); 46 | } else if (state && (value === undefined || state.val === value)) { 47 | if (cb) cb(); 48 | } else { 49 | setTimeout(function () { 50 | checkValueOfState(id, value, cb, counter + 1); 51 | }, 500); 52 | } 53 | }); 54 | } 55 | 56 | // eslint-disable-next-line no-unused-vars 57 | function sendTo(target, command, message, callback) { 58 | onStateChanged = function (id, state) { 59 | if (id === 'messagebox.system.adapter.test.0') { 60 | callback(state.message); 61 | } 62 | }; 63 | 64 | states.pushMessage('system.adapter.' + target, { 65 | command: command, 66 | message: message, 67 | from: 'system.adapter.test.0', 68 | callback: { 69 | message: message, 70 | id: sendToID++, 71 | ack: false, 72 | time: (new Date()).getTime() 73 | } 74 | }); 75 | } 76 | 77 | describe('Test ' + adapterShortName + ' adapter', function() { 78 | before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { 79 | this.timeout(600000); // because of first install from npm 80 | 81 | setup.setupController(async function () { 82 | const config = await setup.getAdapterConfig(); 83 | // enable adapter 84 | config.common.enabled = true; 85 | config.common.loglevel = 'debug'; 86 | 87 | //config.native.dbtype = 'sqlite'; 88 | 89 | await setup.setAdapterConfig(config.common, config.native); 90 | 91 | // eslint-disable-next-line no-unused-vars 92 | setup.startController(true, (id, obj) => { }, function (id, state) { 93 | if (onStateChanged) onStateChanged(id, state); 94 | }, 95 | function (_objects, _states) { 96 | objects = _objects; 97 | states = _states; 98 | _done(); 99 | }); 100 | }); 101 | }); 102 | 103 | /* 104 | ENABLE THIS WHEN ADAPTER RUNS IN DEAMON MODE TO CHECK THAT IT HAS STARTED SUCCESSFULLY 105 | */ 106 | it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { 107 | this.timeout(60000); 108 | checkConnectionOfAdapter(function (res) { 109 | if (res) console.log(res); 110 | expect(res).not.to.be.equal('Cannot check connection'); 111 | objects.setObject('system.adapter.test.0', { 112 | common: { 113 | 114 | }, 115 | type: 'instance' 116 | }, 117 | function () { 118 | states.subscribeMessage('system.adapter.test.0'); 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | /* 125 | PUT YOUR OWN TESTS HERE USING 126 | it('Testname', function ( done) { 127 | ... 128 | }); 129 | 130 | You can also use "sendTo" method to send messages to the started adapter 131 | */ 132 | 133 | after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { 134 | this.timeout(10000); 135 | 136 | setup.stopController(function (normalTerminated) { 137 | console.log('Adapter normal terminated: ' + normalTerminated); 138 | done(); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /admin/style.css: -------------------------------------------------------------------------------- 1 | /* ------------------- CSS style provided by ioBroker Adapter Creator ------------------- */ 2 | * { box-sizing: border-box; } 3 | /*.m { overflow: initial;} /* Don't cut off dropdowns! */ 4 | /* -------------------------------------------------------------------------------------- */ 5 | 6 | /* Add your styles here */ 7 | 8 | /* Tabs menu */ 9 | .m .tabs .active { font-weight: bold;} /* Menu text bold */ 10 | .m .tabs .tab a { padding: 0 1px; } /* space of x px at beginning and end of text */ 11 | .m .tabs .tab a:hover { border-bottom: 2px solid #46a0e9 !important; } 12 | .m .row .col.m1 { width: 22%; } 13 | 14 | /* Header section fb-checkpresence Main tab*/ 15 | #header-area {margin-top: 0px; margin-bottom: 0px;} /* Header oben und unten ausrichten */ 16 | #header-area #header-logo-title {display: flex; align-items: center; } 17 | #header-area #header-logo-title .logo {height: auto; width: 48px; float:left; margin-right: 0px; } /* Icon ausrichten */ 18 | #header-area #header-logo-title p {line-height: normal !important; margin: 0 !important; padding: 0px 0 0 0px;} 19 | #header-area #header-logo-title p>span.h-title {font-size: 1.4em !important; } /* Titel */ 20 | #header-area #header-logo-title p>span.h-sub-title {font-size: 0.9em !important; font-style: italic; white-space: nowrap} 21 | 22 | /* ****** collapsible sections ****** */ 23 | /* collapsible: general ****** */ 24 | .collapsible .collapsible-header { background-color: #c8e4fa69 !important; box-shadow: none!important;} 25 | .collapsible .collapsible-header { padding-top:1px !important; padding-bottom:1px !important; } 26 | .collapsible .collapsible-header i { padding-top:3px; } /* ? icon Ausrichtung */ 27 | .collapsible .collapsible-header h5 { font-weight: bold; font-size:1.0em; margin-top:0.3em; } 28 | 29 | /* collapsible: Config Table Header/Documentation ****** */ 30 | .collapsible-header.config-section { background-color:#c5c1c1 !important; } 31 | .collapsible-header.config-section i.material-icons { font-size: 1.5em; padding-right: 0; margin-right: 0;} 32 | .collapsible-header.config-section h5 { margin-top: 10px; } 33 | .collapsible-header.config-section h6 { padding-top: 7px; margin-top: 1px; margin-bottom: 3px; } 34 | .collapsible-header.config-section h6 { font-weight: bold; font-size:0.9em; margin-top:5px; } 35 | .collapsible-header.config-section { display: flex; justify-content: space-between; } /* for right alignment of question mark icon */ 36 | 37 | .collapsible .collapsible-body {margin-top: 5px; margin-right: 5px; margin-bottom: 5px; padding-top: 0px; padding-bottom: 0px; box-shadow: none!important;} 38 | .collapsible .collapsible-body p { font-size: 0.9em; line-height: 1.0em !important; margin: 5px;} 39 | .collapsible .collapsible-body td:nth-child(1) { width: 20%; } /* Format 1 column */ 40 | .collapsible .collapsible-body td:nth-child(2) { width: 20%; } /* Format 2 column */ 41 | .collapsible .collapsible-body td:nth-child(3) { width: 60%; } /* Format 3 column */ 42 | .collapsible .collapsible-body table.striped > tbody > tr:nth-child(odd) {background-color: rgba(201, 203, 204, 0.653);} 43 | .collapsible .collapsible-body table {font-size: 0.8em; margin-top:0px; margin-left: 0px; } 44 | .collapsible .collapsible-body th {background-color: #38383869; padding: 1px; margin-right: 1px;} 45 | .collapsible .collapsible-body tr td .btn {color: #555; font-size: 0.8em; line-height: 1.9em; } 46 | .collapsible .collapsible-body tr td .button .btn text {vertical-align: middle;} 47 | 48 | /* link behaviour */ 49 | .page a:hover {font-weight:bold !important; color:#2696ef !important; text-decoration: underline !important; } /* mouse over link */ 50 | .page a:link {font-weight:bold !important; color:#061ce5; text-decoration: none;} /* unvisited link */ 51 | .page a:visited {color:#b020f2; text-decoration: none;} /* visited link */ 52 | .page a:active {font-weight:bold; color: #2696ef !important; text-decoration: underline;} /* selected link */ 53 | 54 | /* Tooltip container */ 55 | .tooltip { position: relative; display: inline-block;} 56 | 57 | /* Show the tooltip text when you mouse over the tooltip container */ 58 | .tooltip:hover .tooltiptext { 59 | visibility: visible; opacity: 1; 60 | } 61 | 62 | /* Tooltip text */ 63 | .tooltip .tooltiptext { 64 | visibility: hidden; 65 | text-align: left; 66 | width: auto; 67 | height: auto; 68 | background-color: #777; 69 | color: #fff; 70 | border-radius: 4px; 71 | max-width: 90vw; 72 | word-wrap: break-word; 73 | white-space: normal; 74 | z-index: 1; 75 | opacity: 0; 76 | transition: opacity 1s; /* Fade in tooltip */ 77 | position: absolute; 78 | padding: 5px; 79 | font-size: 0.9rem; 80 | left: 4%; /* Position direkt über der Checkbox */ 81 | } 82 | 83 | @media (max-width: 600px) { 84 | .tooltip .tooltiptext { 85 | padding: 3px; 86 | font-size: 0.8rem; 87 | } 88 | } 89 | 90 | /* Checkbox-spezifische Tooltips */ 91 | .tooltip.checkbox-tooltip .tooltiptext { 92 | bottom: calc(100% + 10px); 93 | } 94 | 95 | /* Textfeld-spezifische Tooltips */ 96 | .tooltip.textfield-tooltip .tooltiptext { 97 | bottom: calc(100% + 20px); 98 | } 99 | 100 | /* Tooltip arrow */ 101 | .tooltip .tooltiptext::after { 102 | content: " "; 103 | position: absolute; 104 | top: 100%; /* Pfeil wird unter dem Tooltip platziert */ 105 | left: 10%; /* Horizontale Ausrichtung */ 106 | margin-left: -5px; /* Zentriert den Pfeil */ 107 | border-width: 7px; /* Größe des Pfeils */ 108 | border-style: solid; 109 | border-color: #777 transparent transparent transparent; /* Sichtbare obere Seite */ 110 | } 111 | 112 | /* Input validation for admin configuration */ 113 | input:invalid {border: red solid 3px;} 114 | input:valid {border: rgb(0, 255, 21) solid 3px;} 115 | 116 | #buttonaddwl { 117 | margin-left: 20px; /* Kein zusätzlicher Abstand */ 118 | margin-top: 2px; 119 | } 120 | 121 | /* Admin Tabelle Familienmitglieder */ 122 | table.wladmin tr td:nth-child(2) input, 123 | table.fmadmin tr td:nth-child(3) input { font-weight:bold; } 124 | 125 | /* Dialogboxes family members and whitelist devices */ 126 | /* ------------------------------------------------ */ 127 | 128 | /* Error dialog box*/ 129 | .dlgErrorTitle { 130 | border: 1px solid #d46e29; /* Umrandung passend zur Schriftfarbe */ 131 | border-radius: 5px; /* Abgerundete Ecken */ 132 | padding: 10px; /* Innenabstand für eine Box-Optik */ 133 | font-size: 1rem; /* Textgröße */ 134 | margin: 6px !important; /* Kein zusätzlicher Abstand */ 135 | text-align: center; /* Zentrierter Text */ 136 | } 137 | .dlgErrorText {margin:30px 0px 10px 10px; font-size:1.2em; text-align: center;} 138 | 139 | /* Devices dialog box */ 140 | /*--------------------------------------------------*/ 141 | #tabDevices, #tabWl {overflow-y: hidden!important; width:100%} 142 | #tabDevices tr td, #tabWl tr td {text-align:left; padding-left: 10px;} 143 | #tabDevices tr, #tabWl tr {line-height: 6px!important; font-size:90%;padding:0px!important} 144 | 145 | /* Dialog box devices & whitelist */ 146 | .dlgTitle { 147 | border: 1px solid #0c29e9; 148 | border-radius: 5px; 149 | padding: 6px!important; 150 | font-size: 0.8em; 151 | margin: 6px!important; 152 | } 153 | 154 | /* Dialog box footer */ 155 | .btnDlg {color: white !important; margin-right: 5px!important; } 156 | 157 | /* Search text field */ 158 | input.searchInput { padding-left: 1px; width: 200px !important; margin:0 0 0 25px !important; height: 2.0rem !important;} 159 | .input-field { position: relative;} 160 | #searchIcon { 161 | display: block; /* Blockelement für einfache Zentrierung */ 162 | margin: 0 auto; /* Horizontal zentrieren */ 163 | font-size: 1.9rem; 164 | color: #555; 165 | position: absolute; /* Absolute Positionierung */ 166 | top: 50%; /* Vertikal zentriert relativ zum Container */ 167 | transform: translateY(-50%); /* Exakte Zentrierung */ 168 | left: 10px; /* Abstand vom linken Rand */ 169 | } 170 | 171 | /* Search x button */ 172 | #btnResetSearchWl, 173 | #btnResetSearch { left: 250px; width: 1.6em !important; height: 1.6em !important;} 174 | #btnResetSearchWl i, 175 | #btnResetSearch i {font-size: .9rem !important; line-height: 1rem !important;} 176 | 177 | /* Table Content */ 178 | #dlgDevices .modal-content, 179 | #dlgWl .modal-content {margin: 0px 0px 0px 5px; padding:0px; height: calc(85% - 140px)!important } 180 | 181 | #dlgWl .modal-footer, 182 | #dlgDevices .modal-footer { 183 | padding:10px; 184 | } 185 | 186 | table.fm {width:94%; margin-top:0px; } 187 | table.fm tr.header { background-color:#b6b6b6; color: white; font-size:90%; text-align:left; line-height: 1em; padding:5px!important; } 188 | table.fm th.header { padding:0px!important; text-align:left; margin: 5px 0px 0px 6px;} 189 | table.fm tr td { font-size: 90%!important; height: 0.9em!important; margin-top:5px!important} 190 | table.fm tr td:nth-child(3) input { font-weight:bold; } 191 | 192 | table.fm th:nth-child(1) { width: 5%; } 193 | table.fm td:nth-child(1) { width: 5%; } 194 | table.fm tr th:nth-child(2) { width: 35%; } 195 | table.fm tr td:nth-child(2) { width: 35%; } 196 | table.fm tr th:nth-child(3) { width: 30%; } 197 | table.fm tr td:nth-child(3) { width: 30%; } 198 | table.fm tr th:nth-child(4) { width: 30%; } 199 | table.fm tr td:nth-child(4) { width: 30%; } 200 | 201 | table.wl {width:94%; margin-top:0px; } 202 | table.wl tr.header { background-color:#b6b6b6; color: white; font-size:90%; text-align:left; line-height: 1em; padding:5px!important; } 203 | table.wl th.header { padding: 0px 0 0 10px!important; text-align:left; margin: 5px 0px 0px 6px;} 204 | table.wl tr td { font-size: 90%!important; height: 0.9em!important; margin-top:5px!important} 205 | table.wl tr td:nth-child(3) input { font-weight:bold; } 206 | 207 | table.wl th:nth-child(1) { width: 5%; } 208 | table.wl td:nth-child(1) { width: 5%; } 209 | table.wl tr th:nth-child(2) { width: 55%; } 210 | table.wl tr td:nth-child(2) { width: 55%; } 211 | table.wl tr th:nth-child(3) { width: 40%; } 212 | table.wl tr td:nth-child(3) { width: 40%; } 213 | 214 | -------------------------------------------------------------------------------- /CHANGELOG_OLD.md: -------------------------------------------------------------------------------- 1 | # Older changes 2 | ## 1.2.8 (2024-11-20) 3 | * (afuerhoff) bugfix configuration 4 | * (afuerhoff) dependencies updated 5 | 6 | ## 1.2.7 (2024-11-18) 7 | * (afuerhoff) bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319) 8 | 9 | ## 1.2.6 (2024-11-14) 10 | * (afuerhoff) dependencies updated 11 | * (afuerhoff) DisAllowWanAccess optimized 12 | 13 | ## 1.2.5 (2024-09-18) 14 | * (afuerhoff) new filter function implemented (experimental) 15 | * (afuerhoff) node >=18 16 | * (afuerhoff) dependencies updated 17 | * (afuerhoff) repository checker issues fixed 18 | * (afuerhoff) issue fixed [#294](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/294) 19 | * (afuerhoff) 'devicename' as state added [#299](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/299) 20 | 21 | ## 1.2.4 (2024-02-09) 22 | * (afuerhoff) test code deleted [#257](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/257) 23 | * (afuerhoff) dependencies updated 24 | 25 | ## 1.2.3 (2024-01-18) 26 | * (afuerhoff) Readme updated 27 | * (afuerhoff) function jsontables optimized 28 | * (afuerhoff) non existent members set to false [#253](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/253) 29 | * (afuerhoff) optimization of log message [#240](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/240) 30 | 31 | ## 1.2.2 (2023-07-28) 32 | * (afuerhoff) bug fixed json tables [#215](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/215) 33 | * (afuerhoff) link feature optimized. See #206 34 | 35 | ## 1.2.1 (2023-07-14) 36 | * (afuerhoff) bug fixed property link 37 | 38 | ## 1.2.0 (2023-07-13) 39 | * (afuerhoff) dependencies updated 40 | * (afuerhoff) mesh link added to family members [#206](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/206) 41 | 42 | ## 1.1.26 (2023-04-06) 43 | * (afuerhoff) Wrong default settings in io-package.json [#188](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/188) 44 | * (afuerhoff) Wrong Axios parameter in getMeshList [#197](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/197) 45 | * (afuerhoff) dependencies updated 46 | 47 | ## 1.1.25 (2023-01-21) 48 | * (afuerhoff) Warning message empty hostname optimized. Issue [#180](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/180) 49 | 50 | ## 1.1.24 (2022-12-22) 51 | * (afuerhoff) axios updated 52 | * (afuerhoff) CancelToken as option implemented. For node versions less than 16 53 | 54 | ## 1.1.23 (2022-12-20) 55 | * (afuerhoff) fb devices with empty hostname are ignored 56 | 57 | ## 1.1.22 (2022-12-13) 58 | * (afuerhoff) axios updated 59 | * (afuerhoff) error handling optimized 60 | 61 | ## 1.1.21 (2022-09-05) 62 | * (afuerhoff) dependencies updated 63 | 64 | ## 1.1.20 (2022-09-05) 65 | * (afuerhoff) issue [#136](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/136) force update on demand 66 | * (afuerhoff) issue [#139](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/139) Add family members fixed 67 | * (afuerhoff) issue [#140](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/140) Add family members dialogbox fixed 68 | * (afuerhoff) issue [#129](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/129) Dateformat library changed 69 | 70 | ## 1.1.19 (2022-07-08) 71 | * (afuerhoff) issue [#137](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/137) guest device listed twice 72 | 73 | ## 1.1.18 (2022-07-04) 74 | * (afuerhoff) issue [#67](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/67) fbdevices states for vpn connection added 75 | * (afuerhoff) issue [#128](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/128) Restart adapter after cycle error 76 | 77 | ## 1.1.17 (2022-06-15) 78 | * (afuerhoff) issue [#126](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/126) bugfix undefined historyAlive object 79 | * (afuerhoff) log optimized 80 | 81 | ## 1.1.16 (2022-06-12) 82 | * (afuerhoff) issue [#111](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/111) Main interval changed from minutes to seconds 83 | * (afuerhoff) issue [#123](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/123) States set only if they changed 84 | * (afuerhoff) issue [#124](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/124) guestinfos decoupled from fbdevices 85 | * (afuerhoff) bug in v1.1.15 fixed: info.extip was set without existing object 86 | 87 | ## 1.1.15 (2022-05-27) 88 | * (afuerhoff) issue [#110](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/110) enhancement 89 | * (afuerhoff) dependency updated 90 | * (afuerhoff) issue [#115] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/115) configuration switch for external ip address added 91 | 92 | ## 1.1.14 (2022-03-31) 93 | * (afuerhoff) dependencies updated 94 | * (afuerhoff) issue [#85](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/85) bugfix 95 | * (afuerhoff) issue [[#91](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/91)] bugfix 96 | * (afuerhoff) whitelist.html bug fixed 97 | * (afuerhoff) issue [#92](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/92) bugfix 98 | 99 | ## 1.1.13 (2022-01-18) 100 | * (afuerhoff) issue #73: bugfix 101 | 102 | ## 1.1.12 (2022-01-13) 103 | * (afuerhoff) issue #74: bugfix 104 | * (afuerhoff) dependencies updated 105 | 106 | ## 1.1.11 (2021-12-22) 107 | * (afuerhoff) dependencies updated 108 | * (afuerhoff) bugfix blacklist 109 | 110 | ## 1.1.10 (2021-09-24) 111 | * (afuerhoff) dependencies updated 112 | 113 | ## 1.1.9 (2021-09-24) 114 | * (afuerhoff) dependencies updated 115 | * (afuerhoff) issue #63: getExtIp logging solved 116 | * (afuerhoff) issue #65: wrong logging in instance > 0 solved 117 | * (afuerhoff) qr-code WPA2 + WPA3 fixed 118 | 119 | ## 1.1.8 (2021-09-01) 120 | * (afuerhoff) issue#61: units added 121 | * (afuerhoff) workaround for 7390 added (missing systemVersion in xml) 122 | * (afuerhoff) dependencies updated 123 | 124 | ## 1.1.7 (2021-06-23) 125 | * (afuerhoff) message handling optimized 126 | * (afuerhoff) dependencies updated 127 | 128 | ## 1.1.6 (2021-06-20) 129 | * (afuerhoff) html input pattern fixed for password and user 130 | * (afuerhoff) getActive function fixed 131 | * (afuerhoff) dependencies updated 132 | 133 | ## 1.1.5 (2021-06-03) 134 | * (afuerhoff) dependencies updated 135 | * (afuerhoff) checkservice fixed 136 | 137 | ## 1.1.4 (2021-05-11) 138 | * (afuerhoff) family groups implemented 139 | * (afuerhoff) compatability mode implemented 140 | * (afuerhoff) dependencies updated 141 | * (afuerhoff) configuration options added 142 | * (afuerhoff) dialogboxes optimized 143 | * (afuerhoff) translations updated 144 | * (afuerhoff) general program structure optimized 145 | * (afuerhoff) filter for family members implemeted 146 | * (afuerhoff) password handling updated 147 | * (afuerhoff) documentation updated 148 | * (afuerhoff) QR-Code implemented 149 | * (afuerhoff) setState presence only if changed 150 | * (afuerhoff) access rights implemented 151 | * (afuerhoff) use name for presence 152 | * (afuerhoff) active / inactive devices 153 | * (afuerhoff) interval 10s bug fixed 154 | * (afuerhoff) Bugfix dateformat pattern 155 | * (afuerhoff) SSL (https) workaround implemented 156 | * (afuerhoff) Connection check optimized 157 | * (afuerhoff) Mesh handling optimized 158 | 159 | ## 1.1.3 (2021-03-31) 160 | * (afuerhoff) family groups implemented 161 | * (afuerhoff) compatability mode implemented 162 | * (afuerhoff) dependencies updated 163 | * (afuerhoff) configuration options added 164 | * (afuerhoff) dialogboxes optimized 165 | * (afuerhoff) translations updated 166 | * (afuerhoff) general program structure optimized 167 | * (afuerhoff) filter for family members implemeted 168 | * (afuerhoff) password handling updated 169 | * (afuerhoff) documentation updated 170 | 171 | ## 1.1.2 (2021-01-13) 172 | * (afuerhoff) QR-Code implemented 173 | * (afuerhoff) setState presence only if changed 174 | * (afuerhoff) access rights implemented 175 | * (afuerhoff) use name for presence 176 | * (afuerhoff) active / inactive devices 177 | * (afuerhoff) interval 10s bug fixed 178 | * (afuerhoff) documentation edited 179 | 180 | ## 1.1.1 (2020-12-27) 181 | * (afuerhoff) Configuration optimized 182 | * (afuerhoff) Bugfix dateformat pattern 183 | * (afuerhoff) SSL (https) workaround implemented 184 | * (afuerhoff) Connection check optimized 185 | * (afuerhoff) Documentation added 186 | * (afuerhoff) Mesh handling optimized 187 | 188 | ## 1.1.0 (2020-10-24) 189 | * (afuerhoff) second interval for family members implemented 190 | * (afuerhoff) mesh info added 191 | * (afuerhoff) configuration validation added 192 | * (afuerhoff) switch on, off guest wlan 193 | * (afuerhoff) switch on, off internet access of devices 194 | * (afuerhoff) structural changes 195 | * (afuerhoff) code optimization 196 | 197 | ## 1.0.4 (2020-06-28) 198 | * (afuerhoff) bugfix json list and guest handling, new object guest.presence 199 | 200 | ## 1.0.3 (2020-05-26) 201 | * (afuerhoff) bugfix checking mac or ip 202 | 203 | ## 1.0.2 (2020-05-24) 204 | * (afuerhoff) error handling optimized 205 | * (afuerhoff) external ip implemented 206 | * (afuerhoff) check if mac or ip are listed in fritzbox 207 | 208 | ## 1.0.1 (2020-04-12) 209 | * (afuerhoff) error handling optimized 210 | * (afuerhoff) history configuration optimized 211 | * (afuerhoff) re-synchronisation of fb-devices implemented 212 | 213 | ## 1.0.0 (2020-03-30) 214 | * (afuerhoff) Configuration dialog optimized 215 | * (afuerhoff) fbdevice speed added 216 | * (afuerhoff) present-, absentMembers inserted 217 | * (afuerhoff) Ip address handling optimized 218 | * (afuerhoff) iobroker functions changed to async 219 | * (afuerhoff) instance.0 dependency fixed 220 | * (afuerhoff) depricated request changed to axios 221 | 222 | ## 0.3.0 223 | * (afuerhoff) Documentation optimized 224 | * (afuerhoff) LastVal error fixed 225 | * (afuerhoff) Json table failure fixed 226 | * (afuerhoff) Connection type added 227 | * (afuerhoff) Ipaddress default value changed 228 | * (afuerhoff) New feature fb-devices added 229 | * (afuerhoff) Error messages optimized 230 | * (afuerhoff) Dateformat default value changed 231 | * (afuerhoff) Debug info added 232 | * (afuerhoff) GetDeviceInfo failure fixed 233 | * (afuerhoff) Update testing 234 | 235 | ## 0.2.2 236 | * (afuerhoff) outdated packages updated, documentation changed, 237 | history dependency removed, onstate/objectChange removed, scheduler library removed, 238 | two fixes from publish review 239 | 240 | ## 0.2.1 241 | * (afuerhoff) getGuests issue resolved, lastVal function and debug information optimized 242 | 243 | ## 0.2.0 244 | * (afuerhoff) debug and error information optimized, crypto dependency removed, service check and blacklist added 245 | 246 | ## 0.1.0 247 | * (afuerhoff) Influxdb added, debug information added 248 | 249 | ## 0.0.7 250 | * (afuerhoff) Fix bug invalid date. Add debug information. 251 | 252 | ## 0.0.6 253 | * (afuerhoff) bug in json and html table resolved 254 | 255 | ## 0.0.5 256 | * (afuerhoff) configuration optimized 257 | 258 | ## 0.0.4 259 | * (afuerhoff) calculation error resolved 260 | 261 | ## 0.0.3 262 | * (afuerhoff) guest feature added 263 | 264 | ## 0.0.2 265 | * (afuerhoff) optimized features 266 | 267 | ## 0.0.1 268 | * (afuerhoff) initial release -------------------------------------------------------------------------------- /lib/dateformat/dateformat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | //Changes: 16 | // afuerhoff 2022.07.31: initial copy of v4.5.1 https://github.com/felixge/node-dateformat/blob/v4.5.1/src/dateformat.js 17 | // based on https://blog.stevenlevithan.com/archives/javascript-date-format 18 | 19 | (function (global) { 20 | const dateFormat = (() => { 21 | const token = /d{1,4}|D{3,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|W{1,2}|[LlopSZN]|"[^"]*"|'[^']*'/g; 22 | const timezone = 23 | /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g; 24 | const timezoneClip = /[^-+\dA-Z]/g; 25 | 26 | // Regexes and supporting functions are cached through closure 27 | return (date, mask, utc, gmt) => { 28 | // You can't provide utc if you skip other args (use the 'UTC:' mask prefix) 29 | if (arguments.length === 1 && kindOf(date) === 'string' && !/\d/.test(date)) { 30 | mask = date; 31 | date = undefined; 32 | } 33 | 34 | date = date || date === 0 ? date : new Date(); 35 | 36 | if (!(date instanceof Date)) { 37 | date = new Date(date); 38 | } 39 | 40 | if (isNaN(date)) { 41 | throw TypeError('Invalid date'); 42 | } 43 | 44 | mask = String(dateFormat.masks[mask] || mask || dateFormat.masks['default']); 45 | 46 | // Allow setting the utc/gmt argument via the mask 47 | const maskSlice = mask.slice(0, 4); 48 | if (maskSlice === 'UTC:' || maskSlice === 'GMT:') { 49 | mask = mask.slice(4); 50 | utc = true; 51 | if (maskSlice === 'GMT:') { 52 | gmt = true; 53 | } 54 | } 55 | 56 | const _ = () => (utc ? 'getUTC' : 'get'); 57 | const d = () => date[`${_()}Date`](); 58 | const D = () => date[`${_()}Day`](); 59 | const m = () => date[`${_()}Month`](); 60 | const y = () => date[`${_()}FullYear`](); 61 | const H = () => date[`${_()}Hours`](); 62 | const M = () => date[`${_()}Minutes`](); 63 | const s = () => date[`${_()}Seconds`](); 64 | const L = () => date[`${_()}Milliseconds`](); 65 | const o = () => (utc ? 0 : date.getTimezoneOffset()); 66 | const W = () => getWeek(date); 67 | const N = () => getDayOfWeek(date); 68 | 69 | const flags = { 70 | d: () => d(), 71 | dd: () => pad(d()), 72 | ddd: () => dateFormat.i18n.dayNames[D()], 73 | DDD: () => 74 | getDayName({ 75 | y: y(), 76 | m: m(), 77 | d: d(), 78 | _: _(), 79 | dayName: dateFormat.i18n.dayNames[D()], 80 | short: true, 81 | }), 82 | dddd: () => dateFormat.i18n.dayNames[D() + 7], 83 | DDDD: () => 84 | getDayName({ 85 | y: y(), 86 | m: m(), 87 | d: d(), 88 | _: _(), 89 | dayName: dateFormat.i18n.dayNames[D() + 7], 90 | }), 91 | m: () => m() + 1, 92 | mm: () => pad(m() + 1), 93 | mmm: () => dateFormat.i18n.monthNames[m()], 94 | mmmm: () => dateFormat.i18n.monthNames[m() + 12], 95 | yy: () => String(y()).slice(2), 96 | yyyy: () => pad(y(), 4), 97 | h: () => H() % 12 || 12, 98 | hh: () => pad(H() % 12 || 12), 99 | H: () => H(), 100 | HH: () => pad(H()), 101 | M: () => M(), 102 | MM: () => pad(M()), 103 | s: () => s(), 104 | ss: () => pad(s()), 105 | l: () => pad(L(), 3), 106 | L: () => pad(Math.floor(L() / 10)), 107 | t: () => (H() < 12 ? dateFormat.i18n.timeNames[0] : dateFormat.i18n.timeNames[1]), 108 | tt: () => (H() < 12 ? dateFormat.i18n.timeNames[2] : dateFormat.i18n.timeNames[3]), 109 | T: () => (H() < 12 ? dateFormat.i18n.timeNames[4] : dateFormat.i18n.timeNames[5]), 110 | TT: () => (H() < 12 ? dateFormat.i18n.timeNames[6] : dateFormat.i18n.timeNames[7]), 111 | Z: () => 112 | gmt 113 | ? 'GMT' 114 | : utc 115 | ? 'UTC' 116 | : (String(date).match(timezone) || ['']) 117 | .pop() 118 | .replace(timezoneClip, '') 119 | .replace(/GMT\+0000/g, 'UTC'), 120 | o: () => (o() > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o()) / 60) * 100 + (Math.abs(o()) % 60), 4), 121 | p: () => 122 | `${(o() > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o()) / 60), 2)}:${pad( 123 | Math.floor(Math.abs(o()) % 60), 124 | 2, 125 | )}`, 126 | S: () => ['th', 'st', 'nd', 'rd'][d() % 10 > 3 ? 0 : (((d() % 100) - (d() % 10) != 10) * d()) % 10], 127 | W: () => W(), 128 | WW: () => pad(W()), 129 | N: () => N(), 130 | }; 131 | 132 | return mask.replace(token, match => { 133 | if (match in flags) { 134 | return flags[match](); 135 | } 136 | return match.slice(1, match.length - 1); 137 | }); 138 | }; 139 | })(); 140 | 141 | dateFormat.masks = { 142 | default: 'ddd mmm dd yyyy HH:MM:ss', 143 | shortDate: 'm/d/yy', 144 | paddedShortDate: 'mm/dd/yyyy', 145 | mediumDate: 'mmm d, yyyy', 146 | longDate: 'mmmm d, yyyy', 147 | fullDate: 'dddd, mmmm d, yyyy', 148 | shortTime: 'h:MM TT', 149 | mediumTime: 'h:MM:ss TT', 150 | longTime: 'h:MM:ss TT Z', 151 | isoDate: 'yyyy-mm-dd', 152 | isoTime: 'HH:MM:ss', 153 | isoDateTime: "yyyy-mm-dd'T'HH:MM:sso", 154 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'", 155 | expiresHeaderFormat: 'ddd, dd mmm yyyy HH:MM:ss Z', 156 | }; 157 | 158 | // Internationalization strings 159 | dateFormat.i18n = { 160 | dayNames: [ 161 | 'Sun', 162 | 'Mon', 163 | 'Tue', 164 | 'Wed', 165 | 'Thu', 166 | 'Fri', 167 | 'Sat', 168 | 'Sunday', 169 | 'Monday', 170 | 'Tuesday', 171 | 'Wednesday', 172 | 'Thursday', 173 | 'Friday', 174 | 'Saturday', 175 | ], 176 | monthNames: [ 177 | 'Jan', 178 | 'Feb', 179 | 'Mar', 180 | 'Apr', 181 | 'May', 182 | 'Jun', 183 | 'Jul', 184 | 'Aug', 185 | 'Sep', 186 | 'Oct', 187 | 'Nov', 188 | 'Dec', 189 | 'January', 190 | 'February', 191 | 'March', 192 | 'April', 193 | 'May', 194 | 'June', 195 | 'July', 196 | 'August', 197 | 'September', 198 | 'October', 199 | 'November', 200 | 'December', 201 | ], 202 | timeNames: ['a', 'p', 'am', 'pm', 'A', 'P', 'AM', 'PM'], 203 | }; 204 | 205 | const pad = (val, len) => { 206 | val = String(val); 207 | len = len || 2; 208 | while (val.length < len) { 209 | val = `0${val}`; 210 | } 211 | return val; 212 | }; 213 | 214 | const getDayName = ({ y, m, d, _, dayName, short = false }) => { 215 | const today = new Date(); 216 | const yesterday = new Date(); 217 | yesterday.setDate(yesterday[`${_}Date`]() - 1); 218 | const tomorrow = new Date(); 219 | tomorrow.setDate(tomorrow[`${_}Date`]() + 1); 220 | const today_d = () => today[`${_}Date`](); 221 | const today_m = () => today[`${_}Month`](); 222 | const today_y = () => today[`${_}FullYear`](); 223 | const yesterday_d = () => yesterday[`${_}Date`](); 224 | const yesterday_m = () => yesterday[`${_}Month`](); 225 | const yesterday_y = () => yesterday[`${_}FullYear`](); 226 | const tomorrow_d = () => tomorrow[`${_}Date`](); 227 | const tomorrow_m = () => tomorrow[`${_}Month`](); 228 | const tomorrow_y = () => tomorrow[`${_}FullYear`](); 229 | 230 | if (today_y() === y && today_m() === m && today_d() === d) { 231 | return short ? 'Tdy' : 'Today'; 232 | } else if (yesterday_y() === y && yesterday_m() === m && yesterday_d() === d) { 233 | return short ? 'Ysd' : 'Yesterday'; 234 | } else if (tomorrow_y() === y && tomorrow_m() === m && tomorrow_d() === d) { 235 | return short ? 'Tmw' : 'Tomorrow'; 236 | } 237 | return dayName; 238 | }; 239 | 240 | /** 241 | * Get the ISO 8601 week number 242 | * Based on comments from 243 | * http://techblog.procurios.nl/k/n618/news/view/33796/14863/Calculate-ISO-8601-week-and-year-in-javascript.html 244 | * 245 | * @param {object} date - date object 246 | * @returns {number} - week number 247 | */ 248 | const getWeek = date => { 249 | // Remove time components of date 250 | const targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate()); 251 | 252 | // Change date to Thursday same week 253 | targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3); 254 | 255 | // Take January 4th as it is always in week 1 (see ISO 8601) 256 | const firstThursday = new Date(targetThursday.getFullYear(), 0, 4); 257 | 258 | // Change date to Thursday same week 259 | firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3); 260 | 261 | // Check if daylight-saving-time-switch occurred and correct for it 262 | const ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset(); 263 | targetThursday.setHours(targetThursday.getHours() - ds); 264 | 265 | // Number of weeks between target Thursday and first Thursday 266 | const weekDiff = (targetThursday - firstThursday) / (86400000 * 7); 267 | return 1 + Math.floor(weekDiff); 268 | }; 269 | 270 | /** 271 | * Get ISO-8601 numeric representation of the day of the week 272 | * 1 (for Monday) through 7 (for Sunday) 273 | * 274 | * @param {object} date - date 275 | * @returns {number} - day of the week 276 | */ 277 | const getDayOfWeek = date => { 278 | let dow = date.getDay(); 279 | if (dow === 0) { 280 | dow = 7; 281 | } 282 | return dow; 283 | }; 284 | 285 | /** 286 | * kind-of shortcut 287 | * 288 | * @param {*} val - input value 289 | * @returns {string} - shortcut 290 | */ 291 | const kindOf = val => { 292 | if (val === null) { 293 | return 'null'; 294 | } 295 | 296 | if (val === undefined) { 297 | return 'undefined'; 298 | } 299 | 300 | if (typeof val !== 'object') { 301 | return typeof val; 302 | } 303 | 304 | if (Array.isArray(val)) { 305 | return 'array'; 306 | } 307 | 308 | return {}.toString.call(val).slice(8, -1).toLowerCase(); 309 | }; 310 | 311 | // eslint-disable-next-line no-undef 312 | if (typeof define === 'function' && define.amd) { 313 | // eslint-disable-next-line no-undef 314 | define(() => { 315 | return dateFormat; 316 | }); 317 | } else if (typeof exports === 'object') { 318 | module.exports = dateFormat; 319 | } else { 320 | global.dateFormat = dateFormat; 321 | } 322 | })(this); 323 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/fb-checkpresence.png) 2 | # ioBroker.fb-checkpresence 3 | ![GitHub license](https://img.shields.io/github/license/afuerhoff/iobroker.fb-checkpresence) 4 | [![Downloads](https://img.shields.io/npm/dm/iobroker.fb-checkpresence.svg)](https://www.npmjs.com/package/iobroker.fb-checkpresence) 5 | ![GitHub repo size](https://img.shields.io/github/repo-size/afuerhoff/iobroker.fb-checkpresence) 6 | 7 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/afuerhoff/iobroker.fb-checkpresence) 8 | ![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/afuerhoff/iobroker.fb-checkpresence/latest) 9 | ![GitHub last commit](https://img.shields.io/github/last-commit/afuerhoff/iobroker.fb-checkpresence) 10 | ![GitHub issues](https://img.shields.io/github/issues/afuerhoff/iobroker.fb-checkpresence) 11 | [![Known Vulnerabilities](https://snyk.io/test/github/afuerhoff/ioBroker.fb-checkpresence/badge.svg)](https://snyk.io/test/github/afuerhoff/ioBroker.fb-checkpresence) 12 | 13 | [![NPM](https://nodei.co/npm/iobroker.fb-checkpresence.png?downloads=true)](https://nodei.co/npm/iobroker.fb-checkpresence/) 14 | 15 | ![Number of Installations (latest)](https://iobroker.live/badges/fb-checkpresence-installed.svg) 16 | ![Stable version](https://iobroker.live/badges/fb-checkpresence-stable.svg) 17 | [![Latest NPM version](https://img.shields.io/npm/v/iobroker.fb-checkpresence.svg)](https://www.npmjs.com/package/iobroker.fb-checkpresence) 18 | 19 | **Tests:** ![Test and Release](https://github.com/afuerhoff/ioBroker.fb-checkpresence/workflows/Test%20and%20Release/badge.svg) 20 | 21 | ## fb-checkpresence adapter for ioBroker 22 | 23 | The adapter checks the presence of family members over the fritzbox. 24 | You must fill in the name of the family member and the mac-address (or ip-address) of the used device. 25 | The comment is optional and you can enable or disable the family member. 26 | The datapoint is based on the member name. 27 | 28 | ### Used open source code 29 | #### npm dateformat v4.5.3 30 | (c) 2007-2009 Steven Levithan 31 | npm: https://www.npmjs.com/package/dateformat 32 | github: https://github.com/felixge/node-dateformat 33 | license: MIT 34 | 35 | ### Adapter pre conditions 36 | For the correct function you have to install a history adapter. You can choose 37 | one of the following adapters: 38 | * History 39 | * SQL 40 | * InfluxDB 41 | 42 | ## Used device 43 | For this adapter the AVM Fritzbox is used. Here you can find informations about 44 | the Fritzbox https://avm.de/produkte/fritzbox/. 45 | The fritzbox services are used over the TR-064 protocol. 46 | 47 | ### Fritzbox conditions 48 | 49 | The used TR-064 interface from the fritzbox is described here: https://avm.de/service/schnittstellen/. 50 | Following TR-064 services and actions are used: 51 | * Hosts:1 - X_AVM-DE_GetHostListPath (supported since 2017-01-09) 52 | * Hosts:1 - X_AVM-DE_GetMeshListPath 53 | * Hosts:1 - GetSpecificHostEntry 54 | * Hosts:1 - X_AVM-DE_GetSpecificHostEntryByIP (supported since 2016-05-18) 55 | * DeviceInfo:1 - GetSecurityPort 56 | * DeviceInfo:1 - GetInfo 57 | * WANPPPConnection:1 - GetInfo 58 | * WANIPConnection:1 - GetInfo 59 | * WLANConfiguration3 - SetEnable 60 | * WLANConfiguration3 - GetInfo 61 | * WLANConfiguration3 - GetSecurityKeys 62 | * X_AVM-DE_HostFilter - DisallowWANAccessByIP 63 | * X_AVM-DE_HostFilter - GetWANAccessByIP 64 | * DeviceConfig:1 - Reboot 65 | * LANConfigSecurity1 - X_AVM-DE_GetCurrentUser 66 | 67 | By default, the TR-064 interface is not activated. However, this can easily be changed via the 68 | FritzBox web interface. To do this log in into your FritzBox and ensure that the expert view is activated. 69 | Then you will find below "Home Network »Home Network Overview» Network Settings" the point 70 | "Allow access for applications". There you have to activate the checkbox and then restart the FritzBox once. 71 | 72 | Hint: After changing the options, don't forget the restart of the Fritzbox ! 73 | 74 | 75 | ## Configuration dialog 76 | 77 | ### General 78 | The configuration values are validated and only correct values can be saved. Otherwise the save button is disabled. 79 | 80 | ### Fritzbox IP-address, user and password 81 | The configuration of ip-address, user and password is necessary to get the device data from the fritzbox. 82 | Therefore a user has to be created in the fritzbox. This is required with newer 83 | firmware version (>= 7.25)of the fritzbox. See here fore information: https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/Empfehlungen%20zur%20Benutzerfu%CC%88hrung%20bei%20der%20Anmeldung%20an%20einer%20FRITZ%21Box_v1.1.pdf 84 | The password is encrypted and wasn't saved in clear text. The user name and password may have a maximum of 85 | 32 characters. See for information: https://service.avm.de/help/de/FRITZ-Box-Fon-WLAN-7490/014/hilfe_zeichen_fuer_kennwoerter#:~:text=Namen%20f%C3%BCr%20Benutzer,Kennwortfeld%20darf%20nicht%20leer%20sein. 86 | Hint: In some cases it could be that the fritzbox blocked the user if the password was not correctly inserted. 87 | Often a timeout message is in the log. Please check than the if you has insert the correct username and password.Then you have to reboot the fritzbox. 88 | 89 | ### Ssl option 90 | In some cases the adapter could not connect to the fritzbox. It could help to disable this option. 91 | In this case the adapter tries to connect without https. 92 | 93 | ### Interval 94 | You have separate intervals for family members and Fritzbox devices. 95 | The interval for Fritzbox devices can be configured from 10s to 3600s. Normally a value between 60 and 300 seconds is an optimal interval to read the fritzbox data. Family members could be configured from 10s to 600s. Every new cycle starts if the previous cycle is finished. 96 | 97 | ### Filter time 98 | If the filter time is greater than 0s the state of a family member is checked twice (after the filter time) if the state is changing to false. If the state is true the state is immediate set. 99 | 100 | ### History adapter 101 | Over the history adapter some values are calculated. You can choose, if the history, the sql or the influxdb adapter is used for this calculations. The history adapter must be installed preliminary and can then selected in the configuration dialog. 102 | If the history configuration is disabled then the calculation of some values could not be realized. 103 | 104 | ### Dateformat 105 | The date format mask options are described on this web page: https://www.npmjs.com/package/dateformat. 106 | The format mask is used for formatting the html and json table objects. 107 | 108 | ### Creation of FB devices 109 | If this option is checked, the objects for every device in the Fritzbox device list are created. 110 | If this option is disabled, then also the mesh informations are disabled. 111 | 112 | ### Resynchronisation of FB device objects 113 | If this option is checked, then the FB device object are re-synchronized with the device list fom Fritzbox. 114 | 115 | ### Creation of mesh information 116 | This option can be checked if the creation of FB devices is allowed. If this option is checked, 117 | the mesh objects for every device in the Fritzbox device list are created. 118 | 119 | ### guest information 120 | If this option is checked the states for guests are created. 121 | 122 | ### qr-code generation 123 | If this option is checked the qr-code from guest wlan is generated. 124 | You can show this QR code in your VIS with the widget "Basic Boolesches SVG". 125 | Please use following settings: 126 | 127 | 128 | ### Family member settings 129 | For a configured family member you should enter the member name, the hostname, the mac- and ip-address, a comment and you can enable or disable the member. A group is optional. 130 | If you leave the group empty and set the compatibility flag to true the behaviour is like an older version of the adaper. You can use the presence state from the family member or the state directly mapped to the family member name. In a future version you must use the presence state. This behaviour could be switched on/off with the compatibility checkbox: 131 | -> compatibility = true: behaviour as an older version with empty group. 132 | -> compatibility = true and group not empty: new behaviour. All states beneath the familymembers folder. 133 | -> compatibility = false: new behaviour. All states beneath the familymembers folder. 134 | 135 | For every member the adapter creates a presence state and checks if the member is present or absent. The state was changed if the presence state changed. 136 | You can also enable the filtering for a member. If the state is true the state changes immediately to true. If it is false then the value will checked after the filter time again. 137 | If the state is in both cases false then the state changes to false. Otherwise it does not change. 138 | 139 | To get the speed information in the objects you have to select fb-devices option. 140 | 141 | ### Manually trigger presence 142 | In javascript you can trigger the presence manually. When you send the message to the adapter 143 | every new message is blocked for 10 seconds. You receive a negative result (false) if the message is blocked. 144 | True if the message is received from the adapter. 145 | ` 146 | sendTo('fb-checkpresence.0', 'triggerPresence', {} 147 | , function (result) { 148 | log(result, 'info'); 149 | }); 150 | ` 151 | 152 | ### Whitelist settings 153 | In the white list you can insert every known device. Any unknown devices are listed in the blacklist object. 154 | If you check the checkbox in the headline of the table all devices are selected. 155 | 156 | In Javascript you can send an item to the whitelist. 157 | The sent data (hostname, MAC) is compared with the Fritzbox device list. If the entry is present, it is checked whether it is already saved in the whitelist. If not, the entry is saved in the whitelist configuration table. 158 | 159 | sendTo('fb-checkpresence.0', 'addDeviceToWhitelist', 160 | { 161 | hostname: 'devicename', 162 | mac: '00:00:00:00:00:00' 163 | } 164 | , function (result) { 165 | log(result, 'info'); 166 | }); 167 | 168 | ## Features 169 | 170 | ### AVM support check 171 | The function checks the availability of used fritzbox features. The availability is logged as info. If you have problems look if the features are all set to true. Also the access rights are checked for the user and 172 | the feature is set to false if the acces right is not correct. 173 | 174 | ### Switch on / off the guest wlan 175 | Under the folder guest you can set the state wlan to true or false and then the guest wlan switches on or off. 176 | 177 | ### QR code of guest wlan 178 | The QR code of the guest wlan is saved in the state wlanQR in the guest folder. The QR code can show in vis in the basic - Bool SVG widget. 179 | 180 | ### Switch on / off the internet access of Fritzbox devices 181 | Under the folder FB-devices you could set the disabled state to true or false and the the internet access of this device 182 | is blocked in the Fritzbox. 183 | 184 | ### Get guests, blacklist 185 | In this function it is checked if any user is logged in as guest. Also is checked if any device is not in the white list listed. 186 | This devices are added to the blacklist. 187 | 188 | ### Get Active 189 | For every family member the presence, the comming and going dates and several other infos are calculated and saved in the member object if a history adapter is selected. 190 | 191 | ### Host number, active devices 192 | The amount of devices and how many are active are get from the fritzbox. 193 | 194 | ## Objects 195 | 196 | ### Object presenceAll 197 | If all family members are present then the object is true. 198 | 199 | ### Object presence 200 | If one family member ist present then the object is true. 201 | 202 | ### Object devices 203 | These are all listed devices in the fritzbox 204 | 205 | ### Object activeDevices 206 | These are the amount of all active devices in the fritzbox 207 | 208 | ### Object html, json 209 | These objects are tables (json and html) with the comming and going information of all family members in it. 210 | 211 | ### Object info 212 | Here are informations listed about the last update and the connection status from the adapter. 213 | 214 | ### Object guest 215 | Here are informations listed about the amount of active guests and table objects with the device information in it. 216 | 217 | ### Object blacklist 218 | Here are informations listed about the amount of unknown devices and table objects with the unknown device information in it. 219 | 220 | ### Object member.present 221 | Here you will find information about the presence of a member on the current day and how long the member has been the status true since the last change. 222 | 223 | ### Object member.absent 224 | Here you will find information about the absence of a member on the current day and how long the member has been the status false since the last change. 225 | 226 | ### Object member.comming, member.going 227 | Here you will find information when the family member arrives or leaving home. 228 | 229 | ### Object member.history, member.historyHtml 230 | Here you will find information about the history of the current day. 231 | 232 | ## Changelog 233 | ### **WORK IN PROGRESS** 234 | * (afuerhoff) dependencies updated 235 | * (afuerhoff) dependabot.yml fixed [#358](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/358) 236 | 237 | ### 1.4.2 (2025-10-30) 238 | * (afuerhoff) dependencies updated 239 | * (afuerhoff) package.json issues fixed [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350) 240 | * (afuerhoff) npm security changes 241 | * (afuerhoff) filter time extended to 300s 242 | * (afuerhoff) guest wlan bug fixed [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353) 243 | * (afuerhoff) deprecated functions changed 244 | 245 | ### 1.4.1 (2025-09-19) 246 | * (afuerhoff) dependencies updated 247 | * (afuerhoff) repository checker & code scanning issues fixed 248 | 249 | ### 1.4.0 (2025-05-28) 250 | * (afuerhoff) dependencies updated 251 | * (afuerhoff) error handling optimized 252 | * (afuerhoff) enhancement [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336) 253 | * (afuerhoff) issue [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337) 254 | * (afuerhoff) issue [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335) 255 | 256 | ### 1.3.1 (2025-03-02) 257 | * (afuerhoff) dependencies updated 258 | * (afuerhoff) bug fixed [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333) 259 | * (afuerhoff) bug fixed [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305) 260 | 261 | ### 1.3.0 (2025-02-14) 262 | * (afuerhoff) dependencies updated 263 | * (afuerhoff) eslint setup changed 264 | * (afuerhoff) ipv6 ip-address and prefix added 265 | 266 | ## License 267 | MIT License 268 | 269 | Copyright (c) 2019-2025 Achim Fürhoff 270 | 271 | Permission is hereby granted, free of charge, to any person obtaining a copy 272 | of this software and associated documentation files (the "Software"), to deal 273 | in the Software without restriction, including without limitation the rights 274 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 275 | copies of the Software, and to permit persons to whom the Software is 276 | furnished to do so, subject to the following conditions: 277 | 278 | The above copyright notice and this permission notice shall be included in all 279 | copies or substantial portions of the Software. 280 | 281 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 282 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 283 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 284 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 285 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 286 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 287 | SOFTWARE. -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "fb-checkpresence", 4 | "version": "1.4.2", 5 | "news": { 6 | "1.4.2": { 7 | "en": "dependencies updated\npackage.json issues fixed [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm security changes\nfilter time extended to 300s\nguest wlan bug fixed [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\ndeprecated functions changed", 8 | "de": "aktualisierte abhängigkeiten\npaket.json-Ausgaben behoben [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm sicherheitsänderungen\nfilterzeit verlängert auf 300s\ngast wlan Bug behoben [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\ndepretiert funktionen geändert", 9 | "ru": "обновленные зависимости\npackage.json issues fixed [#350] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nизменения в безопасности pm\nвремя фильтрации увеличено до 300\nгостевой wlan bug fixed [#353] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\nизменились устаревшие функции", 10 | "pt": "dependências atualizadas\npackage.json issues fixed [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nalterações de segurança do npm\ntempo de filtro estendido para 300s\nwlan convidado corrigido [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\nfunções desactualizadas alteradas", 11 | "nl": "afhankelijkheden bijgewerkt\npackage.json issues fixed [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm beveiligingswijzigingen\nfiltertijd verlengd tot 300s\nguest wlan bug fixed [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\nverouderde functies gewijzigd", 12 | "fr": "dépendances mises à jour\npaquet.json numéros corrigés [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm changements de sécurité\ndurée du filtre étendue à 300s\nguest wlan bug corrigé [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\nfonctions dépréciées modifiées", 13 | "it": "dipendenze aggiornate\n(https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm modifiche di sicurezza\ntempo di filtro esteso a 300s\nguest wlan bug fisso [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\nfunzioni deprecate modificate", 14 | "es": "dependencias actualizadas\npackage.json issues fixed [#350](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nnpm cambios de seguridad\ntiempo de filtro extendido a 300\nguest wlan bug fixed [#353](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/353)\ncambios en las funciones deprecatadas", 15 | "pl": "zaktualizowane zależności\ninne informacje\nnpm zmiany bezpieczeństwa\nczas filtra wydłużony do 300 s\nguest wlan bug fixed [# 353] (https: / / github.com / afuerhoff / ioBroker.fb- checkobecności / issues / 353)\nzmienione funkcje", 16 | "uk": "оновлені залежності\n[#350] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/350)\nзміни безпеки npm\nтривалість фільтра до 300-х\nhref=\"http://realtor.if.ua/\" title=\"Агентство нерухомості Ріелтор\" target=\"_blank\">\"Агентство\nзмінені функції", 17 | "zh-cn": "更新依赖关系\n(https://github.com/afuerhoff/ioBroker.fb-check presence/issues/350) 固定版\nnpm 安全变化\n过滤时间延长至 300s\n宾客 wlan bug 固定 [# 353] (https://github.com/afuerhoff/ioBroker.fb-check presence/issues/353) (中文(简体) )\n已贬值的函数已更改" 18 | }, 19 | "1.4.1": { 20 | "en": "dependencies updated\nrepository checker & code scanning issues fixed", 21 | "de": "Abhängigkeiten aktualisiert\nRepository-Checker und Code-Scanning-Probleme behoben", 22 | "ru": "обновлены зависимости\nисправлены проблемы с проверкой репозитория и сканированием кода", 23 | "pt": "dependências atualizadas\nProblemas de verificação de repositório e varredura de código corrigidos", 24 | "nl": "afhankelijkheden bijgewerkt\nRepository checker & code scanning issues opgelost", 25 | "fr": "dépendances mises à jour\nProblèmes de vérification du dépôt et de balayage de code corrigés", 26 | "it": "dipendenze aggiornate\nproblemi risolti del controllo del repository e della scansione del codice", 27 | "es": "dependencias actualizadas\nSe solucionaron problemas de verificación de repositorio y escaneo de código.", 28 | "pl": "zaktualizowane zależności\nnaprawione problemy z sprawdzaniem repozytorium i skanowaniem kodu", 29 | "uk": "оновлено залежності\nвиправлені проблеми з перевіркою репозиторію та скануванням коду", 30 | "zh-cn": "依赖项已更新\n修复了存储库检查器和代码扫描问题" 31 | }, 32 | "1.4.0": { 33 | "en": "dependencies updated\nerror handling optimized\nenhancement [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\nissue [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\nissue [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 34 | "de": "aktualisierte abhängigkeiten\nfehlerbehandlung optimiert\nverbesserung [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\nausgabe [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\nausgabe [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 35 | "ru": "обновленные зависимости\nоптимизация обработки ошибок\nрасширение [#336] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\n#337 (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\n#335 (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 36 | "pt": "dependências atualizadas\nmanipulação de erros otimizado\nrealce [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336))\n[#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337))\n[#335] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335))", 37 | "nl": "afhankelijkheden bijgewerkt\nfoutafhandeling geoptimaliseerd\nverbetering [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\nnummer [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\nnummer [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 38 | "fr": "dépendances mises à jour\ngestion des erreurs optimisée\namélioration [#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\nnuméro [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\nnuméro [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 39 | "it": "dipendenze aggiornate\ngestione degli errori ottimizzata\n[#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\nemissione [#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\nemissione [#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 40 | "es": "dependencias actualizadas\nmanejo de errores optimizado\n[#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/336)\n[#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/337)\n[#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/335)", 41 | "pl": "zaktualizowane zależności\nzoptymalizowana obsługa błędów\n(Dz.U. L 328 z 7.12.2013, s. 1)\ndistribution [# 337] (https: / / github.com / afuerhoff / ioBroker.fb- checkobecności / issues / 337)\ndata urodzenia: 7.7.1956", 42 | "uk": "оновлені залежності\nоптимізація обробки помилок\n[#336](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/336)\n[#337](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/337)\n[#335](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/335)", 43 | "zh-cn": "更新依赖关系\n优化了错误处理\n[336] (https://github.com/afuerhoff/ioBroker.fb-check presence/issues/336)\n[337] (https://github.com/afuerhoff/ioBroker.fb-check presence/issues/337)\n(https://github.com/afuerhoff/ioBroker.fb-check presence/issues/335) (中文(简体) )" 44 | }, 45 | "1.3.1": { 46 | "en": "dependencies updated\nbug fixed [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug fixed [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 47 | "de": "aktualisierte abhängigkeiten\nfehler behoben [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nfehler behoben [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 48 | "ru": "обновленные зависимости\nисправленный [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nисправленный [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 49 | "pt": "dependências atualizadas\nbug corrigido [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug corrigido [#305] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 50 | "nl": "afhankelijkheden bijgewerkt\nbugfixed [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug fixed [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 51 | "fr": "dépendances mises à jour\nbug corrigé [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug corrigé [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 52 | "it": "dipendenze aggiornate\nbug fisso [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug fisso [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 53 | "es": "dependencias actualizadas\nbug fixed [#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/333)\nbug fixed [#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/305)", 54 | "pl": "zaktualizowane zależności\nrozporządzenie Parlamentu Europejskiego i Rady (UE) nr 1303 / 2013 z dnia 11 grudnia 2013 r. w sprawie Europejskiego Funduszu Społecznego (Dz.U. L 347 z 20.12.2013, s. 671)\nrozporządzenie Parlamentu Europejskiego i Rady (UE) nr 1303 / 2013 z dnia 11 grudnia 2013 r. w sprawie zasad finansowych mających zastosowanie do budżetu ogólnego Unii oraz uchylające rozporządzenie Rady (WE) nr 1606 / 2002 (Dz.U. L 347 z 20.12.2013, s. 671)", 55 | "uk": "оновлені залежності\n[#333](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/333)\n[#305](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/305)", 56 | "zh-cn": "更新依赖关系\n错误固定 [# 333] (https://github.com/afuerhoff/ioBroker.fb-check presence/ probes/333) (中文(简体) )\n错误已固定 [# 305] (https://github.com/afuerhoff/ioBroker.fb- check presence/issues/305) " 57 | }, 58 | "1.3.0": { 59 | "en": "dependencies updated\neslint setup changed\nipv6 ip-address and prefix added", 60 | "de": "aktualisierte abhängigkeiten\neslint setup geändert\nipv6 ip-address und prefix hinzugefügt", 61 | "ru": "обновленная информация о зависимостях\neslint настройка изменена\nipv6 ip-адрес и префикс добавлены", 62 | "pt": "dependências atualizadas\neslint configuração alterada\nipv6 ip-address e prefixo adicionado", 63 | "nl": "afhankelijkheden bijgewerkt\neslint-configuratie gewijzigd\nipv6 ip-adres en voorvoegsel toegevoegd", 64 | "fr": "dépendances mises à jour\nconfiguration d'eslint changé\nipv6 ip-adresse et préfixe ajoutés", 65 | "it": "dipendenze aggiornate\nimpostazione eslint cambiata\nipv6 ip-address e prefisso aggiunto", 66 | "es": "dependencias actualizadas\neslint setup changed\nipv6 ip-address y prefijo añadido", 67 | "pl": "zaktualizowane zależności\nzmiana konfiguracji eslinta\nipv6 ip- address i prefix dodany", 68 | "uk": "оновлені залежності\nзмінено налаштування eslint\nipv6 ip-address і prefix додано", 69 | "zh-cn": "更新依赖关系\neslint 设置已更改\n添加 ipv6 地址和前缀" 70 | }, 71 | "1.2.8": { 72 | "en": "bugfix configuration\ndependencies updated", 73 | "de": "bugfix konfiguration\naktualisierte abhängigkeiten", 74 | "ru": "конфигурация исправления\nобновленная информация о зависимостях", 75 | "pt": "configuração do bugfix\ndependências atualizadas", 76 | "nl": "bugfix configuratie\nafhankelijkheden bijgewerkt", 77 | "fr": "configuration du correctif\ndépendances mises à jour", 78 | "it": "configurazione bugfix\ndipendenze aggiornate", 79 | "es": "configuración de bugfix\ndependencias actualizadas", 80 | "pl": "konfiguracja bugfix\nzaktualizowane zależności", 81 | "uk": "налаштування помилок\nоновлені залежності", 82 | "zh-cn": "错误修正配置\n更新依赖关系" 83 | }, 84 | "1.2.7": { 85 | "en": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 86 | "de": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 87 | "ru": "bugfix [#319] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 88 | "pt": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 89 | "nl": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 90 | "fr": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 91 | "it": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 92 | "es": "bugfix [#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319)", 93 | "pl": "bugfix [# 319] (https: / / github.com / afuerhoff / ioBroker.fb- checkobecności / issues / 319)", 94 | "uk": "[#319](https://github.com/afuerhoff/ioBroker.fb-checkpresence/products/319)", 95 | "zh-cn": "bugfix [# 319] (https://github.com/afuerhoff/ioBroker.fb-checkpresence/issues/319) (中文(简体) )" 96 | } 97 | }, 98 | "titleLang": { 99 | "en": "Fritzbox CheckPresence", 100 | "de": "Fritzbox CheckPresence", 101 | "ru": "Fritzbox CheckPresence", 102 | "pt": "Fritzbox CheckPresence", 103 | "nl": "Fritzbox CheckPresence", 104 | "fr": "Fritzbox CheckPresence", 105 | "it": "Fritzbox CheckPresence", 106 | "es": "Fritzbox CheckPresence", 107 | "pl": "Fritzbox CheckPresence", 108 | "zh-cn": "Fritzbox CheckPresence", 109 | "uk": "Fritzbox CheckPresence" 110 | }, 111 | "desc": { 112 | "en": "The adapter checks the presence of family members over the fritzbox. You must fill in the name of the family member and the mac-address of the used device. The comment is optional and you can enable or disable the family member. The datapoint based on the member name.", 113 | "de": "Der Adapter prüft die Anwesenheit von Familienmitgliedern über die Fritzbox. ", 114 | "ru": "Адаптер проверяет присутствие членов семьи через fritzbox. ", 115 | "pt": "O adaptador verifica a presença de membros da família sobre o fritzbox. ", 116 | "nl": "De adapter controleert de aanwezigheid van familieleden via de fritzbox. ", 117 | "fr": "L'adaptateur vérifie la présence de membres de la famille sur la fritzbox. ", 118 | "it": "L'adattatore controlla la presenza dei membri della famiglia sul fritzbox. ", 119 | "es": "El adaptador comprueba la presencia de miembros de la familia sobre el fritzbox. ", 120 | "pl": "Adapter sprawdza obecność członków rodziny na fritzbox. ", 121 | "zh-cn": "适配器通过fritzbox检查家庭成员的存在。", 122 | "uk": "Адаптер перевіряє присутність членів сім'ї над fritzbox. Необхідно вказати ім'я члена сім'ї та mac-адресу використовуваного пристрою. Коментар необов’язковий, і ви можете ввімкнути або вимкнути члена сім’ї. Точка даних на основі імені члена." 123 | }, 124 | "authors": [ 125 | "Achim Fürhoff " 126 | ], 127 | "keywords": [ 128 | "presence", 129 | "family", 130 | "tr064", 131 | "fritz.box", 132 | "fritzbox" 133 | ], 134 | "licenseInformation": { 135 | "type": "free", 136 | "link": "https://github.com/afuerhoff/ioBroker.fb-checkpresence/blob/master/LICENSE", 137 | "license": "MIT" 138 | }, 139 | "tier": 3, 140 | "platform": "Javascript/Node.js", 141 | "icon": "fb-checkpresence.png", 142 | "enabled": true, 143 | "extIcon": "https://raw.githubusercontent.com/afuerhoff/ioBroker.fb-checkpresence/master/admin/fb-checkpresence.png", 144 | "readme": "https://github.com/afuerhoff/ioBroker.fb-checkpresence/blob/master/README.md", 145 | "loglevel": "info", 146 | "mode": "daemon", 147 | "type": "infrastructure", 148 | "connectionType": "local", 149 | "dataSource": "poll", 150 | "compact": true, 151 | "adminUI": { 152 | "config": "materialize" 153 | }, 154 | "stopBeforeUpdate": true, 155 | "stopTimeout": 1000, 156 | "messagebox": true, 157 | "messages": [ 158 | { 159 | "condition": { 160 | "operand": "and", 161 | "rules": [ 162 | "oldVersion<=1.3.2", 163 | "newVersion>=1.3.2" 164 | ] 165 | }, 166 | "title": { 167 | "en": "Important notice!", 168 | "de": "Wichtiger Hinweis!", 169 | "ru": "Важное замечание!", 170 | "pt": "Notícia importante!", 171 | "nl": "Belangrijke mededeling!", 172 | "fr": "Avis important!", 173 | "it": "Avviso IMPORTANTE!", 174 | "es": "Noticia importante!", 175 | "pl": "Ważna uwaga!", 176 | "zh-cn": "重要通知!", 177 | "uk": "Важливе повідомлення!" 178 | }, 179 | "text": { 180 | "en": "The compatibility setting will be removed with the next version.\nIf you still have this setting checked, you should uncheck it immediately and follow the instructions in the log.\nThe object model will change as a result and a familymembers folder will be created under which the data points can then be found.", 181 | "de": "Die Kompatibilitätseinstellung wird mit der nächsten Version entfernt.\nWenn diese Einstellung bei Ihnen noch aktiviert ist, sollten Sie sie umgehend deaktivieren und den Anweisungen im Protokoll folgen.\nDadurch ändert sich das Objektmodell und es wird ein Ordner „familymembers“ erstellt, unter dem die Datenpunkte dann zu finden sind.", 182 | "ru": "Настройка совместимости будет удалена в следующей версии.\nЕсли у вас по-прежнему установлен этот флажок, вам следует немедленно снять его и следовать инструкциям в журнале.\nВ результате изменится объектная модель и будет создана папка familymembers, в которой затем можно будет найти точки данных.", 183 | "pt": "A configuração de compatibilidade será removida na próxima versão.\nSe você ainda tiver essa configuração marcada, desmarque-a imediatamente e siga as instruções no log.\nO modelo de objeto mudará como resultado e uma pasta de membros da família será criada, na qual os pontos de dados poderão ser encontrados.", 184 | "nl": "De compatibiliteitsinstelling wordt verwijderd bij de volgende versie.\nAls u deze instelling nog steeds hebt aangevinkt, moet u deze onmiddellijk uitvinken en de instructies in het logboek volgen.\nHierdoor verandert het objectmodel en wordt er een map 'familieleden' aangemaakt waarin de datapunten te vinden zijn.", 185 | "fr": "Le paramètre de compatibilité sera supprimé avec la prochaine version.\nSi ce paramètre est toujours coché, vous devez le décocher immédiatement et suivre les instructions du journal.\nLe modèle d'objet changera en conséquence et un dossier familymembers sera créé sous lequel les points de données pourront alors être trouvés.", 186 | "it": "L'impostazione di compatibilità verrà rimossa nella prossima versione.\nSe questa impostazione è ancora selezionata, dovresti deselezionarla immediatamente e seguire le istruzioni nel registro.\nDi conseguenza, il modello dell'oggetto cambierà e verrà creata una cartella \"familymembers\" in cui sarà possibile trovare i punti dati.", 187 | "es": "La configuración de compatibilidad se eliminará con la próxima versión.\nSi aún tiene esta configuración marcada, debe desmarcarla inmediatamente y seguir las instrucciones del registro.\nComo resultado, el modelo de objeto cambiará y se creará una carpeta de miembros de la familia en la que se podrán encontrar los puntos de datos.", 188 | "pl": "Ustawienie zgodności zostanie usunięte w następnej wersji.\nJeśli to ustawienie jest nadal zaznaczone, należy je natychmiast odznaczyć i postępować zgodnie z instrukcjami w dzienniku.\nW wyniku tej zmiany model obiektu ulegnie zmianie, a także zostanie utworzony folder członków rodziny, w którym będzie można znaleźć punkty danych.", 189 | "uk": "Налаштування сумісності буде видалено з наступною версією.\nЯкщо цей параметр усе ще позначено, негайно зніміть його та дотримуйтесь інструкцій у журналі.\nУ результаті модель об’єкта зміниться, і буде створено папку членів родини, у якій можна буде знайти точки даних.", 190 | "zh-cn": "下一版本将删除兼容性设置。\n如果您仍选中此设置,您应该立即取消选中它并按照日志中的说明进行操作。\n对象模型将因此而发生改变,并且会创建一个家庭成员文件夹,然后可以在该文件夹下找到数据点。" 191 | }, 192 | "level": "warn", 193 | "buttons": [ 194 | "agree", 195 | "cancel" 196 | ] 197 | } 198 | ], 199 | "dependencies": [ 200 | { 201 | "js-controller": ">=5.0.19" 202 | } 203 | ], 204 | "globalDependencies": [ 205 | { 206 | "admin": ">=6.17.14" 207 | } 208 | ] 209 | }, 210 | "encryptedNative": [ 211 | "password" 212 | ], 213 | "protectedNative": [ 214 | "password" 215 | ], 216 | "native": { 217 | "familymembers": [], 218 | "whitelist": [], 219 | "ipaddress": "fritz.box", 220 | "ssl": true, 221 | "interval": 60, 222 | "interval_seconds": true, 223 | "intervalFamily": 60, 224 | "delay": 15, 225 | "newfilter": false, 226 | "compatibility": true, 227 | "history": "", 228 | "dateformat": "yyyy.mm.dd HH:MM:ss", 229 | "fbdevices": false, 230 | "meshinfo": false, 231 | "syncfbdevices": false, 232 | "guestinfo": true, 233 | "qrcode": false, 234 | "qrcodecolor": "black", 235 | "extip": true, 236 | "username": "", 237 | "password": "", 238 | "enableWl": true 239 | }, 240 | "objects": [], 241 | "instanceObjects": [ 242 | { 243 | "_id": "info", 244 | "type": "channel", 245 | "common": { 246 | "name": "Information" 247 | }, 248 | "native": {} 249 | } 250 | ] 251 | } 252 | -------------------------------------------------------------------------------- /admin/index_m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | 34 |
35 | 36 | 37 |
38 |
39 |
40 | 41 |

42 | Fb-Checkpresence
43 | Detects the presence of family members via the FRITZBox 44 |

45 |
46 |
47 |
48 |
49 |
    50 |
  • 51 |
    For help click here
    live_help
    52 |
    53 |
      54 |
    • 55 |
      FRITZ!Box settings
      live_help
      56 |
      57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
      SettingsMandatoryDescription
      Help Fritzbox TR-064Yes

      Help Fritzbox TR-064 1v1

      70 | access settings 71 |
      Help Fritzbox registrationYesHelp Fritzbox registration 1v1
      Help Fritzbox userYesHelp Fritzbox user 1v1
      85 | 86 |
      87 |
    • 88 |
    • 89 |
      Adapter settings
      live_help
      90 |
      91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
      SettingsMandatoryDescription
      Help adapter ipYesHelp adapter ip 1v1
      Help adapter usernameYesHelp adapter username 1v0
      Help adapter passwordYesHelp adapter password 1v0
      Help adapter sslYesHelp adapter ssl 1v0
      Help adapter intervalYesHelp adapter interval 1v1
      Help adapter historyNoHelp adapter history 1v2
      Help adapter date formatYesHelp adapter date format 1v0
      Help adapter fb-devicesNoHelp adapter fb-devices 1v1
      Help adapter meshNoHelp adapter mesh 1v1
      Help adapter syncNoHelp adapter sync 1v0
      guestNoHelp adapter guest 1v0
      qrcodeNoHelp adapter qr 1v0
      QR code colorNoHelp adapter qrcolor 1v1
      167 |
      168 |
    • 169 |
    170 |
    171 |
  • 172 |
173 |
174 |
175 | 176 |
177 |
178 | 180 | 181 | tooltipIp 182 |
183 |
184 | 185 | 186 | tooltipUser 187 |
188 |
189 | 190 | 191 | tooltipPw 192 |
193 |
194 | 195 | 196 | tooltipSslv0 197 |
198 |
199 | 200 |
201 |
202 | 209 | 210 | tooltipHistory 211 |
212 |
213 | 214 | 215 | tooltipDateformat 216 |
217 |
218 | 219 | 220 | tooltipIntervalv0 221 |
222 |
223 | 224 |
225 |
226 | 227 | 228 | tooltipFbdevices 229 |
230 |
231 | 232 | 233 | tooltipMesh 234 |
235 |
236 | 237 | 238 | tooltipFbsync 239 |
240 |
241 | 242 |
243 |
244 | 245 | 246 | tooltipGuest 247 |
248 |
249 | 250 | 251 | tooltipQR 252 |
253 |
254 | 255 | 256 | tooltipQRColor 257 |
258 |
259 | 260 |
261 |
262 | 263 | 264 | tooltipExtip 265 |
266 |
267 | 268 |
269 | 270 | 271 |
272 |
273 |
274 |
    275 |
  • 276 |
    For help click here
    live_help
    277 |
    278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 |
    SettingsMandatoryDescription
    Help family intervalYesHelp family interval 1v0
    addNoHelp family add 1
    Add family memberNoHelp family add member 1v0
    Help family memberYesHelp family member 1v2
    Help family macYesHelp family mac 1
    Help family ipYesHelp family ip 1
    Help family use ipNoHelp family use ip 1v0
    Use nameNoHelp family use name 1v0
    Help family commentNoHelp family comment 1
    Help family enabledYesHelp family enabled 1v1
    339 | 340 |
    341 |
  • 342 |
343 |
344 |
345 | 346 |
347 |
348 |
349 | 350 | 351 | tooltipInterval2 352 |
353 |
354 | 355 | 356 | tooltipDelayv1 357 |
358 |
359 | 360 | 361 | tooltipNewFilterv0 362 |
363 |
364 | 365 | 366 | tooltipCompatibilityv0 367 |
368 |
369 |
370 |
371 | add 372 |
373 |
374 | Add family member 375 |
376 |
377 |
378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 |
check_boxGroupFamily memberHostnameMac-addressIp-addressUsage ofUse filterComment
397 |
398 |
399 |
400 | 401 | 402 |
403 |
404 |
405 |
    406 |
  • 407 |
    For help click here
    live_help
    408 |
    409 |

    All known devices in the home network are entered in the whitelist. The devices that are not later found in the whitelist are reported in the object area in the Blacklist folder.

    410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 |
    SettingsMandatoryDescription
    Enable whitelistNoHelp wl enable 1v1
    addNoHelp wl add 1v0
    Add a deviceNoHelp wl add device 1v0
    Help wl deviceYesHelp wl device 1v0
    Help wl macYesHelp wl mac 1v0
    446 |
    447 |
  • 448 |
449 |
450 |
451 | 452 |
453 |
454 |
455 | 456 | 457 | tooltipWlv0 458 |
459 |
460 | add 461 |
462 |
463 | Add a device 464 |
465 |
466 |
467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 |
NrHostnameMac-address
480 |
481 |
482 |
483 |
484 |
485 | 486 | 487 |
488 |
489 | 490 | -------------------------------------------------------------------------------- /admin/index_m.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable no-irregular-whitespace */ 3 | /* eslint-disable-next-line no-undef */ 4 | /* eslint-env jquery, browser */ // https://eslint.org/docs/user-guide/configuring#specifying-environments 5 | /* global sendTo, getEnums, common, systemLang, socket, values2table, table2values, M, _, instance */ // for eslint 6 | /*eslint no-undef: "error"*/ 7 | /*eslint-env browser*/ 8 | 9 | const adapterNamespace = `fb-checkpresence.${instance}`; 10 | 11 | let familymembers = []; 12 | let whitelist = []; 13 | let arr; 14 | let active = false; 15 | 16 | if (typeof _ !== 'function') _ = translateWord; 17 | 18 | function search(event) { 19 | const input = document.getElementById(event.currentTarget.id); 20 | const filter = input.value.toUpperCase(); 21 | let table = null; 22 | if( event.currentTarget.id == 'searchDevice'){ 23 | table = document.getElementById('tabDevices'); 24 | // Enable "reset search" button 25 | $('button#btnResetSearch').attr('disabled', false); 26 | } 27 | if( event.currentTarget.id == 'searchWL'){ 28 | table = document.getElementById('tabWl'); 29 | $('button#btnResetSearchWl').attr('disabled', false); 30 | } 31 | const tr = table.getElementsByTagName('tr'); 32 | // Loop through all table rows, and hide those who don't match the search query 33 | for (let i = 0; i < tr.length; i++) { 34 | const td = tr[i].getElementsByTagName('td')[1]; //search in second column 35 | if (td) { 36 | const txtValue = td.textContent || td.innerText; 37 | if (txtValue.toUpperCase().indexOf(filter) > -1) { 38 | tr[i].style.display = ''; 39 | } else { 40 | tr[i].style.display = 'none'; 41 | } 42 | } 43 | } 44 | } 45 | 46 | /*function setValue(id, value, onChange) { 47 | const $value = $('#' + id + '.value'); 48 | if ($value.attr('type') === 'checkbox') { 49 | $value.prop('checked', value).change(function() { 50 | onChange(); 51 | }); 52 | } else { 53 | const val = $value.data('crypt') && value ? decrypt(secret, value) : value; 54 | $value.val(val).change(function() { 55 | onChange(); 56 | }).keyup(function() { 57 | // Check that only numbers entered 58 | if ($(this).hasClass('number')) { 59 | const val = $(this).val(); 60 | if (val) { 61 | let newVal = ''; 62 | for (let i = 0; i < val.length; i++) { 63 | if (val[i] >= '0' && val[i] <= '9') { 64 | newVal += val[i]; 65 | } 66 | } 67 | if (val != newVal) $(this).val(newVal); 68 | } 69 | } 70 | onChange(); 71 | }); 72 | } 73 | }*/ 74 | 75 | async function getHistoryInstances(settings){ 76 | let histArr =[]; 77 | histArr = await getHistoryAdapter('history', histArr); 78 | histArr = await getHistoryAdapter('sql', histArr); 79 | histArr = await getHistoryAdapter('influxdb', histArr); 80 | const selectElement = document.getElementById('history'); 81 | const cnfHistory = settings.history; 82 | let option = document.createElement('option'); 83 | option.text = 'disabled'; 84 | option.value = ''; 85 | selectElement.options[0] = option; 86 | for (let i = 0; i < histArr.length; i++) { 87 | option = document.createElement('option'); 88 | const str = histArr[i].name.replace('system.adapter.', ''); 89 | option.text = str; 90 | option.value = str; 91 | selectElement.options[i+1] = option; 92 | if (cnfHistory == str){ 93 | selectElement.selectedIndex = i+1; 94 | } 95 | } 96 | $('select').select(); 97 | } 98 | 99 | function getHistoryAdapter (adapter, hist) { 100 | return new Promise((resolve, reject) => { 101 | getAdapterInstances(adapter, function (arr) { 102 | for (let i = 0; i < arr.length; i++) { 103 | hist.push({'name' : arr[i]._id}); 104 | } 105 | resolve(hist); 106 | }); 107 | }); 108 | } 109 | 110 | 111 | // Field is valid? 112 | function chkValidity() { 113 | let valid = true; 114 | 115 | $('.value').each(function() { 116 | const $key = $(this); 117 | const element = document.getElementById($key.attr('id')); 118 | 119 | if ($key.attr('type') !== 'checkbox' && !element.checkValidity()) { 120 | valid = false; 121 | } 122 | }); 123 | return valid; 124 | } 125 | 126 | function dlgError(text){ 127 | const content = 128 | '' + 131 | '' + 134 | ''; 137 | $('#dlgDevices').html(content); 138 | $('#dlgDevices').modal(); 139 | $('#dlgWL').html(content); 140 | $('#dlgWL').modal(); 141 | } 142 | 143 | function dlgDevicesOld(arr, title, id){ 144 | let tableBodyDevices = ''; 145 | arr.forEach(function (element) { 146 | const chkVal2 = ''; 147 | 148 | //onMessage -> allDevices name, mac, ip -> see main.js 149 | tableBodyDevices += 150 | '' + 154 | '' + 155 | '' + element.name + '' + 156 | '' + element.mac + '' + 157 | '' + element.ip + '' + 158 | ''; 159 | }); 160 | 161 | const dialogDevices = 162 | '' + 183 | '' + 192 | ''; 196 | 197 | $(id).html(dialogDevices); 198 | $(id).modal({dismissible: false}); 199 | } 200 | 201 | function dlgDevices(arr, title, id) { 202 | const tableRows = arr.map(function (element) { 203 | const chkVal2 = ''; 204 | 205 | // onMessage -> allDevices name, mac, ip -> see main.js 206 | return ` 207 | 211 | 212 | ${element.name} 213 | ${element.mac} 214 | ${element.ip} 215 | `; 216 | }); 217 | 218 | const tableBodyDevices = tableRows.join(''); 219 | 220 | const dialogDevices = ` 221 | 242 | 251 | `; 255 | 256 | $(id).html(dialogDevices); 257 | $(id).modal({dismissible: false}); 258 | } 259 | 260 | 261 | 262 | function dlgWl(arr, title, id){ 263 | let tableBodyWl = ''; 264 | arr.forEach(function (element) { 265 | let chkVal = ''; 266 | for(let i=0; i < whitelist.length; i++){ 267 | if (element.mac == whitelist[i].white_macaddress){ 268 | chkVal = 'checked'; 269 | break; 270 | } 271 | } 272 | 273 | //onMessage -> allDevices name, mac, ip -> see main.js 274 | tableBodyWl += 275 | '' + 278 | '' + 279 | '' + element.name + '' + 280 | '' + element.mac + '' + 281 | ''; 282 | }); 283 | 284 | const dialogWl= 285 | '' + 305 | '' + 314 | ''; 318 | 319 | $(id).html(dialogWl); 320 | $(id).modal({dismissible: false,}); 321 | } 322 | 323 | // This will be called by the admin adapter when the settings page loads 324 | async function load(settings, onChange) { 325 | try { 326 | if (!settings) return; 327 | //$('.hideOnLoad').hide(); 328 | familymembers = settings.familymembers || []; 329 | whitelist = settings.whitelist || []; 330 | values2table('values', familymembers, onChange, tableOnReady); 331 | values2table('whitevalues', whitelist, onChange); 332 | 333 | getHistoryInstances(settings); //fill select options 334 | 335 | // Secret für Passwortverschlüsselung abfragen 336 | //const obj = await emitAsync('getObject', 'system.config'); 337 | //if (obj){ 338 | //secret = (obj.native ? obj.native.secret : '') || 'SdoeQ85NTrg1B0FtEyzf'; 339 | $('.value').each(function () { 340 | const $key = $(this); 341 | const id = $key.attr('id'); 342 | 343 | if ($key.attr('type') === 'checkbox') { 344 | // do not call onChange direct, because onChange could expect some arguments 345 | $key.prop('checked', settings[id]).on('change', function(){ 346 | if (chkValidity()) { 347 | onChange(); 348 | } else { 349 | onChange(false); 350 | } 351 | }); //=> onChange()) 352 | } else { 353 | let val; 354 | if ($key.data('crypt') =='1'){ 355 | //val = decrypt(secret, settings[id]) ; 356 | val = settings[id]; 357 | } else{ 358 | val = settings[id]; 359 | } 360 | 361 | $key.val(val).on('change', function(){ //=> onChange()) 362 | if (chkValidity()) { 363 | onChange(); 364 | } else { 365 | onChange(false); 366 | } 367 | }).on('keyup', function(){ //=> onChange()) 368 | if (chkValidity()) { 369 | onChange(); 370 | } else { 371 | onChange(false); 372 | } 373 | }); 374 | } 375 | }); 376 | //} 377 | $('select').select(); 378 | if (M) M.updateTextFields(); 379 | onChange(false); 380 | 381 | // if adapter is alive 382 | const state = await emitAsync('getState', 'system.adapter.' + adapter + '.' + instance + '.alive'); 383 | active = (state && state.val); 384 | if (!active) { 385 | dlgError('You have to start your ioBroker.' + adapter + ' adapter before you can use this function!'); 386 | }else{ 387 | const g_onChange = onChange; 388 | const result = await sendToAsync('getDevices', { onlyActive: true, reread: true }); 389 | if (result != null) { 390 | arr = JSON.parse(result); 391 | if(arr || arr.result == true){ 392 | dlgDevices(arr, 'Add family member', '#dlgDevices'); 393 | dlgWl(arr, 'Add a device', '#dlgWL'); 394 | }else{ 395 | dlgError('Can not read devices! Result = false'); 396 | return false; 397 | } 398 | 399 | $('#save1').click(function () { 400 | // Loop through all checkboxes, and add all devices with selected checkboxes 401 | whitelist = []; //clear whitelist 402 | $('#tabWL input[type=checkbox]:checked').each(function () { 403 | const row = $(this).closest('tr')[0]; 404 | const mac = $(row).data('white_macaddress'); 405 | const device = $(row).data('white_device'); 406 | if (device != null){ 407 | whitelist.push({white_macaddress: mac, white_device: device}); 408 | values2table('whitevalues', whitelist, g_onChange); 409 | g_onChange(true); 410 | } 411 | }); 412 | }); 413 | 414 | $('#save').click(function () { 415 | // Loop through all checkboxes, and add all devices with selected checkboxes 416 | const devices = []; //clear whitelist 417 | //familymembers = settings.familymembers || []; 418 | familymembers = table2values('values') || []; 419 | 420 | $('#tabDevices input[type=checkbox]:checked').each(function () { 421 | const row = $(this).closest('tr')[0]; 422 | const ip = $(row).data('ip'); 423 | const mac = $(row).data('macaddress'); 424 | const device = $(row).data('familymember'); 425 | if (device != undefined){ 426 | let comment = ''; 427 | let enabled = true; 428 | let famname = device; 429 | let usefilter = false; 430 | let usage = 'MAC'; 431 | let group = ''; 432 | for (let i=0; i { 685 | sendTo(adapterNamespace, cmd, obj, (result) => { 686 | if (result.error) { 687 | //console.error('sendToAsync(): ' + result.error); 688 | reject(null); 689 | } else { 690 | resolve(result); 691 | } 692 | }); 693 | }); 694 | } 695 | 696 | function emitAsync(func, id) { 697 | return new Promise((resolve, reject) => { 698 | socket.emit(func, id, (err, stateObj) => { 699 | if (err) { 700 | console.error('emitAsync(): ' + err); 701 | reject(null); 702 | } else { 703 | resolve(stateObj); 704 | } 705 | }); 706 | }); 707 | } 708 | -------------------------------------------------------------------------------- /lib/fb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const xml2jsP = require('xml2js'); 4 | const https = require('https'); 5 | const axios = require('axios'); 6 | const crypto = require('crypto'); 7 | const qr = require('qr-image'); 8 | 9 | class exceptionFb extends Error { 10 | constructor(name, message) { 11 | super(message); 12 | this.name = name; 13 | this.toString = function () { 14 | return `${this.name}: ${this.message}`; 15 | }; 16 | } 17 | } 18 | 19 | class exceptionSoapCommand extends Error { 20 | constructor(name, message) { 21 | super(message); 22 | this.name = name; 23 | this.toString = function () { 24 | return `${this.name}: ${this.message}`; 25 | }; 26 | } 27 | } 28 | 29 | class exceptionFbSendWithRetry extends Error { 30 | constructor(message) { 31 | super(message); 32 | this.name = 'exceptionFbSendWithRetry'; 33 | this.toString = function () { 34 | return `${this.name}: ${this.message}`; 35 | }; 36 | } 37 | } 38 | 39 | /** 40 | * A class to use the fritzbox api 41 | * 42 | */ 43 | class Fb { 44 | /** 45 | * @param {{ host: any; uid: any; pwd: any; }} deviceInfo - configuration items 46 | * @param {any} ssl - configuration item 47 | * @param {any} adapter - adapter object 48 | */ 49 | constructor(deviceInfo, ssl, adapter) { 50 | this._urn = 'urn:dslforum-org:service:'; 51 | this._MAX_RETRY = 3; 52 | this.adapter = adapter; 53 | this.modelName = null; 54 | this.version = null; 55 | this._sslPort = null; 56 | this.host = deviceInfo.host; 57 | this.port = 49000; 58 | this.timeout = 6000; 59 | this._auth = { 60 | uid: deviceInfo.uid, 61 | pwd: deviceInfo.pwd, 62 | sn: null, 63 | auth: null, 64 | realm: 'F!Box SOAP-Auth', 65 | chCount: 0, 66 | }; 67 | this.agent = null; //new https.Agent({ defaultPort: 43213, keepAlive: true, rejectUnauthorized: false }); 68 | this.controller = null; 69 | this.source = null; 70 | try { 71 | this.controller = new AbortController(); 72 | 73 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 74 | } catch (error) { 75 | this.controller = null; 76 | this.source = axios.CancelToken.source(); 77 | } 78 | this.ssl = ssl; 79 | this.serviceUrl = `http://${this.host}:${this.port}/tr64desc.xml`; 80 | this.services = null; 81 | this.currentRetry = 0; 82 | this.deviceList = null; 83 | this.meshList = null; 84 | this.accessRights = { C: 'none', P: 'none', D: 'none', N: 'none', H: 'none', A: 'none', '-': 'readwrite' }; 85 | this.accessType = null; //DSL, Ethernet, X_AVM-DE_Fiber, X_AVMDE_UMTS, X_AVM-DE_Cable, X_AVM-DE_LTE 86 | this.ssid = null; 87 | this.beaconType = null; 88 | this.connection = null; //1.WANPPPConnection.1, 89 | //http://fritz.box:49000/tr64desc.xml 90 | this.suportedServices = []; 91 | this.GETPATH = false; 92 | this.GETMESHPATH = false; 93 | this.GETBYMAC = false; 94 | this.GETBYIP = false; 95 | this.GETPORT = false; 96 | this.GETEXTIP = false; 97 | this.GETEXTIPBYIP = false; 98 | this.GETACCESSTYPE = false; 99 | this.SETENABLE = false; 100 | this.WLAN3INFO = false; 101 | this.WLAN3GETSECKEY = false; 102 | this.WLAN4INFO = false; 103 | this.WLAN4GETSECKEY = false; 104 | this.DEVINFO = false; 105 | this.GETWANACCESSBYIP = false; 106 | this.DISALLOWWANACCESSBYIP = false; 107 | this.REBOOT = false; 108 | this.RECONNECT = false; 109 | this.USERRIGHTS = false; 110 | this.CONNECTION = false; 111 | } 112 | 113 | /** 114 | * init function for Fb class 115 | * 116 | * @param deviceInfo - configuration parameters for fritzbox authentification 117 | * @param ssl - ssl configuration parameter 118 | * @param adapter - adapter object 119 | */ 120 | static init(deviceInfo, ssl, adapter) { 121 | return (async function () { 122 | try { 123 | const x = new Fb(deviceInfo, ssl, adapter); 124 | await x._getSSLPort(); 125 | if (x._sslPort && x.ssl) { 126 | x.agent = new https.Agent({ 127 | rejectUnauthorized: false, 128 | }); 129 | } 130 | await x._getAccessRights(); 131 | const res = await x._getServices(); 132 | if (res === true) { 133 | await x._checkServices(); 134 | } 135 | if (x.GETACCESSTYPE && x.GETACCESSTYPE === true) { 136 | await x._getWANAccessType(); 137 | } 138 | if (x.CONNECTION && x.CONNECTION === true) { 139 | await x._getDefaultConnection(); 140 | } 141 | return x; 142 | } catch (error) { 143 | throw new exceptionSoapCommand('Fb init', error); 144 | } 145 | })(); 146 | } 147 | 148 | /** 149 | * fritzbox action GetDefaultConnectionService 150 | */ 151 | async _getDefaultConnection() { 152 | try { 153 | const soapResult = await this.soapAction( 154 | '/upnp/control/layer3forwarding', 155 | 'urn:dslforum-org:service:' + 'Layer3Forwarding:1', 156 | 'GetDefaultConnectionService', 157 | null, 158 | ); 159 | if (soapResult) { 160 | this.connection = soapResult['NewDefaultConnectionService']; 161 | return; 162 | } 163 | throw new exceptionSoapCommand( 164 | 'getDefaultConnection', 165 | `Cannot get default connection from Fritzbox! ${JSON.stringify(soapResult)}`, 166 | ); 167 | } catch (error) { 168 | throw new exceptionFb('getDefaultConnection', error); 169 | } 170 | } 171 | 172 | /** 173 | * fritzbox action X_AVM-DE_GetCurrentUser to get access rights 174 | */ 175 | async _getAccessRights() { 176 | try { 177 | const soapResult = await this.soapAction( 178 | '/upnp/control/lanconfigsecurity', 179 | 'urn:dslforum-org:service:' + 'LANConfigSecurity:1', 180 | 'X_AVM-DE_GetCurrentUser', 181 | null, 182 | ); 183 | if (soapResult) { 184 | const result = await xml2jsP.parseStringPromise(soapResult['NewX_AVM-DE_CurrentUserRights'], { 185 | explicitArray: false, 186 | }); 187 | if (result && result.rights && result.rights.path) { 188 | for (let i = 0; i < result.rights.path.length; i++) { 189 | switch (result.rights.path[i]) { 190 | case 'BoxAdmin': 191 | this.accessRights.C = result.rights.access[i]; 192 | break; 193 | case 'Phone': 194 | this.accessRights.P = result.rights.access[i]; 195 | break; 196 | case 'Dial': 197 | this.accessRights.D = result.rights.access[i]; 198 | break; 199 | case 'NAS': 200 | this.accessRights.N = result.rights.access[i]; 201 | break; 202 | case 'HomeAuto': 203 | this.accessRights.H = result.rights.access[i]; 204 | break; 205 | case 'App': 206 | this.accessRights.A = result.rights.access[i]; 207 | break; 208 | default: 209 | this.accessRights.none = '-'; 210 | } 211 | } 212 | return ''; 213 | } 214 | throw new Error('Cannot parse soap result! '); 215 | } else { 216 | throw new exceptionSoapCommand('getAccessRights', 'Cannot get access rights from Fritzbox!'); 217 | } 218 | } catch (error) { 219 | if (error.message.includes('authentification failure')) { 220 | throw new exceptionSoapCommand('_getAccessRights', 'please check the user or password!'); //this.adapter.log.warn('please check the user or password!'); 221 | } else { 222 | throw new exceptionSoapCommand('_getAccessRights', error); 223 | } 224 | } 225 | } 226 | 227 | /** 228 | * fritzbox action to get supported services 229 | */ 230 | async _getServices() { 231 | try { 232 | const para = 233 | this.controller === null 234 | ? { 235 | url: this.serviceUrl, 236 | method: 'get', 237 | timeout: this.timeout, 238 | responseType: 'json', 239 | responseEncoding: 'utf8', 240 | cancelToken: this.source.token, 241 | } 242 | : { 243 | url: this.serviceUrl, 244 | method: 'get', 245 | timeout: this.timeout, 246 | responseType: 'json', 247 | responseEncoding: 'utf8', 248 | signal: this.controller.signal, 249 | }; 250 | const response = await axios(para); 251 | if (response && response.status == 200) { 252 | this.services = await xml2jsP.parseStringPromise(response.data, { explicitArray: false }); 253 | this.modelName = this.services.root.device.modelName; 254 | if (this.services.root.systemVersion != null) { 255 | this.version = this.services.root.systemVersion.Display; 256 | } else { 257 | this.version = 'not defined'; 258 | } 259 | return true; 260 | } 261 | throw Error(`Can not read services! Status: ${JSON.stringify(response)}`); 262 | } catch (error) { 263 | this.services = null; 264 | throw new exceptionFb('getServices', error); 265 | } 266 | } 267 | 268 | /** 269 | * fritzbox action to get services 270 | * 271 | * @param device - device 272 | * @param serviceId - service id 273 | */ 274 | _getService(device, serviceId) { 275 | const device1 = device; 276 | const dlength = device1.length; 277 | if (device1 && device1.length == undefined) { 278 | const length = device1['serviceList']['service'].length; 279 | for (let s = 0; s < length; s++) { 280 | const service = device1['serviceList']['service'][s]; 281 | if (service.serviceId.includes(serviceId)) { 282 | return service; 283 | } 284 | } 285 | if (device1.deviceList && device1.deviceList.device) { 286 | return this._getService(device1.deviceList.device, serviceId); 287 | } 288 | } else { 289 | for (let d = 0; d < dlength; d++) { 290 | const length = device1[d]['serviceList']['service'].length; 291 | const dev = device1[d]['serviceList']['service']; 292 | for (let s = 0; s < length; s++) { 293 | const service = dev[s]; 294 | if (service.serviceId.includes(serviceId)) { 295 | return service; 296 | } 297 | } 298 | if (device1[d].deviceList && device1[d].deviceList.device) { 299 | return this._getService(device1[d]['deviceList']['device'], serviceId); 300 | } 301 | } 302 | } 303 | return null; 304 | } 305 | 306 | /** 307 | * check service if it is supported by fritzbox 308 | */ 309 | async _checkServices() { 310 | try { 311 | this.GETPATH = await this._chkService('X_AVM-DE_GetHostListPath', 'Hosts1', 'X_AVM-DE_GetHostListPath', [ 312 | 'C', 313 | 'A', 314 | 'P', 315 | ]); 316 | this.GETMESHPATH = await this._chkService( 317 | 'X_AVM-DE_GetMeshListPath', 318 | 'Hosts1', 319 | 'X_AVM-DE_GetMeshListPath', 320 | ['C', 'A', 'P'], 321 | ); 322 | this.GETBYMAC = await this._chkService('GetSpecificHostEntry', 'Hosts1', 'GetSpecificHostEntry', ['-']); 323 | this.GETBYIP = await this._chkService( 324 | 'X_AVM-DE_GetSpecificHostEntryByIP', 325 | 'Hosts1', 326 | 'X_AVM-DE_GetSpecificHostEntryByIP', 327 | ['C', 'A', 'P'], 328 | ); 329 | this.GETPORT = await this._chkService('GetSecurityPort', 'DeviceInfo1', 'GetSecurityPort', ['-']); 330 | this.GETACCESSTYPE = await this._chkService( 331 | 'GetCommonLinkProperties', 332 | 'WANCommonInterfaceConfig1', 333 | 'GetCommonLinkProperties', 334 | ['C'], 335 | ); 336 | this.GETEXTIP = await this._chkService('GetInfo', 'WANPPPConnection1', 'GetInfo', ['C']); 337 | this.GETEXTIPBYIP = await this._chkService('GetInfo', 'WANIPConnection1', 'GetInfo', ['C']); 338 | this.SETENABLE = await this._chkService('SetEnable', 'WLANConfiguration3', 'SetEnable', ['A']); 339 | this.WLAN3INFO = await this._chkService('GetInfo', 'WLANConfiguration3', 'WLANConfiguration3-GetInfo', [ 340 | 'C', 341 | 'A', 342 | 'P', 343 | ]); 344 | this.WLAN3GETSECKEY = await this._chkService( 345 | 'GetSecurityKeys', 346 | 'WLANConfiguration3', 347 | 'WLANConfiguration3-GetSecurityKeys', 348 | ['A'], 349 | ); 350 | this.WLAN4INFO = await this._chkService('GetInfo', 'WLANConfiguration4', 'WLANConfiguration4-GetInfo', [ 351 | 'C', 352 | 'A', 353 | 'P', 354 | ]); 355 | this.WLAN4GETSECKEY = await this._chkService( 356 | 'GetSecurityKeys', 357 | 'WLANConfiguration4', 358 | 'WLANConfiguration4-GetSecurityKeys', 359 | ['A'], 360 | ); 361 | this.DEVINFO = await this._chkService('GetInfo', 'DeviceInfo1', 'DeviceInfo1-GetInfo', ['C']); 362 | this.DISALLOWWANACCESSBYIP = await this._chkService( 363 | 'DisallowWANAccessByIP', 364 | 'X_AVM-DE_HostFilter', 365 | 'DisallowWANAccessByIP', 366 | ['C', 'A'], 367 | ); 368 | this.GETWANACCESSBYIP = await this._chkService( 369 | 'GetWANAccessByIP', 370 | 'X_AVM-DE_HostFilter', 371 | 'GetWANAccessByIP', 372 | ['C', 'A'], 373 | ); 374 | this.REBOOT = await this._chkService('Reboot', 'DeviceConfig1', 'Reboot', ['C']); 375 | this.RECONNECT = await this._chkService('ForceTermination', 'WANPPPConnection1', 'ForceTermination', ['C']); 376 | this.USERRIGHTS = await this._chkService( 377 | 'X_AVM-DE_GetCurrentUser', 378 | 'LANConfigSecurity1', 379 | 'X_AVM-DE_GetCurrentUser', 380 | ['C', 'A', 'P', 'N', 'H'], 381 | ); 382 | this.CONNECTION = await this._chkService( 383 | 'GetDefaultConnectionService', 384 | 'Layer3Forwarding1', 385 | 'GetDefaultConnectionService', 386 | ['C'], 387 | ); 388 | } catch (error) { 389 | throw new exceptionFb('checkServices', error); 390 | } 391 | } 392 | 393 | /** 394 | * 395 | * @param action - action name 396 | * @param serviceId - service id 397 | * @param dp - data point 398 | * @param neededAccessRights - needed access rights for correct function 399 | */ 400 | async _chkService(action, serviceId, dp, neededAccessRights) { 401 | try { 402 | if (this.services === null) { 403 | throw Error('can not get services!'); 404 | } 405 | const service = this._getService(this.services['root']['device'], serviceId); 406 | if (service) { 407 | const nurl = `http://${this.host}:${this.port}${service.SCPDURL}`; 408 | const para = 409 | this.controller === null 410 | ? { 411 | url: nurl, 412 | method: 'get', 413 | timeout: this.timeout, 414 | responseType: 'json', 415 | responseEncoding: 'utf8', 416 | cancelToken: this.source.token, 417 | } 418 | : { 419 | url: nurl, 420 | method: 'get', 421 | timeout: this.timeout, 422 | responseType: 'json', 423 | responseEncoding: 'utf8', 424 | signal: this.controller.signal, 425 | }; 426 | const response = await axios(para); 427 | if (response && response.status == 200) { 428 | const found = JSON.stringify(response.data).search(action); 429 | if (found == -1) { 430 | this.suportedServices.push({ id: dp, name: `${serviceId}-${action}`, enabled: false }); 431 | return false; 432 | } 433 | let supported = false; 434 | for (let i = 0; i < neededAccessRights.length; i++) { 435 | const right = this.accessRights[neededAccessRights[i]]; 436 | if (right != 'none') { 437 | supported = true; 438 | break; 439 | } 440 | } 441 | this.suportedServices.push({ id: dp, name: `${serviceId}-${action}`, enabled: supported }); 442 | return supported; 443 | } 444 | this.adapter.log.info( 445 | `service ${serviceId}-${action} is not supported! Can not find action! Feature is deactivated! `, 446 | ); 447 | return false; 448 | } 449 | this.adapter.log.info( 450 | `service ${serviceId}-${action} is not supported! Can not find service file! Feature is deactivated`, 451 | ); 452 | return false; 453 | } catch (error) { 454 | throw new exceptionFb('checkService', `${error.name} ${error.message}`); 455 | } 456 | } 457 | 458 | /** 459 | * read ssl port from fritzbox 460 | */ 461 | async _getSSLPort() { 462 | try { 463 | const soapResult = await this.soapAction( 464 | '/upnp/control/deviceinfo', 465 | 'urn:dslforum-org:service:' + 'DeviceInfo:1', 466 | 'GetSecurityPort', 467 | null, 468 | ); 469 | if (soapResult) { 470 | const sslPort = parseInt(soapResult['NewSecurityPort']); 471 | if (typeof sslPort === 'number' && isFinite(sslPort)) { 472 | this._sslPort = sslPort; 473 | return sslPort; 474 | } 475 | throw new Error(`Cannot get ssl port! Wrong type. ${JSON.stringify(soapResult)}`); 476 | } else { 477 | throw new exceptionSoapCommand( 478 | 'GetSecurityPort', 479 | `Cannot get ssl port from Fritzbox! ${JSON.stringify(soapResult)}`, 480 | ); 481 | } 482 | } catch (error) { 483 | this._sslPort = null; 484 | if (error.message.includes('timeout')) { 485 | this.adapter.log.warn( 486 | 'The user could have been blocked from the fritzbox due to authentification problems! Please stop the adapter and reboot the fritzbox. Then start and try again.', 487 | ); 488 | throw new exceptionFb('getSSLPort', error); 489 | } else { 490 | throw new exceptionFb('getSSLPort', error); 491 | } 492 | } 493 | } 494 | 495 | /** 496 | * 497 | * @param url - soap url 498 | * @param timeout - timeout 499 | * @param start - retry counter 500 | * @param message - message 501 | */ 502 | async _sendWithRetry(url, timeout, start, message) { 503 | this.currentRetry = start; 504 | 505 | try { 506 | const para = 507 | this.controller === null 508 | ? { 509 | url: url, 510 | method: 'get', 511 | timeout: this.timeout, 512 | responseType: 'text', 513 | responseEncoding: 'utf8', 514 | httpsAgent: this.agent, 515 | cancelToken: this.source.token, 516 | } 517 | : { 518 | url: url, 519 | method: 'get', 520 | timeout: this.timeout, 521 | responseType: 'text', 522 | responseEncoding: 'utf8', 523 | httpsAgent: this.agent, 524 | signal: this.controller.signal, 525 | }; 526 | const response = await axios(para); 527 | return response; 528 | } catch (error) { 529 | if (error.message && error.message == 'Request canceled!') { 530 | return false; 531 | } 532 | if (this.currentRetry < this._MAX_RETRY) { 533 | this.currentRetry++; 534 | return await this._sendWithRetry(url, timeout, this.currentRetry, message); 535 | } 536 | this.currentRetry = 0; 537 | throw new exceptionFbSendWithRetry(error); 538 | } 539 | } 540 | 541 | /** 542 | * read fritzbox device list with action X_AVM-DE_GetHostListPath 543 | */ 544 | async getDeviceList() { 545 | try { 546 | const soapResult = await this.soapAction( 547 | '/upnp/control/hosts', 548 | 'urn:dslforum-org:service:' + 'Hosts:1', 549 | 'X_AVM-DE_GetHostListPath', 550 | null, 551 | ); 552 | if (soapResult) { 553 | let url = null; 554 | if (this._sslPort && this.ssl) { 555 | url = `https://${this.host}:${this._sslPort}${soapResult['NewX_AVM-DE_HostListPath']}`; 556 | } else { 557 | url = `http://${this.host}:${this.port}${soapResult['NewX_AVM-DE_HostListPath']}`; 558 | } 559 | const para = 560 | this.controller === null 561 | ? { 562 | url: url, 563 | method: 'get', 564 | timeout: this.timeout + 4000, 565 | responseType: 'text', 566 | responseEncoding: 'utf8', 567 | httpsAgent: this.agent, 568 | cancelToken: this.source.token, 569 | } 570 | : { 571 | url: url, 572 | method: 'get', 573 | timeout: this.timeout + 4000, 574 | responseType: 'text', 575 | responseEncoding: 'utf8', 576 | httpsAgent: this.agent, 577 | signal: this.controller.signal, 578 | }; 579 | const response = await axios(para); 580 | if (response && response.status == 200 && response.data) { 581 | let deviceList = await xml2jsP.parseStringPromise(response.data, { explicitArray: false }); 582 | //this.adapter.log.warn('devicelist: ' + JSON.stringify(deviceList)); 583 | if (deviceList) { 584 | this.deviceList = deviceList['List']['Item']; 585 | deviceList = null; 586 | return true; 587 | } 588 | throw new Error(`Cannot parse response.data ${JSON.stringify(response.data)}`); 589 | } else { 590 | if (response === false) { 591 | return false; 592 | } 593 | throw new exceptionFb('exceptionHostlist', 'Cannot get hostlist'); 594 | } 595 | } else { 596 | throw new exceptionSoapCommand('X_AVM-DE_GetHostListPath', 'Cannot get hostlist path from Fritzbox!'); 597 | } 598 | } catch (error) { 599 | if (error.name == 'CanceledError') { 600 | throw new exceptionSoapCommand('getDeviceList', error.message); 601 | } else { 602 | throw new exceptionSoapCommand('getDeviceList', `${error.name} ${error.message}`); 603 | } 604 | } 605 | } 606 | 607 | /** 608 | * read fritzbox mesh list with action X_AVM-DE_GetMeshListPath 609 | */ 610 | async getMeshList() { 611 | try { 612 | const soapResult = await this.soapAction( 613 | '/upnp/control/hosts', 614 | 'urn:dslforum-org:service:' + 'Hosts:1', 615 | 'X_AVM-DE_GetMeshListPath', 616 | null, 617 | ); 618 | if (soapResult) { 619 | let url = null; 620 | if (this._sslPort && this.ssl) { 621 | url = `https://${this.host}:${this._sslPort}${soapResult['NewX_AVM-DE_MeshListPath']}`; 622 | } else { 623 | url = `http://${this.host}:${this.port}${soapResult['NewX_AVM-DE_MeshListPath']}`; 624 | } 625 | const para = 626 | this.controller === null 627 | ? { 628 | url: url, 629 | method: 'get', 630 | timeout: this.timeout + 8000, 631 | responseType: 'json', 632 | responseEncoding: 'utf8', 633 | httpsAgent: this.agent, 634 | cancelToken: this.source.token, 635 | } 636 | : { 637 | url: url, 638 | method: 'get', 639 | timeout: this.timeout + 8000, 640 | responseType: 'json', 641 | responseEncoding: 'utf8', 642 | httpsAgent: this.agent, 643 | signal: this.controller.signal, 644 | }; 645 | const response = await axios(para); 646 | if (response != null && response.status == 200 && response.data) { 647 | this.meshList = response.data['nodes']; 648 | return true; 649 | } 650 | if (response === false) { 651 | return false; 652 | } 653 | throw new Error('Cannot get mesh list'); 654 | } else { 655 | throw new exceptionSoapCommand('X_AVM-DE_GetMeshListPath', 'Cannot get mesh list path'); 656 | } 657 | } catch (error) { 658 | if (error.name == 'CanceledError') { 659 | throw new exceptionSoapCommand('getMeshList', error.message); 660 | } else { 661 | throw new exceptionSoapCommand('getMeshList', `${error.name} ${error.message}`); 662 | } 663 | } 664 | } 665 | 666 | /** 667 | * read fritzbox wan access type with action GetCommonLinkProperties 668 | */ 669 | async _getWANAccessType() { 670 | try { 671 | const soapResult = await this.soapAction( 672 | '/upnp/control/wancommonifconfig1', 673 | 'urn:dslforum-org:service:' + 'WANCommonInterfaceConfig:1', 674 | 'GetCommonLinkProperties', 675 | null, 676 | ); 677 | if (soapResult) { 678 | this.accessType = soapResult['NewWANAccessType']; 679 | } else { 680 | throw new exceptionSoapCommand('GetCommonLinkProperties', 'Cannot get wan access type from Fritzbox!'); 681 | } 682 | } catch (error) { 683 | throw new exceptionFb('getWANAccessType', `${error.name} ${error.message}`); 684 | } 685 | } 686 | 687 | //Get external IP address 688 | /** 689 | * read external ip from fritzbox 690 | */ 691 | async getExtIp() { 692 | try { 693 | let soapResult = null; 694 | if (this.GETEXTIP && this.GETEXTIP == true) { 695 | if (this.connection == '1.WANPPPConnection.1') { 696 | soapResult = await this.soapAction( 697 | '/upnp/control/wanpppconn1', 698 | 'urn:dslforum-org:service:' + 'WANPPPConnection:1', 699 | 'GetInfo', 700 | null, 701 | ); 702 | } else { 703 | soapResult = await this.soapAction( 704 | '/upnp/control/wanipconnection1', 705 | 'urn:dslforum-org:service:' + 'WANIPConnection:1', 706 | 'GetInfo', 707 | null, 708 | ); 709 | } 710 | if (soapResult) { 711 | return soapResult['NewExternalIPAddress']; 712 | } 713 | const mesg = soapResult == null ? '' : soapResult; 714 | throw new exceptionSoapCommand( 715 | 'GetInfo WANConnection', 716 | `Cannot get external ip address ${JSON.stringify(mesg)}`, 717 | ); 718 | } 719 | } catch (error) { 720 | this.adapter.log.warn(`getExtIp: ${error.name} ${error.message}`); 721 | return ''; 722 | } 723 | } 724 | 725 | //Get external IPv6 address 726 | /** 727 | * read external ipv6 from fritzbox 728 | */ 729 | async getExtIpv6() { 730 | try { 731 | let soapResult = null; 732 | if (this.GETEXTIP && this.GETEXTIP == true) { 733 | if (this.connection == '1.WANPPPConnection.1') { 734 | soapResult = await this.soapAction( 735 | '/igdupnp/control/WANIPConn1', 736 | 'urn:schemas-upnp-org:service:' + 'WANIPConnection:1', 737 | 'X_AVM_DE_GetExternalIPv6Address', 738 | null, 739 | ); 740 | } else { 741 | soapResult = await this.soapAction( 742 | '/igdupnp/control/WANIPConn1', 743 | 'urn:schemas-upnp-org:service:' + 'WANIPConnection:1', 744 | 'X_AVM_DE_GetExternalIPv6Address', 745 | null, 746 | ); 747 | } 748 | if (soapResult) { 749 | return soapResult['NewExternalIPv6Address']; 750 | } 751 | const mesg = soapResult == null ? '' : soapResult; 752 | throw new exceptionSoapCommand( 753 | 'X_AVM_DE_GetExternalIPv6Address', 754 | `Cannot get external ipv6 address ${JSON.stringify(mesg)}`, 755 | ); 756 | } 757 | } catch (error) { 758 | this.adapter.log.warn(`getExtIpv6: ${error.name} ${error.message}`); 759 | return ''; 760 | } 761 | } 762 | //Get external IPv6 address 763 | /** 764 | * read external ipv6 from fritzbox 765 | */ 766 | async getExtIpv6Prefix() { 767 | try { 768 | let soapResult = null; 769 | if (this.GETEXTIP && this.GETEXTIP == true) { 770 | if (this.connection == '1.WANPPPConnection.1') { 771 | soapResult = await this.soapAction( 772 | '/igdupnp/control/WANIPConn1', 773 | 'urn:schemas-upnp-org:service:' + 'WANIPConnection:1', 774 | 'X_AVM_DE_GetIPv6Prefix', 775 | null, 776 | ); 777 | } else { 778 | soapResult = await this.soapAction( 779 | '/igdupnp/control/WANIPConn1', 780 | 'urn:schemas-upnp-org:service:' + 'WANIPConnection:1', 781 | 'X_AVM_DE_GetIPv6Prefix', 782 | null, 783 | ); 784 | } 785 | if (soapResult) { 786 | return soapResult['NewIPv6Prefix']; 787 | } 788 | const mesg = soapResult == null ? '' : soapResult; 789 | throw new exceptionSoapCommand( 790 | 'X_AVM_DE_GetIPv6Prefix', 791 | `Cannot get external ipv6 prefix ${JSON.stringify(mesg)}`, 792 | ); 793 | } 794 | } catch (error) { 795 | this.adapter.log.warn(`getExtIpv6Prefix: ${error.name} ${error.message}`); 796 | return ''; 797 | } 798 | } 799 | 800 | //Get status of guest wlan 801 | /** 802 | * read guest wlan info 803 | */ 804 | async getGuestWlan() { 805 | try { 806 | let serviceType = ''; 807 | let controlURL = ''; 808 | if (serviceType == '' && this.WLAN4INFO && this.WLAN4INFO == true) { 809 | serviceType = 'urn:dslforum-org:service:WLANConfiguration:4'; 810 | controlURL = '/upnp/control/wlanconfig4'; 811 | } 812 | if (serviceType == '' && this.WLAN3INFO && this.WLAN3INFO == true) { 813 | serviceType = 'urn:dslforum-org:service:WLANConfiguration:3'; 814 | controlURL = '/upnp/control/wlanconfig3'; 815 | } 816 | const soapResult = await this.soapAction(controlURL, serviceType, 'GetInfo', null); 817 | if (soapResult) { 818 | const wlanStatus = soapResult['NewEnable'] == 1 ? true : false; 819 | this.ssid = soapResult['NewSSID']; 820 | this.beaconType = soapResult['NewBeaconType']; 821 | return wlanStatus; 822 | } 823 | throw new exceptionSoapCommand( 824 | 'GetInfo WLANConfiguration', 825 | `Cannot get status of guest wlan ${JSON.stringify(soapResult)}`, 826 | ); 827 | } catch (error) { 828 | //throw new exceptionFb('getGuestWlan', error.name + ' ' + error.message); 829 | this.adapter.log.warn(`getGuestWlan ${error.name} ${error.message}`); 830 | return; 831 | } 832 | } 833 | 834 | //Get status of guest wlan 835 | /** 836 | * convert guest wlan info into qr-code 837 | */ 838 | async getGuestQR() { 839 | try { 840 | let serviceType = ''; 841 | let controlURL = ''; 842 | if (serviceType == '' && this.WLAN4GETSECKEY === true) { 843 | serviceType = 'urn:dslforum-org:service:WLANConfiguration:4'; 844 | controlURL = '/upnp/control/wlanconfig4'; 845 | } 846 | if (serviceType == '' && this.WLAN3GETSECKEY === true) { 847 | serviceType = 'urn:dslforum-org:service:WLANConfiguration:3'; 848 | controlURL = '/upnp/control/wlanconfig3'; 849 | } 850 | const soapResult = await this.soapAction(controlURL, serviceType, 'GetSecurityKeys', null); 851 | if (soapResult) { 852 | let password = soapResult['NewKeyPassphrase']; 853 | password = password.replace(/[\\";:,]/g, '\\$&'); 854 | const SSID = this.ssid.replace(/[\\";:,]/g, '\\$&'); 855 | let WPA = null; //this.beaconType == '11i' ? 'WPA2' : this.beaconType; 856 | switch (this.beaconType) { 857 | case 'None': 858 | WPA = 'nopass'; 859 | break; 860 | case 'Basic': 861 | WPA = 'WEP'; 862 | break; 863 | case 'WPA': 864 | WPA = 'WPA'; 865 | break; 866 | case '11i': 867 | WPA = 'WPA'; 868 | break; 869 | case 'WPAand11i': 870 | WPA = 'WPA'; 871 | break; 872 | case 'WPA3': 873 | WPA = 'WPA'; 874 | break; 875 | case '11iandWPA3': 876 | WPA = 'WPA'; 877 | break; 878 | case 'OWE': 879 | WPA = 'WPA'; 880 | break; 881 | case 'OWETrans': 882 | WPA = 'WPA'; 883 | break; 884 | default: 885 | WPA = 'nopass'; 886 | break; 887 | } 888 | const qrcode = `WIFI:T:${WPA};P:${password};S:${SSID};;`; 889 | let svg = qr.imageSync(qrcode, { type: 'svg' }); 890 | svg = svg.replace('` + 998 | `` + 1000 | `${this._auth.sn}` + 1001 | `${this._auth.auth}` + 1002 | `${this._auth.uid}` + 1003 | `${this._auth.realm}` + 1004 | `` + 1005 | ``; 1006 | } else { 1007 | // First Auth 1008 | head = 1009 | ` ` + 1010 | `` + 1012 | `${this._auth.uid}` + 1013 | `` + 1014 | ``; 1015 | } 1016 | } 1017 | let body = 1018 | `` + 1019 | `${ 1020 | head 1021 | }` + 1022 | ``; 1023 | //insert parameters 1024 | if (vars != null) { 1025 | vars.forEach(function (item) { 1026 | body += `<${item[1]}>`; 1027 | body += item[2]; 1028 | body += ``; 1029 | }); 1030 | } 1031 | body = `${body}` + `` + ``; 1032 | let port = 0; 1033 | let proto = ''; 1034 | if (this._sslPort && this._auth.auth && this.ssl) { 1035 | port = this._sslPort; 1036 | proto = 'https://'; 1037 | } else { 1038 | proto = 'http://'; 1039 | port = this.port; 1040 | } 1041 | const uri = `${proto + this.host}:${port}${url}`; 1042 | const para = 1043 | this.controller === null 1044 | ? { 1045 | method: 'post', 1046 | data: body, 1047 | timeout: this.timeout, 1048 | proxy: false, 1049 | responseType: 'text', 1050 | httpsAgent: this.agent, 1051 | headers: { 1052 | SoapAction: `${serviceType}#${action}`, 1053 | 'Content-Type': 'text/xml;charset=UTF-8', 1054 | }, 1055 | cancelToken: this.source.token, 1056 | } 1057 | : { 1058 | method: 'post', 1059 | data: body, 1060 | timeout: this.timeout, 1061 | proxy: false, 1062 | responseType: 'text', 1063 | httpsAgent: this.agent, 1064 | headers: { 1065 | SoapAction: `${serviceType}#${action}`, 1066 | 'Content-Type': 'text/xml;charset=UTF-8', 1067 | }, 1068 | signal: this.controller.signal, 1069 | }; 1070 | const response = await axios(uri, para); 1071 | if (response.status == 200) { 1072 | let result = await xml2jsP.parseStringPromise(response.data, { explicitArray: false }); 1073 | const env = result['s:Envelope']; 1074 | result = null; 1075 | if (env['s:Header']) { 1076 | const header = env['s:Header']; 1077 | if (header['h:Challenge']) { 1078 | const ch = header['h:Challenge']; 1079 | if (this._auth.chCount >= 2) { 1080 | this._auth.chCount = 0; 1081 | this._auth.auth = null; 1082 | //response.data = null; 1083 | throw new exceptionSoapCommand('authentification failure', 'Wrong user or password'); 1084 | } else { 1085 | this._auth.sn = ch.Nonce; 1086 | this._auth.realm = ch.Realm; 1087 | this._auth.auth = this._calcAuthDigest( 1088 | this._auth.uid, 1089 | this._auth.pwd, 1090 | this._auth.realm, 1091 | this._auth.sn, 1092 | ); 1093 | this._auth.chCount++; 1094 | // Repeat request 1095 | //response.data = null; 1096 | return await this.soapAction(url, serviceType, action, vars); 1097 | //return; //response2; 1098 | } 1099 | } else if (header['h:NextChallenge']) { 1100 | const nx = header['h:NextChallenge']; 1101 | //this._auth.auth = nx.Nonce; 1102 | this._auth.sn = nx.Nonce; 1103 | this._auth.realm = nx.Realm; 1104 | this._auth.auth = this._calcAuthDigest( 1105 | this._auth.uid, 1106 | this._auth.pwd, 1107 | this._auth.realm, 1108 | this._auth.sn, 1109 | ); 1110 | this._auth.chCount = 0; 1111 | } 1112 | } 1113 | if (env['s:Body']) { 1114 | const body = env['s:Body']; 1115 | if (body[`u:${action}Response`]) { 1116 | //soapResult.data = body['u:' + action + 'Response']; 1117 | //response = null; 1118 | return body[`u:${action}Response`]; 1119 | } else if (body['s:Fault']) { 1120 | //response.data = null; 1121 | throw new exceptionSoapCommand('Fault', body['s:Fault']); 1122 | } 1123 | } 1124 | } else { 1125 | //response status different to 200 1126 | const mes = JSON.stringify(response.data); 1127 | //response.data = null; 1128 | throw new exceptionSoapCommand('negative response', mes); 1129 | } 1130 | } catch (error) { 1131 | if (error.name === 'CanceledError') { 1132 | throw new exceptionSoapCommand(`${service} ${action}`, error.message); 1133 | } 1134 | if (error.name === 'AxiosError') { 1135 | if (error.response) { 1136 | if (error.response.data) { 1137 | const errResult = await xml2jsP.parseStringPromise(error.response.data, { 1138 | explicitArray: false, 1139 | }); 1140 | if (errResult['s:Envelope']) { 1141 | if (errResult['s:Envelope']['s:Body']) { 1142 | if (errResult['s:Envelope']['s:Body']['s:Fault']) { 1143 | if (errResult['s:Envelope']['s:Body']['s:Fault']['detail']) { 1144 | if (errResult['s:Envelope']['s:Body']['s:Fault']['detail']['UPnPError']) { 1145 | const errMsg = 1146 | errResult['s:Envelope']['s:Body']['s:Fault']['detail']['UPnPError'] 1147 | .errorDescription; 1148 | throw new exceptionSoapCommand( 1149 | `${service} ${action}: ${error.response.statusText} -> ${errMsg}`, 1150 | error, 1151 | ); 1152 | } 1153 | } 1154 | } 1155 | } 1156 | } 1157 | throw new exceptionSoapCommand(`${service} ${action}: ${error.response.statusText}`, error); 1158 | } else { 1159 | throw new exceptionSoapCommand(`${service} ${action}: ${error.response.statusText}`, error); 1160 | } 1161 | } else { 1162 | throw new exceptionSoapCommand(`${service} ${action}`, error); 1163 | } 1164 | } 1165 | if (error.response) { 1166 | // The request was made and the server responded with a status code that falls out of the range of 2xx 1167 | const errResult = await xml2jsP.parseStringPromise(error.response.data, { explicitArray: false }); 1168 | const errMsg = errResult['s:Envelope']['s:Body']['s:Fault']['detail']['UPnPError']; 1169 | throw new exceptionSoapCommand(`${service} ${action}`, errMsg.errorDescription); 1170 | } else if (error.request) { 1171 | // The request was made but no response was received 1172 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of http.ClientRequest in node.js 1173 | throw new exceptionSoapCommand(`${service} ${action}`, error.message); 1174 | } else { 1175 | if (error.message && error.message == 'Request canceled!') { 1176 | throw new exceptionSoapCommand(`${service} ${action}`, error.message); 1177 | } 1178 | // Something happened in setting up the request that triggered an Error 1179 | throw new exceptionSoapCommand(`${service} ${action}`, `${error.name} ${error.message}`); 1180 | } 1181 | } 1182 | } 1183 | } 1184 | 1185 | exports.Fb = Fb; 1186 | --------------------------------------------------------------------------------