├── .create-adapter.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ ├── new-issue.yml │ └── test-and-release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .releaseconfig.json ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG_OLD.md ├── LICENSE ├── README.md ├── admin ├── awtrix-light.png ├── blockly.js ├── i18n │ ├── de │ │ └── translations.json │ ├── en │ │ └── translations.json │ ├── es │ │ └── translations.json │ ├── fr │ │ └── translations.json │ ├── it │ │ └── translations.json │ ├── nl │ │ └── translations.json │ ├── pl │ │ └── translations.json │ ├── pt │ │ └── translations.json │ ├── ru │ │ └── translations.json │ ├── uk │ │ └── translations.json │ └── zh-cn │ │ └── translations.json └── jsonConfig.json ├── build ├── lib │ ├── api.js │ ├── api.js.map │ ├── app-type │ │ ├── abstract.js │ │ ├── abstract.js.map │ │ ├── native.js │ │ ├── native.js.map │ │ ├── user.js │ │ ├── user.js.map │ │ └── user │ │ │ ├── custom.js │ │ │ ├── custom.js.map │ │ │ ├── expert.js │ │ │ ├── expert.js.map │ │ │ ├── history.js │ │ │ └── history.js.map │ ├── color-convert.js │ └── color-convert.js.map ├── main.js └── main.js.map ├── docs ├── de │ ├── README.md │ └── weather-app.md └── en │ ├── README.md │ └── weather-app.md ├── eslint.config.cjs ├── io-package.json ├── package-lock.json ├── package.json ├── src ├── lib │ ├── adapter-config.d.ts │ ├── api.ts │ ├── app-type │ │ ├── abstract.ts │ │ ├── native.ts │ │ ├── user.ts │ │ └── user │ │ │ ├── custom.ts │ │ │ ├── expert.ts │ │ │ └── history.ts │ ├── color-convert.test.ts │ └── color-convert.ts ├── main.test.ts └── main.ts ├── test ├── integration.js ├── mocha.setup.js ├── mocharc.custom.json ├── package.js └── tsconfig.json ├── tsconfig.build.json └── tsconfig.json /.create-adapter.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": true, 3 | "target": "directory", 4 | "adapterName": "awtrix-light", 5 | "title": "Awtrix Light", 6 | "description": "Integrates your Ulanzi TC001 via HTTP", 7 | "keywords": ["awtrix", "ulanzi", "tc001", "pixel-clock"], 8 | "expert": "yes", 9 | "features": ["adapter"], 10 | "adminFeatures": [], 11 | "type": "hardware", 12 | "startMode": "daemon", 13 | "connectionType": "local", 14 | "dataSource": "poll", 15 | "connectionIndicator": "yes", 16 | "language": "TypeScript", 17 | "nodeVersion": "14", 18 | "adminUi": "json", 19 | "tools": ["ESLint", "Prettier"], 20 | "releaseScript": "yes", 21 | "devServer": "yes", 22 | "devServerPort": 8081, 23 | "indentation": "Space (4)", 24 | "quotes": "single", 25 | "es6class": "yes", 26 | "authorName": "Matthias Kleine", 27 | "authorGithub": "klein0r", 28 | "authorEmail": "info@haus-automatisierung.com", 29 | "gitRemoteProtocol": "SSH", 30 | "gitCommit": "yes", 31 | "defaultBranch": "master", 32 | "license": "MIT License", 33 | "dependabot": "yes", 34 | "creatorVersion": "2.5.0" 35 | } 36 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: haus_automation 2 | github: klein0r 3 | custom: ['https://haus-automatisierung.com/kurse/'] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # v0.1 2 | name: Bug report 3 | title: "[Bug]: " 4 | description: "Create a bug report to help improve this ioBroker adapter" 5 | assignees: 6 | - klein0r 7 | body: 8 | # general information 9 | - type: markdown 10 | attributes: 11 | value: > 12 | **Thank you for wanting to report a bug in this adapter!** 13 | 14 | If this is the first time you are doing this, please take a few moments to read 15 | through the [README](https://github.com/klein0r/ioBroker.awtrix-light/blob/master/README.md). 16 | 17 | You are about to report a bug in **Awtrix Light Adapter**. Do not proceed if your issue 18 | occurs with ioBroker core, any other adapters, unofficial or outdated 19 | adapter or NodeJS versions. 20 | 21 | Do also not seek support here ("I need help with ...", "I have a 22 | question ...", "Can someone walk me through ..."), that belongs into the 23 | [ioBroker forum at forum.iobroker.net](https://forum.iobroker.net/). 24 | 25 | Thank you for your collaboration! 26 | - type: checkboxes 27 | attributes: 28 | label: I'm sure that 29 | options: 30 | - label: This issue is still present in the **current beta version** of this adapter 31 | required: true 32 | - label: There is no other (open) issue with the same topic (use the search!) 33 | required: true 34 | - label: This issue is not described in the adapter documentation / FAQ (read the docs!) 35 | required: true 36 | # adapter specific input fields 37 | - type: input 38 | attributes: 39 | label: Version of awtrix-light firmware 40 | description: Displayed during startup of the device (and in the ioBroker states of this adapter) 41 | validations: 42 | required: true 43 | # general questions 44 | - type: textarea 45 | attributes: 46 | label: The problem 47 | description: >- 48 | Describe the issue you are experiencing here. Tell us what you were trying to do 49 | step by step, and what happened that you did not expect. 50 | 51 | Provide a clear and concise description of what the problem is and include as many 52 | details as possible. 53 | validations: 54 | required: true 55 | - type: textarea 56 | attributes: 57 | label: iobroker.current.log (in debug mode!) 58 | description: >- 59 | Share the log file of this adapter in debug mode 60 | - type: input 61 | attributes: 62 | label: Version of nodejs 63 | description: Can be found in the host section of ioBroker admin 64 | validations: 65 | required: true 66 | - type: input 67 | attributes: 68 | label: Version of ioBroker js-controller 69 | description: Can be found in the host section of ioBroker admin 70 | validations: 71 | required: true 72 | - type: input 73 | attributes: 74 | label: Version of adapter 75 | description: Can be found in the adapters tab of ioBroker admin 76 | validations: 77 | required: true 78 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Have questions or need support? 4 | url: https://forum.iobroker.net/topic/65571/test-adapter-awtrix-light 5 | about: Please get in touch on the ioBroker Community Forum! -------------------------------------------------------------------------------- /.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 18 | -------------------------------------------------------------------------------- /.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 | assignees: 11 | - klein0r 12 | versioning-strategy: increase 13 | 14 | - package-ecosystem: github-actions 15 | directory: "/" 16 | schedule: 17 | interval: monthly 18 | time: "04:00" 19 | timezone: Europe/Berlin 20 | open-pull-requests-limit: 5 21 | assignees: 22 | - klein0r 23 | -------------------------------------------------------------------------------- /.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 | 6 | on: 7 | # WARNING: This needs to be run in the PR base, DO NOT build untrusted code in this action 8 | # details under https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/ 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@v4 18 | 19 | - name: Check if PR should be auto-merged 20 | uses: ahmadnassri/action-dependabot-auto-merge@v2 21 | with: 22 | github-token: ${{ secrets.AUTO_MERGE_TOKEN }} 23 | command: squash and merge 24 | -------------------------------------------------------------------------------- /.github/workflows/new-issue.yml: -------------------------------------------------------------------------------- 1 | # v0.6 2 | name: New issue 3 | 4 | on: 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | issueCreated: 10 | runs-on: ubuntu-latest 11 | if: ${{ !github.event.issue.pull_request && !startsWith(github.event.issue.title, 'Update stable version in repo') && github.actor != 'ioBroker-Bot' }} 12 | permissions: 13 | issues: write 14 | steps: 15 | - name: Create label (not beta version) 16 | id: create_label_version 17 | run: gh label create not-latest-version --description "Beta version not found in issue" --color B02727 --force 18 | env: 19 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | GH_REPO: ${{ github.repository }} 21 | NUMBER: ${{ github.event.issue.number }} 22 | 23 | - name: Setup node 24 | id: setup_node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 20 28 | - name: Get package name 29 | id: get_package_name 30 | uses: ASzc/change-string-case-action@v6 31 | with: 32 | string: ${{ github.event.repository.name }} 33 | - name: Get NPM version 34 | id: get_npm_version 35 | run: echo "LATEST_VERSION=$(npm view ${{ steps.get_package_name.outputs.lowercase }}@latest version)" >> "$GITHUB_OUTPUT" 36 | - name: Create comment 37 | id: create_comment_ok 38 | if: ${{ contains(github.event.issue.body, steps.get_npm_version.outputs.LATEST_VERSION) }} 39 | uses: peter-evans/create-or-update-comment@v4 40 | with: 41 | issue-number: ${{ github.event.issue.number }} 42 | body: | 43 | Thanks for reporting a new issue @${{ github.actor }}! 44 | 45 | 1. Please ensure your topic is not covered in the [documentation](https://github.com/${{ github.event.repository.full_name }}/blob/v${{ steps.get_npm_version.outputs.LATEST_VERSION }}/docs/en/README.md) 46 | 2. Please attach all necessary log files (in debug mode!), screenshots and other information to reproduce this issue. 47 | 3. [Search for the issue topic](https://github.com/${{ github.event.repository.full_name }}/issues?q=is%3Aissue) in other/closed issues to avoid duplicates! 48 | 4. Check the changelog if the issue has already been covered in a previous release 49 | 50 | *Otherwise this issue will be closed!* 51 | - name: Create comment (not beta version) 52 | id: create_comment_version 53 | if: ${{ !contains(github.event.issue.body, steps.get_npm_version.outputs.LATEST_VERSION) }} 54 | uses: peter-evans/create-or-update-comment@v4 55 | with: 56 | issue-number: ${{ github.event.issue.number }} 57 | body: | 58 | Thanks for reporting a new issue @${{ github.actor }}! 59 | 60 | **Important:** Ensure that you use the latest available **beta version** of this adapter (not the current stable version!): **${{ steps.get_npm_version.outputs.LATEST_VERSION }}** 61 | 62 | 1. Please ensure your topic is not covered in the [documentation](https://github.com/${{ github.event.repository.full_name }}/blob/v${{ steps.get_npm_version.outputs.LATEST_VERSION }}/docs/en/README.md) 63 | 2. Please attach all necessary log files (in debug mode!), screenshots and other information to reproduce this issue. 64 | 3. [Search for the issue topic](https://github.com/${{ github.event.repository.full_name }}/issues?q=is%3Aissue) in other/closed issues to avoid duplicates! 65 | 4. Check the changelog if the issue has already been covered in a previous release 66 | 67 | *Otherwise this issue will be closed!* 68 | - name: Add label (not beta version) 69 | id: add_label_version 70 | if: ${{ !contains(github.event.issue.body, steps.get_npm_version.outputs.LATEST_VERSION) }} 71 | run: gh issue edit "$NUMBER" --add-label not-latest-version 72 | env: 73 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | GH_REPO: ${{ github.repository }} 75 | NUMBER: ${{ github.event.issue.number }} 76 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | 3 | # Run this job on all pushes and pull requests 4 | # as well as tags with a semantic version 5 | on: 6 | push: 7 | branches: 8 | - "master" 9 | tags: 10 | # normal versions 11 | - "v[0-9]+.[0-9]+.[0-9]+" 12 | # pre-releases 13 | - "v[0-9]+.[0-9]+.[0-9]+-**" 14 | pull_request: {} 15 | 16 | # Cancel previous PR/branch runs when a new commit is pushed 17 | concurrency: 18 | group: ${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | # Performs quick checks before the expensive test runs 23 | check-and-lint: 24 | if: contains(github.event.head_commit.message, '[skip ci]') == false 25 | 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: ioBroker/testing-action-check@v1 30 | with: 31 | node-version: '20.x' 32 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 33 | # install-command: 'npm install' 34 | type-checking: true 35 | lint: true 36 | 37 | # Runs adapter tests on all supported node versions and OSes 38 | adapter-tests: 39 | if: contains(github.event.head_commit.message, '[skip ci]') == false 40 | 41 | runs-on: ${{ matrix.os }} 42 | strategy: 43 | matrix: 44 | node-version: [20.x, 22.x, 24.x] 45 | os: [ubuntu-latest, windows-latest, macos-latest] 46 | 47 | steps: 48 | - uses: ioBroker/testing-action-adapter@v1 49 | with: 50 | node-version: ${{ matrix.node-version }} 51 | os: ${{ matrix.os }} 52 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 53 | # install-command: 'npm install' 54 | build: true 55 | 56 | deploy: 57 | needs: [check-and-lint, adapter-tests] 58 | 59 | # Trigger this step only when a commit on any branch is tagged with a version number 60 | if: | 61 | contains(github.event.head_commit.message, '[skip ci]') == false && 62 | github.event_name == 'push' && 63 | startsWith(github.ref, 'refs/tags/v') 64 | 65 | runs-on: ubuntu-latest 66 | 67 | # Write permissions are required to create Github releases 68 | permissions: 69 | contents: write 70 | 71 | steps: 72 | - uses: ioBroker/testing-action-deploy@v1 73 | with: 74 | node-version: '20.x' 75 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 76 | # install-command: 'npm install' 77 | build: true 78 | npm-token: ${{ secrets.NPM_TOKEN }} 79 | github-token: ${{ secrets.GITHUB_TOKEN }} 80 | 81 | sentry: true 82 | sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 83 | sentry-project: "iobroker-awtrix-light" 84 | sentry-version-prefix: "iobroker.awtrix-light" 85 | sentry-sourcemap-paths: "build/" 86 | # If your sentry project is linked to a GitHub repository, you can enable the following option 87 | # sentry-github-integration: true 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # No dot-directories except github/vscode 2 | .*/ 3 | !.vscode/ 4 | !.github/ 5 | 6 | *.code-workspace 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 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | README.md 4 | io-package.json 5 | docs/** 6 | .github/** 7 | build/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 200, 6 | "useTabs": false, 7 | "tabWidth": 4, 8 | "endOfLine": "lf" 9 | } 10 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license", "manual-review"], 3 | "exec": { 4 | "before_commit": "npm run build" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "eslint.enable": true, 4 | "editor.formatOnSave": false, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "[typescript]": { 7 | "editor.codeActionsOnSave": { 8 | "source.organizeImports": "explicit" 9 | } 10 | }, 11 | "json.schemas": [ 12 | { 13 | "fileMatch": [ 14 | "io-package.json" 15 | ], 16 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" 17 | }, 18 | { 19 | "fileMatch": [ 20 | "admin/jsonConfig.json", 21 | "admin/jsonCustom.json", 22 | "admin/jsonTab.json" 23 | ], 24 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.admin/master/packages/jsonConfig/schemas/jsonConfig.json" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /CHANGELOG_OLD.md: -------------------------------------------------------------------------------- 1 | # Older changes 2 | ## 1.4.0 (2024-11-20) 3 | 4 | * (@klein0r) Added support for notification manager 5 | 6 | ## 1.3.0 (2024-08-01) 7 | 8 | * (@klein0r) Added sentry plugin for error reporting 9 | 10 | ## 1.2.1 (2024-06-07) 11 | 12 | * (klein0r) Fixed Blockly definitions (removed warnings) 13 | * (klein0r) Updated dependencies 14 | 15 | ## 1.2.0 (2024-05-16) 16 | 17 | * (klein0r) Fixed wrong translations ins expert apps (duration) 18 | * (klein0r) Added progress bar for expert apps 19 | 20 | ## 1.1.0 (2024-05-11) 21 | 22 | * (klein0r) Sync app activations (if enabled) 23 | 24 | ## 1.0.1 (2024-04-28) 25 | 26 | * (klein0r) Keep text case of expert apps (ignore system settings) 27 | 28 | ## 1.0.0 (2024-04-04) 29 | 30 | NodeJS >= 18.x and js-controller >= 5 is required 31 | 32 | Updated recommended firmware version to 0.96 33 | 34 | ## 0.16.0 (2024-03-12) 35 | 36 | Updated recommended firmware version to 0.95 37 | 38 | * (klein0r) Added notification for firmware update 39 | * (klein0r) Added setting state for volume 40 | * (klein0r) Rebranding Awtrix Light to Awtrix 3 41 | 42 | ## 0.15.1 (2024-03-12) 43 | 44 | * (klein0r) Fixed default values of color states 45 | 46 | ## 0.15.0 (2024-03-06) 47 | 48 | * (klein0r) Keep apps contents in sync 49 | 50 | ## 0.14.1 (2024-03-06) 51 | 52 | * (klein0r) Fixed roles of calendar header, body and text (rgb) 53 | 54 | ## 0.14.0 (2024-02-20) 55 | 56 | * (klein0r) Allow to round numbers dynamically (depends on length) 57 | 58 | ## 0.13.1 (2024-01-25) 59 | 60 | * (klein0r) Fixed hold option in blockly 61 | 62 | ## 0.13.0 (2024-01-25) 63 | 64 | * (klein0r) Added state for text color and background color in expert apps 65 | * (klein0r) Avoid app refresh when no values have been changed 66 | 67 | ## 0.12.0 (2024-01-24) 68 | 69 | * (klein0r) Added hold option to blockly 70 | * (klein0r) Added state to dismiss notifications 71 | 72 | ## 0.11.0 (2024-01-09) 73 | 74 | Updated recommended firmware version to 0.94 75 | 76 | * (klein0r) Added bar graph to history apps 77 | * (klein0r) Added aggregation for history apps 78 | 79 | ## 0.10.2 (2023-12-14) 80 | 81 | * (klein0r) Removed callbacks in blockly code to prevent timeouts 82 | 83 | ## 0.10.1 (2023-12-01) 84 | 85 | Updated recommended firmware version to 0.91 86 | 87 | * (klein0r) Added uid and ip address states 88 | 89 | ## 0.10.0 (2023-10-23) 90 | 91 | Updated recommended firmware version to 0.90 92 | 93 | * (klein0r) Added support for sleep mode 94 | * (klein0r) Added fading for indicators 95 | 96 | ## 0.9.2 (2023-10-22) 97 | 98 | * (klein0r) Fixed: Visisble state of expert apps 99 | 100 | ## 0.9.1 (2023-10-02) 101 | 102 | NodeJS 16.x is required 103 | 104 | * (klein0r) Fixed hidden apps 105 | * (klein0r) Fixed color conversions of settings 106 | 107 | ## 0.9.0 (2023-10-01) 108 | 109 | Updated recommended firmware version to 0.88 110 | 111 | * (klein0r) Added expert apps 112 | * (klein0r) Use the last value of fast refreshing states 113 | * (klein0r) Added settings for calendar colors 114 | * (klein0r) Allow to use apps without text (just background effect) 115 | * (AlCalzone) Added rtttl api endpoint support (via sendTo) 116 | * (klein0r) Native apps have been renamed 117 | 118 | ## 0.8.0 (2023-09-04) 119 | 120 | Updated recommended firmware version to 0.83 121 | 122 | * (klein0r) Allow to set custom app positions (expert options) 123 | * (klein0r) Unsubscribe from all states if device is not reachable 124 | * (klein0r) Added options basic auth 125 | * (klein0r) Get background effects via API 126 | * (klein0r) Fixed 0 decimals setting 127 | * (klein0r) Changed log level of some messages 128 | * (klein0r) Added states for transitions 129 | 130 | ## 0.7.1 (2023-08-09) 131 | 132 | * (klein0r) Added option for number format 133 | 134 | ## 0.7.0 (2023-08-03) 135 | 136 | Updated recommended firmware version to 0.72 137 | 138 | * (klein0r) Added MovingLine effect 139 | * (klein0r) Added settings for time style and transition effect 140 | * (klein0r) Setting repeat to 1 in blockly notifications 141 | 142 | ## 0.6.2 (2023-07-30) 143 | 144 | * (klein0r) Fixed handling of state cache when object has been changed 145 | 146 | ## 0.6.1 (2023-07-28) 147 | 148 | * (klein0r) Remove background effect in threshold overrides 149 | * (klein0r) Minor fixes in admin config 150 | * (klein0r) Fixed missing icon in history apps 151 | 152 | ## 0.6.0 (2023-07-26) 153 | 154 | Updated recommended firmware version to 0.71 155 | 156 | * (klein0r) Added option for background effects 157 | * (klein0r) Setting default of repeat to 0 158 | * (klein0r) Dropped timer support (removed in firmware 0.71) 159 | * (klein0r) Removed native app "eyes" (removed in firmware 0.71) 160 | 161 | ## 0.5.1 (2023-07-19) 162 | 163 | * (klein0r) Fixed color conversion for svg 164 | * (klein0r) Added support for state type "mixed" 165 | * (klein0r) Improved log messages 166 | 167 | ## 0.5.0 (2023-07-18) 168 | 169 | * (klein0r) Added options to override icon, text color and backgroup color for thresholds 170 | * (klein0r) Added option to download screen content to state (as SVG graphic) 171 | * (klein0r) Draw welcome icon on connection 172 | 173 | ## 0.4.0 (2023-07-12) 174 | 175 | * (klein0r) Allow to import settings from another instance 176 | 177 | ## 0.3.4 (2023-07-11) 178 | 179 | * (klein0r) Use default scroll speed if 0 180 | * (klein0r) Instance selection for history apps 181 | 182 | ## 0.3.3 (2023-07-07) 183 | 184 | * (klein0r) Use default duration if 0 185 | 186 | ## 0.3.2 (2023-07-06) 187 | 188 | * (klein0r) Delete apps on instance stop (configurable) 189 | * (klein0r) Added scrolling speed to settings 190 | * (klein0r) Added block buttons to settings 191 | 192 | ## 0.3.1 (2023-07-06) 193 | 194 | * (klein0r) Some app options were ignored for static text apps 195 | 196 | ## 0.3.0 (2023-07-05) 197 | 198 | Admin adapter in version 6.6.0 is required 199 | 200 | * (klein0r) Updated instance configuration (new admin component) 201 | * (klein0r) Added several options for custom apps 202 | * (klein0r) Individual background color for each app 203 | 204 | ## 0.2.0 (2023-07-03) 205 | 206 | * (klein0r) Added default values to blockly blocks 207 | * (klein0r) Remove custom apps when text is empty 208 | 209 | ## 0.1.2 (2023-06-28) 210 | 211 | * (klein0r) Limit the number of history items 212 | * (klein0r) Warn if a connected state has the wrong data type 213 | 214 | ## 0.1.1 (2023-06-22) 215 | 216 | * (klein0r) Improved error handling when device is not reachable 217 | 218 | ## 0.1.0 (2023-06-16) 219 | 220 | * (klein0r) Added rainbow and color to blockly notifications 221 | 222 | ## 0.0.16 (2023-06-14) 223 | 224 | Updated recommended firmware version to 0.70 225 | 226 | * (klein0r) Added expert option for HTTP timeout 227 | * (klein0r) Added color settings for native apps 228 | 229 | ## 0.0.15 (2023-06-13) 230 | 231 | Updated recommended firmware version to 0.69 232 | 233 | * (klein0r) Added option to hide own apps via state 234 | * (klein0r) Dropped framerate setting (no longer supported) 235 | 236 | ## 0.0.14 (2023-06-11) 237 | 238 | * (klein0r) Added validator for IP address 239 | * (klein0r) usedRam was renamed to freeRam 240 | 241 | ## 0.0.13 (2023-06-10) 242 | 243 | * (klein0r) Refresh all states when device was offline / not reachable 244 | * (klein0r) Automatically remove history apps (if not updated) 245 | * (klein0r) Improved checks for history data 246 | 247 | ## 0.0.12 (2023-06-08) 248 | 249 | * (klein0r) Added number of decimals to app configuration (for numeric values) 250 | * (klein0r) Added line color of history apps to each table row 251 | * (klein0r) Removed visibility states for native apps (deprecated by firmware) 252 | * (klein0r) Improved logging for history apps 253 | * (klein0r) Added wifi signal strength and used RAM as states 254 | 255 | ## 0.0.11 (2023-06-07) 256 | 257 | * (klein0r) Display history data in apps as charts 258 | * (klein0r) Format number values and limit number of decimals 259 | * (klein0r) Limit app refresh time when state changes (configurable) 260 | 261 | ## 0.0.10 (2023-06-06) 262 | 263 | * (klein0r) Automatically delete unknown or old apps 264 | * (klein0r) Added option to play sound via message / blockly 265 | 266 | ## 0.0.9 (2023-06-06) 267 | 268 | Updated recommended firmware version to 0.68 269 | 270 | * (klein0r) Added battery, uptime and display states 271 | * (klein0r) Added more settings options 272 | * (klein0r) Color conversion for all rgb settings 273 | 274 | ## 0.0.8 (2023-06-05) 275 | 276 | * (klein0r) Added custom apps via instance configuration 277 | 278 | ## 0.0.7 (2023-06-04) 279 | 280 | * (klein0r) Added timer feature to blockly 281 | * (klein0r) Added color conversion to hex in settings 282 | 283 | ## 0.0.6 (2023-06-03) 284 | 285 | Updated recommended firmware version to 0.67 286 | 287 | * (klein0r) Added some settings as states 288 | * (klein0r) Added stack and wakeup option to blockly 289 | * (klein0r) Battery app was hidden when invisible 290 | 291 | ## 0.0.5 (2023-05-24) 292 | 293 | * (klein0r) Added notification support via sendTo / blockly 294 | * (klein0r) Improved error handling 295 | 296 | ## 0.0.4 (2023-05-19) 297 | 298 | * (klein0r) Added indicator support 299 | * (klein0r) Added moodlight support 300 | * (klein0r) Added device update and reboot options 301 | 302 | ## 0.0.3 (2023-05-16) 303 | 304 | * (klein0r) Allow to switch apps via state 305 | * (klein0r) Added more states 306 | 307 | ## 0.0.2 (2023-05-16) 308 | 309 | * (klein0r) Initial release 310 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matthias Kleine 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/awtrix-light.png) 2 | 3 | # ioBroker.awtrix-light 4 | 5 | [![NPM version](https://img.shields.io/npm/v/iobroker.awtrix-light?style=flat-square)](https://www.npmjs.com/package/iobroker.awtrix-light) 6 | [![Downloads](https://img.shields.io/npm/dm/iobroker.awtrix-light?label=npm%20downloads&style=flat-square)](https://www.npmjs.com/package/iobroker.awtrix-light) 7 | ![node-lts](https://img.shields.io/node/v-lts/iobroker.awtrix-light?style=flat-square) 8 | ![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/iobroker.awtrix-light?label=npm%20dependencies&style=flat-square) 9 | 10 | ![GitHub](https://img.shields.io/github/license/klein0r/iobroker.awtrix-light?style=flat-square) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/klein0r/iobroker.awtrix-light?logo=github&style=flat-square) 12 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/klein0r/iobroker.awtrix-light?logo=github&style=flat-square) 13 | ![GitHub last commit](https://img.shields.io/github/last-commit/klein0r/iobroker.awtrix-light?logo=github&style=flat-square) 14 | ![GitHub issues](https://img.shields.io/github/issues/klein0r/iobroker.awtrix-light?logo=github&style=flat-square) 15 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/klein0r/iobroker.awtrix-light/test-and-release.yml?branch=master&logo=github&style=flat-square) 16 | 17 | ## Versions 18 | 19 | ![Beta](https://img.shields.io/npm/v/iobroker.awtrix-light.svg?color=red&label=beta) 20 | ![Stable](http://iobroker.live/badges/awtrix-light-stable.svg) 21 | ![Installed](http://iobroker.live/badges/awtrix-light-installed.svg) 22 | 23 | Integrate your [Awtrix 3 (Awtrix Light)](https://github.com/Blueforcer/awtrix3) device (e.g. Ulanzi TC001) via HTTP 24 | 25 | Buy here: [Aliexpress.com](https://haus-auto.com/p/ali/UlanziTC001) or here: [ulanzi.de](https://haus-auto.com/p/ula/UlanziTC001) (Affiliate-Links) 26 | 27 | ## Sponsored by 28 | 29 | [![ioBroker Master Kurs](https://haus-automatisierung.com/images/ads/ioBroker-Kurs.png?2024)](https://haus-automatisierung.com/iobroker-kurs/?refid=iobroker-awtrix-light) 30 | 31 | ## Documentation 32 | 33 | [🇺🇸 Documentation](./docs/en/README.md) 34 | 35 | [🇩🇪 Dokumentation](./docs/de/README.md) 36 | 37 | ## Sentry 38 | 39 | **This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0. 40 | 41 | ## Changelog 42 | 46 | ### 2.0.0 (2025-05-02) 47 | 48 | * (@klein0r) Added base object for expert apps to allow all options 49 | * (@klein0r) Added responsive design for admin config 50 | 51 | ### 1.7.0 (2025-04-08) 52 | 53 | * (@klein0r) Improved error handling when adapter is not ready (starting) 54 | * (@klein0r) Added scroll speed to expert apps 55 | * (@klein0r) Added icons for custom apps in object tree 56 | 57 | ### 1.6.0 (2025-01-27) 58 | 59 | Updated recommended firmware version to 0.98 60 | 61 | * (@klein0r) Updated dependencies 62 | 63 | ### 1.5.0 (2025-01-07) 64 | 65 | Updated recommended firmware version to 0.97 66 | 67 | * (@klein0r) Updated dependencies 68 | 69 | ### 1.4.1 (2024-11-20) 70 | 71 | NodeJS >= 20.x and js-controller >= 6 is required 72 | 73 | ## License 74 | 75 | MIT License 76 | 77 | Copyright (c) 2025 Matthias Kleine 78 | 79 | Permission is hereby granted, free of charge, to any person obtaining a copy 80 | of this software and associated documentation files (the "Software"), to deal 81 | in the Software without restriction, including without limitation the rights 82 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 83 | copies of the Software, and to permit persons to whom the Software is 84 | furnished to do so, subject to the following conditions: 85 | 86 | The above copyright notice and this permission notice shall be included in all 87 | copies or substantial portions of the Software. 88 | 89 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 90 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 91 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 92 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 93 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 94 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 95 | SOFTWARE. 96 | -------------------------------------------------------------------------------- /admin/awtrix-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klein0r/ioBroker.awtrix-light/e4654b544ef8215cd6e5e5508a152b16459862fd/admin/awtrix-light.png -------------------------------------------------------------------------------- /admin/i18n/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Optionen", 3 | "IP address of your Awtrix Light device": "IP-Adresse Ihres Awtrix Light-Geräts", 4 | "Apps": "Apps", 5 | "Custom Apps": "Benutzerdefinierte Apps", 6 | "Automatically delete unknown apps": "Unbekannte Apps automatisch löschen", 7 | "Ignore new values after state change (in seconds)": "Neue Werte nach Änderung ignorieren (in Sekunden)", 8 | "Prevents too many requests for rapid state changes": "Verhindert zu viele Anfragen nach schnellen Zustandsänderungen", 9 | "Background color": "Hintergrundfarbe", 10 | "Refresh interval (in seconds)": "Aktualisierungsintervall (in Sekunden)", 11 | "Expert options": "Expertenoptionen", 12 | "HTTP timeout": "HTTP-Timeout", 13 | "Name": "Name", 14 | "Just lower case letters (a-z)": "Nur Kleinbuchstaben (a-z)", 15 | "Icon": "Symbol", 16 | "Must be uploaded manually": "Muss manuell hochgeladen werden", 17 | "Duration (sec)": "Dauer (Sek.)", 18 | "Repetitions": "Wiederholungen", 19 | "Text": "Text", 20 | "%s will be replaced by value and %u by unit": "%s wird durch Wert und %u durch die Einheit ersetzt", 21 | "Remove object or add %s (and %u) to text": "Objekt entfernen oder %s (und %u) zum Text hinzufügen", 22 | "Object": "Objekt", 23 | "Should be type of string or number": "Sollte ein String- oder Zahlentyp sein", 24 | "Select object or remove %s and %u from text": "Wähle ein Objekt aus oder entferne %s und %u aus dem Text", 25 | "Text color": "Textfarbe", 26 | "Decimals": "Dezimalstellen", 27 | "Numeric values will be rounded": "Numerische Werte werden gerundet", 28 | "Text in rainbow colors": "Text in Regenbogenfarben", 29 | "Disable text scrolling": "Deaktiviere das scrollen des Textes", 30 | "Scroll speed": "Scrollgeschwindigkeit", 31 | "Source instance": "Datenquelle (Instanz)", 32 | "Logging must be enabled for the selected ID": "Für die ausgewählte ID muss die Protokollierung aktiviert sein", 33 | "Define a valid source for history data": "Definiere ein gültige Quelle für Verlaufsdaten", 34 | "Select data source": "Datenquelle auswählen", 35 | "Color": "Farbe", 36 | "Line color of chart": "Linienfarbe des Diagramms", 37 | "Delete apps when instance is stopped": "Apps löschen, wenn die Instanz gestoppt wird", 38 | "0 = default": "0 = Standard", 39 | "Use settings of other instance": "Einstellungen einer anderen Instanz verwenden", 40 | "Copy app settings of": "App-Einstellungen kopieren von", 41 | "Select another instance": "Wähle eine andere Instanz", 42 | "Text options": "Textoptionen", 43 | "Threshold overrides (type of state must be number)": "Schwellenwert-Überschreibungen (Datentyp muss Zahl sein)", 44 | "If value less than": "Falls Wert kleiner als", 45 | "If value greater than": "Falls Wert größer als", 46 | "Set icon, text color or background color override": "Lege ein Symbol, die Textfarbe oder die Hintergrundfarbe fest", 47 | "Connection": "Verbindung", 48 | "Support this project": "Unterstütze dieses Projekt", 49 | "Download screen content": "Bildschirminhalt herunterladen", 50 | "Download interval (in seconds)": "Download-Intervall (in Sekunden)", 51 | "Background options": "Hintergrundoptionen", 52 | "Use background effect": "Hintergrundeffekt verwenden", 53 | "Background effect": "Hintergrundeffekt", 54 | "See all effects": "Alle Effekte anzeigen", 55 | "Number format": "Zahlenformat", 56 | "Own position for each app": "Eigene Position für jede App", 57 | "Position": "Position", 58 | "Expert Apps": "Experten-Apps", 59 | "Username": "Benutzername", 60 | "Password": "Passwort", 61 | "Screen": "Bildschirm", 62 | "Round dynamically": "Dynamisch runden", 63 | "Adjust digits to avoid scrolling": "Passt Dezimalstellen an, um ein Scrollen zu vermeiden", 64 | "Display as": "Darstellen als", 65 | "Line": "Linie", 66 | "Bar": "Balken", 67 | "Mode": "Modus", 68 | "Last values": "Letzte Werte", 69 | "Aggregated values": "Aggregierte Werte", 70 | "Aggregation": "Aggregieren nach", 71 | "Average": "Durchschnitt", 72 | "Min": "Minimal", 73 | "Max": "Maximal", 74 | "Count": "Anzahl", 75 | "Step size": "Schrittweite", 76 | "seconds": "Sekunden", 77 | "Activate same apps as in main instance": "Dieselben Apps wie in der Hauptinstanz aktivieren" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Options", 3 | "IP address of your Awtrix Light device": "IP address of your Awtrix Light device", 4 | "Apps": "Apps", 5 | "Custom Apps": "Custom Apps", 6 | "Automatically delete unknown apps": "Automatically delete unknown apps", 7 | "Ignore new values after state change (in seconds)": "Ignore new values after state change (in seconds)", 8 | "Prevents too many requests for rapid state changes": "Prevents too many requests for rapid state changes", 9 | "Background color": "Background color", 10 | "Refresh interval (in seconds)": "Refresh interval (in seconds)", 11 | "Expert options": "Expert options", 12 | "HTTP timeout": "HTTP timeout", 13 | "Name": "Name", 14 | "Just lower case letters (a-z)": "Just lower case letters (a-z)", 15 | "Icon": "Icon", 16 | "Must be uploaded manually": "Must be uploaded manually", 17 | "Duration (sec)": "Duration (sec)", 18 | "Repetitions": "Repetitions", 19 | "Text": "Text", 20 | "%s will be replaced by value and %u by unit": "%s will be replaced by value and %u by unit", 21 | "Remove object or add %s (and %u) to text": "Remove object or add %s (and %u) to text", 22 | "Object": "Object", 23 | "Should be type of string or number": "Should be type of string or number", 24 | "Select object or remove %s and %u from text": "Select object or remove %s and %u from text", 25 | "Text color": "Text color", 26 | "Decimals": "Decimals", 27 | "Numeric values will be rounded": "Numeric values will be rounded", 28 | "Text in rainbow colors": "Text in rainbow colors", 29 | "Disable text scrolling": "Disable text scrolling", 30 | "Scroll speed": "Scroll speed", 31 | "Source instance": "Source instance", 32 | "Logging must be enabled for the selected ID": "Logging must be enabled for the selected ID", 33 | "Define a valid source for history data": "Define a valid source for history data", 34 | "Select data source": "Select data source", 35 | "Color": "Color", 36 | "Line color of chart": "Line color of chart", 37 | "Delete apps when instance is stopped": "Delete apps when instance is stopped", 38 | "0 = default": "0 = default", 39 | "Use settings of other instance": "Use settings of other instance", 40 | "Copy app settings of": "Copy app settings of", 41 | "Select another instance": "Select another instance", 42 | "Text options": "Text options", 43 | "Threshold overrides (type of state must be number)": "Threshold overrides (type of state must be number)", 44 | "If value less than": "If value less than", 45 | "If value greater than": "If value greater than", 46 | "Set icon, text color or background color override": "Set icon, text color or background color override", 47 | "Connection": "Connection", 48 | "Support this project": "Support this project", 49 | "Download screen content": "Download screen content", 50 | "Download interval (in seconds)": "Download interval (in seconds)", 51 | "Background options": "Background options", 52 | "Use background effect": "Use background effect", 53 | "Background effect": "Background effect", 54 | "See all effects": "See all effects", 55 | "Number format": "Number format", 56 | "Own position for each app": "Own position for each app", 57 | "Position": "Position", 58 | "Expert Apps": "Expert Apps", 59 | "Username": "Username", 60 | "Password": "Password", 61 | "Screen": "Screen", 62 | "Round dynamically": "Round dynamically", 63 | "Adjust digits to avoid scrolling": "Adjust digits to avoid scrolling", 64 | "Display as": "Display as", 65 | "Line": "Line", 66 | "Bar": "Bar", 67 | "Mode": "Mode", 68 | "Last values": "Last values", 69 | "Aggregated values": "Aggregated values", 70 | "Aggregation": "Aggregation", 71 | "Average": "Average", 72 | "Min": "Min", 73 | "Max": "Max", 74 | "Count": "Count", 75 | "Step size": "Step size", 76 | "seconds": "seconds", 77 | "Activate same apps as in main instance": "Activate same apps as in main instance" 78 | } -------------------------------------------------------------------------------- /admin/i18n/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Opciones", 3 | "IP address of your Awtrix Light device": "Dirección IP de su dispositivo Awtrix Light", 4 | "Apps": "aplicaciones", 5 | "Custom Apps": "Aplicaciones personalizadas", 6 | "Automatically delete unknown apps": "Eliminar aplicaciones desconocidas automáticamente", 7 | "Ignore new values after state change (in seconds)": "Ignorar nuevos valores después del cambio de estado (en segundos)", 8 | "Prevents too many requests for rapid state changes": "Evita demasiadas solicitudes de cambios rápidos de estado", 9 | "Background color": "Color de fondo", 10 | "Refresh interval (in seconds)": "Intervalo de actualización (en segundos)", 11 | "Expert options": "Opciones de experto", 12 | "HTTP timeout": "Tiempo de espera de HTTP", 13 | "Name": "Nombre", 14 | "Just lower case letters (a-z)": "Solo letras minúsculas (a-z)", 15 | "Icon": "Icono", 16 | "Must be uploaded manually": "Debe cargarse manualmente", 17 | "Duration (sec)": "Duración (seg)", 18 | "Repetitions": "repeticiones", 19 | "Text": "Texto", 20 | "%s will be replaced by value and %u by unit": "%s será reemplazado por valor y %u por unidad", 21 | "Remove object or add %s (and %u) to text": "Eliminar objeto o agregar %s (y %u) al texto", 22 | "Object": "Objeto", 23 | "Should be type of string or number": "Debe ser tipo de cadena o número", 24 | "Select object or remove %s and %u from text": "Seleccionar objeto o eliminar %s y %u del texto", 25 | "Text color": "Color de texto", 26 | "Decimals": "decimales", 27 | "Numeric values will be rounded": "Los valores numéricos se redondearán", 28 | "Text in rainbow colors": "Texto en colores del arco iris", 29 | "Disable text scrolling": "Deshabilitar desplazamiento de texto", 30 | "Scroll speed": "Velocidad de desplazamiento", 31 | "Source instance": "Instancia de origen", 32 | "Logging must be enabled for the selected ID": "El registro debe estar habilitado para el ID seleccionado", 33 | "Define a valid source for history data": "Definir una fuente válida para los datos del historial", 34 | "Select data source": "Seleccionar fuente de datos", 35 | "Color": "Color", 36 | "Line color of chart": "Color de línea del gráfico", 37 | "Delete apps when instance is stopped": "Eliminar aplicaciones cuando se detiene la instancia", 38 | "0 = default": "0 = predeterminado", 39 | "Use settings of other instance": "Usar la configuración de otra instancia", 40 | "Copy app settings of": "Copiar la configuración de la aplicación de", 41 | "Select another instance": "Seleccione otra instancia", 42 | "Text options": "Opciones de texto", 43 | "Threshold overrides (type of state must be number)": "Anulaciones de umbral (el tipo de estado debe ser un número)", 44 | "If value less than": "Si el valor es menor que", 45 | "If value greater than": "Si el valor es mayor que", 46 | "Set icon, text color or background color override": "Establecer anulación de icono, color de texto o color de fondo", 47 | "Connection": "Conexión", 48 | "Support this project": "Apoya este proyecto", 49 | "Download screen content": "Descargar contenido de la pantalla", 50 | "Download interval (in seconds)": "Intervalo de descarga (en segundos)", 51 | "Background options": "Opciones de fondo", 52 | "Use background effect": "Usar efecto de fondo", 53 | "Background effect": "efecto de fondo", 54 | "See all effects": "Ver todos los efectos", 55 | "Number format": "Formato numérico", 56 | "Own position for each app": "Posición propia para cada aplicación.", 57 | "Position": "Posición", 58 | "Expert Apps": "Aplicaciones expertas", 59 | "Username": "Nombre de usuario", 60 | "Password": "Contraseña", 61 | "Screen": "Pantalla", 62 | "Round dynamically": "Redondear dinámicamente", 63 | "Adjust digits to avoid scrolling": "Ajusta los dígitos para evitar el desplazamiento", 64 | "Display as": "Mostrar como", 65 | "Line": "Línea", 66 | "Bar": "Bar", 67 | "Mode": "Modo", 68 | "Last values": "Últimos valores", 69 | "Aggregated values": "Valores agregados", 70 | "Aggregation": "Agregación", 71 | "Average": "Promedio", 72 | "Min": "mín.", 73 | "Max": "máx.", 74 | "Count": "Contar", 75 | "Step size": "Numero de pie", 76 | "seconds": "segundos", 77 | "Activate same apps as in main instance": "Activa las mismas aplicaciones que en la instancia principal." 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Choix", 3 | "IP address of your Awtrix Light device": "Adresse IP de votre appareil Awtrix Light", 4 | "Apps": "applications", 5 | "Custom Apps": "Applications personnalisées", 6 | "Automatically delete unknown apps": "Supprimer automatiquement les applications inconnues", 7 | "Ignore new values after state change (in seconds)": "Ignorer les nouvelles valeurs après changement d'état (en secondes)", 8 | "Prevents too many requests for rapid state changes": "Empêche trop de demandes de changements d'état rapides", 9 | "Background color": "Couleur de l'arrière plan", 10 | "Refresh interval (in seconds)": "Intervalle d'actualisation (en secondes)", 11 | "Expert options": "Options expertes", 12 | "HTTP timeout": "Délai d'attente HTTP", 13 | "Name": "Nom", 14 | "Just lower case letters (a-z)": "Juste des lettres minuscules (a-z)", 15 | "Icon": "Icône", 16 | "Must be uploaded manually": "Doit être téléchargé manuellement", 17 | "Duration (sec)": "Durée (sec)", 18 | "Repetitions": "Répétitions", 19 | "Text": "Texte", 20 | "%s will be replaced by value and %u by unit": "%s sera remplacé par la valeur et %u par l'unité", 21 | "Remove object or add %s (and %u) to text": "Supprimer l'objet ou ajouter %s (et %u) au texte", 22 | "Object": "Objet", 23 | "Should be type of string or number": "Doit être un type de chaîne ou un nombre", 24 | "Select object or remove %s and %u from text": "Sélectionnez un objet ou supprimez %s et %u du texte", 25 | "Text color": "Couleur du texte", 26 | "Decimals": "Décimales", 27 | "Numeric values will be rounded": "Les valeurs numériques seront arrondies", 28 | "Text in rainbow colors": "Texte aux couleurs de l'arc-en-ciel", 29 | "Disable text scrolling": "Désactiver le défilement du texte", 30 | "Scroll speed": "Vitesse de défilement", 31 | "Source instance": "Instance source", 32 | "Logging must be enabled for the selected ID": "La journalisation doit être activée pour l'ID sélectionné", 33 | "Define a valid source for history data": "Définir une source valide pour les données d'historique", 34 | "Select data source": "Sélectionnez la source de données", 35 | "Color": "Couleur", 36 | "Line color of chart": "Couleur des lignes du graphique", 37 | "Delete apps when instance is stopped": "Supprimer les applications lorsque l'instance est arrêtée", 38 | "0 = default": "0 = par défaut", 39 | "Use settings of other instance": "Utiliser les paramètres d'une autre instance", 40 | "Copy app settings of": "Copier les paramètres de l'application de", 41 | "Select another instance": "Sélectionnez une autre instance", 42 | "Text options": "Options de texte", 43 | "Threshold overrides (type of state must be number)": "Remplacements de seuil (le type d'état doit être un nombre)", 44 | "If value less than": "Si valeur inférieure à", 45 | "If value greater than": "Si valeur supérieure à", 46 | "Set icon, text color or background color override": "Définir l'icône, la couleur du texte ou le remplacement de la couleur d'arrière-plan", 47 | "Connection": "Connexion", 48 | "Support this project": "Soutenez ce projet", 49 | "Download screen content": "Télécharger le contenu de l'écran", 50 | "Download interval (in seconds)": "Intervalle de téléchargement (en secondes)", 51 | "Background options": "Options d'arrière-plan", 52 | "Use background effect": "Utiliser l'effet de fond", 53 | "Background effect": "Effet de fond", 54 | "See all effects": "Voir tous les effets", 55 | "Number format": "Format de nombre", 56 | "Own position for each app": "Propre position pour chaque application", 57 | "Position": "Position", 58 | "Expert Apps": "Applications expertes", 59 | "Username": "Nom d'utilisateur", 60 | "Password": "Mot de passe", 61 | "Screen": "Écran", 62 | "Round dynamically": "Arrondir dynamiquement", 63 | "Adjust digits to avoid scrolling": "Ajustez les chiffres pour éviter le défilement", 64 | "Display as": "Afficher comme", 65 | "Line": "Doubler", 66 | "Bar": "Bar", 67 | "Mode": "Mode", 68 | "Last values": "Dernières valeurs", 69 | "Aggregated values": "Valeurs agrégées", 70 | "Aggregation": "Agrégation", 71 | "Average": "Moyenne", 72 | "Min": "Min.", 73 | "Max": "Max.", 74 | "Count": "Compter", 75 | "Step size": "Taille de pas", 76 | "seconds": "secondes", 77 | "Activate same apps as in main instance": "Activez les mêmes applications que dans l'instance principale" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/it/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Opzioni", 3 | "IP address of your Awtrix Light device": "Indirizzo IP del dispositivo Awtrix Light", 4 | "Apps": "App", 5 | "Custom Apps": "App personalizzate", 6 | "Automatically delete unknown apps": "Elimina automaticamente le app sconosciute", 7 | "Ignore new values after state change (in seconds)": "Ignora i nuovi valori dopo il cambio di stato (in secondi)", 8 | "Prevents too many requests for rapid state changes": "Impedisce troppe richieste di rapidi cambiamenti di stato", 9 | "Background color": "Colore di sfondo", 10 | "Refresh interval (in seconds)": "Intervallo di aggiornamento (in secondi)", 11 | "Expert options": "Opzioni per esperti", 12 | "HTTP timeout": "Timeout HTTP", 13 | "Name": "Nome", 14 | "Just lower case letters (a-z)": "Solo lettere minuscole (a-z)", 15 | "Icon": "Icona", 16 | "Must be uploaded manually": "Deve essere caricato manualmente", 17 | "Duration (sec)": "Durata (sec)", 18 | "Repetitions": "Ripetizioni", 19 | "Text": "Testo", 20 | "%s will be replaced by value and %u by unit": "%s verrà sostituito dal valore e %u dall'unità", 21 | "Remove object or add %s (and %u) to text": "Rimuovi oggetto o aggiungi %s (e %u) al testo", 22 | "Object": "Oggetto", 23 | "Should be type of string or number": "Dovrebbe essere un tipo di stringa o un numero", 24 | "Select object or remove %s and %u from text": "Seleziona l'oggetto o rimuovi %s e %u dal testo", 25 | "Text color": "Colore del testo", 26 | "Decimals": "Decimali", 27 | "Numeric values will be rounded": "I valori numerici verranno arrotondati", 28 | "Text in rainbow colors": "Testo nei colori dell'arcobaleno", 29 | "Disable text scrolling": "Disattiva lo scorrimento del testo", 30 | "Scroll speed": "Velocità di scorrimento", 31 | "Source instance": "Istanza di origine", 32 | "Logging must be enabled for the selected ID": "La registrazione deve essere abilitata per l'ID selezionato", 33 | "Define a valid source for history data": "Definire un'origine valida per i dati cronologici", 34 | "Select data source": "Seleziona l'origine dati", 35 | "Color": "Colore", 36 | "Line color of chart": "Colore della linea del grafico", 37 | "Delete apps when instance is stopped": "Elimina le app quando l'istanza viene arrestata", 38 | "0 = default": "0 = predefinito", 39 | "Use settings of other instance": "Utilizza le impostazioni di un'altra istanza", 40 | "Copy app settings of": "Copia le impostazioni dell'app di", 41 | "Select another instance": "Seleziona un'altra istanza", 42 | "Text options": "Opzioni di testo", 43 | "Threshold overrides (type of state must be number)": "Soglie sostitutive (il tipo di stato deve essere un numero)", 44 | "If value less than": "Se il valore è inferiore a", 45 | "If value greater than": "Se il valore è maggiore di", 46 | "Set icon, text color or background color override": "Imposta l'icona, il colore del testo o la sostituzione del colore di sfondo", 47 | "Connection": "Connessione", 48 | "Support this project": "Sostieni questo progetto", 49 | "Download screen content": "Scarica il contenuto dello schermo", 50 | "Download interval (in seconds)": "Intervallo di download (in secondi)", 51 | "Background options": "Opzioni di sfondo", 52 | "Use background effect": "Usa l'effetto di sfondo", 53 | "Background effect": "Effetto sfondo", 54 | "See all effects": "Vedi tutti gli effetti", 55 | "Number format": "Formato numerico", 56 | "Own position for each app": "Propria posizione per ciascuna app", 57 | "Position": "Posizione", 58 | "Expert Apps": "App per esperti", 59 | "Username": "Nome utente", 60 | "Password": "Parola d'ordine", 61 | "Screen": "Schermo", 62 | "Round dynamically": "Arrotondare dinamicamente", 63 | "Adjust digits to avoid scrolling": "Regolare le cifre per evitare lo scorrimento", 64 | "Display as": "Mostra come", 65 | "Line": "Linea", 66 | "Bar": "Sbarra", 67 | "Mode": "Modalità", 68 | "Last values": "Ultimi valori", 69 | "Aggregated values": "Valori aggregati", 70 | "Aggregation": "Aggregazione", 71 | "Average": "Media", 72 | "Min": "minimo", 73 | "Max": "Massimo", 74 | "Count": "Contare", 75 | "Step size": "Dimensione del passo", 76 | "seconds": "secondi", 77 | "Activate same apps as in main instance": "Attiva le stesse app dell'istanza principale" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/nl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Opties", 3 | "IP address of your Awtrix Light device": "IP-adres van uw Awtrix Light-apparaat", 4 | "Apps": "Apps", 5 | "Custom Apps": "Aangepaste apps", 6 | "Automatically delete unknown apps": "Verwijder onbekende apps automatisch", 7 | "Ignore new values after state change (in seconds)": "Negeer nieuwe waarden na statusverandering (in seconden)", 8 | "Prevents too many requests for rapid state changes": "Voorkomt te veel verzoeken om snelle statusveranderingen", 9 | "Background color": "Achtergrond kleur", 10 | "Refresh interval (in seconds)": "Verversingsinterval (in seconden)", 11 | "Expert options": "Deskundige opties", 12 | "HTTP timeout": "HTTP-time-out", 13 | "Name": "Naam", 14 | "Just lower case letters (a-z)": "Gewoon kleine letters (a-z)", 15 | "Icon": "Icoon", 16 | "Must be uploaded manually": "Moet handmatig worden geüpload", 17 | "Duration (sec)": "Duur (seconden)", 18 | "Repetitions": "Herhalingen", 19 | "Text": "Tekst", 20 | "%s will be replaced by value and %u by unit": "%s wordt vervangen door waarde en %u door eenheid", 21 | "Remove object or add %s (and %u) to text": "Verwijder object of voeg %s (en %u) toe aan tekst", 22 | "Object": "Voorwerp", 23 | "Should be type of string or number": "Moet type tekenreeks of getal zijn", 24 | "Select object or remove %s and %u from text": "Selecteer object of verwijder %s en %u uit tekst", 25 | "Text color": "Tekst kleur", 26 | "Decimals": "decimalen", 27 | "Numeric values will be rounded": "Numerieke waarden worden afgerond", 28 | "Text in rainbow colors": "Tekst in regenboogkleuren", 29 | "Disable text scrolling": "Tekst scrollen uitschakelen", 30 | "Scroll speed": "Scrollsnelheid", 31 | "Source instance": "Broninstantie", 32 | "Logging must be enabled for the selected ID": "Logboekregistratie moet zijn ingeschakeld voor de geselecteerde ID", 33 | "Define a valid source for history data": "Definieer een geldige bron voor historische gegevens", 34 | "Select data source": "Selecteer gegevensbron", 35 | "Color": "Kleur", 36 | "Line color of chart": "Lijnkleur van de grafiek", 37 | "Delete apps when instance is stopped": "Verwijder apps wanneer de instantie is gestopt", 38 | "0 = default": "0 = standaard", 39 | "Use settings of other instance": "Gebruik instellingen van een andere instantie", 40 | "Copy app settings of": "Kopieer app-instellingen van", 41 | "Select another instance": "Selecteer een andere instantie", 42 | "Text options": "Tekst opties", 43 | "Threshold overrides (type of state must be number)": "Drempeloverschrijdingen (type status moet getal zijn)", 44 | "If value less than": "Indien waarde kleiner dan", 45 | "If value greater than": "Als de waarde groter is dan", 46 | "Set icon, text color or background color override": "Stel pictogram, tekstkleur of achtergrondkleur override in", 47 | "Connection": "Verbinding", 48 | "Support this project": "Steun dit project", 49 | "Download screen content": "Scherminhoud downloaden", 50 | "Download interval (in seconds)": "Downloadinterval (in seconden)", 51 | "Background options": "Achtergrond opties", 52 | "Use background effect": "Gebruik een achtergrondeffect", 53 | "Background effect": "Achtergrondeffect", 54 | "See all effects": "Bekijk alle effecten", 55 | "Number format": "Nummer formaat", 56 | "Own position for each app": "Eigen positie voor elke app", 57 | "Position": "Positie", 58 | "Expert Apps": "Deskundige apps", 59 | "Username": "Gebruikersnaam", 60 | "Password": "Wachtwoord", 61 | "Screen": "Scherm", 62 | "Round dynamically": "Dynamisch rond", 63 | "Adjust digits to avoid scrolling": "Pas cijfers aan om scrollen te voorkomen", 64 | "Display as": "Weergeven als", 65 | "Line": "Lijn", 66 | "Bar": "Bar", 67 | "Mode": "Modus", 68 | "Last values": "Laatste waarden", 69 | "Aggregated values": "Geaggregeerde waarden", 70 | "Aggregation": "Aggregatie", 71 | "Average": "Gemiddeld", 72 | "Min": "Min", 73 | "Max": "Max", 74 | "Count": "Graaf", 75 | "Step size": "Stapgrootte", 76 | "seconds": "seconden", 77 | "Activate same apps as in main instance": "Activeer dezelfde apps als in de hoofdinstantie" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/pl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Opcje", 3 | "IP address of your Awtrix Light device": "Adres IP Twojego urządzenia Awtrix Light", 4 | "Apps": "Aplikacje", 5 | "Custom Apps": "Aplikacje niestandardowe", 6 | "Automatically delete unknown apps": "Automatycznie usuwaj nieznane aplikacje", 7 | "Ignore new values after state change (in seconds)": "Ignoruj ​​nowe wartości po zmianie stanu (w sekundach)", 8 | "Prevents too many requests for rapid state changes": "Zapobiega zbyt wielu prośbom o szybkie zmiany stanu", 9 | "Background color": "Kolor tła", 10 | "Refresh interval (in seconds)": "Interwał odświeżania (w sekundach)", 11 | "Expert options": "Opcje eksperta", 12 | "HTTP timeout": "Limit czasu HTTP", 13 | "Name": "Nazwa", 14 | "Just lower case letters (a-z)": "Tylko małe litery (a-z)", 15 | "Icon": "Ikona", 16 | "Must be uploaded manually": "Należy przesłać ręcznie", 17 | "Duration (sec)": "Czas trwania (s)", 18 | "Repetitions": "Powtórzenia", 19 | "Text": "Tekst", 20 | "%s will be replaced by value and %u by unit": "%s zostanie zastąpione wartością, a %u jednostką", 21 | "Remove object or add %s (and %u) to text": "Usuń obiekt lub dodaj %s (i %u) do tekstu", 22 | "Object": "Obiekt", 23 | "Should be type of string or number": "Powinien być typem ciągu lub liczby", 24 | "Select object or remove %s and %u from text": "Wybierz obiekt lub usuń %s i %u z tekstu", 25 | "Text color": "Kolor tekstu", 26 | "Decimals": "dziesiętne", 27 | "Numeric values will be rounded": "Wartości liczbowe zostaną zaokrąglone", 28 | "Text in rainbow colors": "Tekst w kolorach tęczy", 29 | "Disable text scrolling": "Wyłącz przewijanie tekstu", 30 | "Scroll speed": "Prędkość przewijania", 31 | "Source instance": "Instancja źródłowa", 32 | "Logging must be enabled for the selected ID": "Logowanie musi być włączone dla wybranego identyfikatora", 33 | "Define a valid source for history data": "Zdefiniuj prawidłowe źródło danych historii", 34 | "Select data source": "Wybierz źródło danych", 35 | "Color": "Kolor", 36 | "Line color of chart": "Kolor linii wykresu", 37 | "Delete apps when instance is stopped": "Usuń aplikacje, gdy instancja jest zatrzymana", 38 | "0 = default": "0 = domyślna", 39 | "Use settings of other instance": "Użyj ustawień innej instancji", 40 | "Copy app settings of": "Skopiuj ustawienia aplikacji z", 41 | "Select another instance": "Wybierz inną instancję", 42 | "Text options": "Opcje tekstu", 43 | "Threshold overrides (type of state must be number)": "Nadpisania wartości progowych (rodzaj stanu musi być liczbą)", 44 | "If value less than": "Jeśli wartość jest mniejsza niż", 45 | "If value greater than": "Jeśli wartość jest większa niż", 46 | "Set icon, text color or background color override": "Ustaw ikonę, kolor tekstu lub nadpisanie koloru tła", 47 | "Connection": "Połączenie", 48 | "Support this project": "Wesprzyj ten projekt", 49 | "Download screen content": "Pobierz zawartość ekranu", 50 | "Download interval (in seconds)": "Interwał pobierania (w sekundach)", 51 | "Background options": "Opcje tła", 52 | "Use background effect": "Użyj efektu tła", 53 | "Background effect": "Efekt tła", 54 | "See all effects": "Zobacz wszystkie efekty", 55 | "Number format": "Format liczbowy", 56 | "Own position for each app": "Własne stanowisko dla każdej aplikacji", 57 | "Position": "Pozycja", 58 | "Expert Apps": "Aplikacje eksperckie", 59 | "Username": "Nazwa użytkownika", 60 | "Password": "Hasło", 61 | "Screen": "Ekran", 62 | "Round dynamically": "Zaokrąglaj dynamicznie", 63 | "Adjust digits to avoid scrolling": "Dostosuj cyfry, aby uniknąć przewijania", 64 | "Display as": "Wyświetl jako", 65 | "Line": "Linia", 66 | "Bar": "Bar", 67 | "Mode": "Tryb", 68 | "Last values": "Ostatnie wartości", 69 | "Aggregated values": "Zagregowane wartości", 70 | "Aggregation": "Zbiór", 71 | "Average": "Przeciętny", 72 | "Min": "Min", 73 | "Max": "Maks", 74 | "Count": "Liczyć", 75 | "Step size": "Rozmiar kroku", 76 | "seconds": "sekundy", 77 | "Activate same apps as in main instance": "Aktywuj te same aplikacje, co w instancji głównej" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/pt/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Opções", 3 | "IP address of your Awtrix Light device": "Endereço IP do seu dispositivo Awtrix Light", 4 | "Apps": "aplicativos", 5 | "Custom Apps": "Aplicativos personalizados", 6 | "Automatically delete unknown apps": "Excluir automaticamente aplicativos desconhecidos", 7 | "Ignore new values after state change (in seconds)": "Ignorar novos valores após mudança de estado (em segundos)", 8 | "Prevents too many requests for rapid state changes": "Evita muitas solicitações para mudanças rápidas de estado", 9 | "Background color": "Cor de fundo", 10 | "Refresh interval (in seconds)": "Intervalo de atualização (em segundos)", 11 | "Expert options": "Opções de especialistas", 12 | "HTTP timeout": "tempo limite HTTP", 13 | "Name": "Nome", 14 | "Just lower case letters (a-z)": "Apenas letras minúsculas (a-z)", 15 | "Icon": "Ícone", 16 | "Must be uploaded manually": "Deve ser carregado manualmente", 17 | "Duration (sec)": "Duração (seg)", 18 | "Repetitions": "Repetições", 19 | "Text": "Texto", 20 | "%s will be replaced by value and %u by unit": "%s será substituído por valor e %u por unidade", 21 | "Remove object or add %s (and %u) to text": "Remova o objeto ou adicione %s (e %u) ao texto", 22 | "Object": "Objeto", 23 | "Should be type of string or number": "Deve ser o tipo de string ou número", 24 | "Select object or remove %s and %u from text": "Selecione o objeto ou remova %s e %u do texto", 25 | "Text color": "Cor do texto", 26 | "Decimals": "decimais", 27 | "Numeric values will be rounded": "Valores numéricos serão arredondados", 28 | "Text in rainbow colors": "Texto em cores do arco-íris", 29 | "Disable text scrolling": "Desativar rolagem de texto", 30 | "Scroll speed": "Velocidade de rolamento", 31 | "Source instance": "Instância de origem", 32 | "Logging must be enabled for the selected ID": "O registro deve ser ativado para o ID selecionado", 33 | "Define a valid source for history data": "Defina uma fonte válida para dados históricos", 34 | "Select data source": "Selecionar fonte de dados", 35 | "Color": "Cor", 36 | "Line color of chart": "Cor da linha do gráfico", 37 | "Delete apps when instance is stopped": "Excluir aplicativos quando a instância for interrompida", 38 | "0 = default": "0 = padrão", 39 | "Use settings of other instance": "Usar configurações de outra instância", 40 | "Copy app settings of": "Copie as configurações do aplicativo de", 41 | "Select another instance": "Selecione outra instância", 42 | "Text options": "opções de texto", 43 | "Threshold overrides (type of state must be number)": "Substituições de limite (o tipo de estado deve ser número)", 44 | "If value less than": "Se valor menor que", 45 | "If value greater than": "Se valor maior que", 46 | "Set icon, text color or background color override": "Definir ícone, cor de texto ou substituição de cor de fundo", 47 | "Connection": "Conexão", 48 | "Support this project": "Apoie este projeto", 49 | "Download screen content": "Baixe o conteúdo da tela", 50 | "Download interval (in seconds)": "Intervalo de download (em segundos)", 51 | "Background options": "Opções de plano de fundo", 52 | "Use background effect": "Usar efeito de fundo", 53 | "Background effect": "efeito de fundo", 54 | "See all effects": "Ver todos os efeitos", 55 | "Number format": "Formato numérico", 56 | "Own position for each app": "Posição própria para cada aplicativo", 57 | "Position": "Posição", 58 | "Expert Apps": "Aplicativos especializados", 59 | "Username": "Nome de usuário", 60 | "Password": "Senha", 61 | "Screen": "Tela", 62 | "Round dynamically": "Arredondar dinamicamente", 63 | "Adjust digits to avoid scrolling": "Ajuste os dígitos para evitar rolagem", 64 | "Display as": "Mostrar como", 65 | "Line": "Linha", 66 | "Bar": "Bar", 67 | "Mode": "Modo", 68 | "Last values": "Últimos valores", 69 | "Aggregated values": "Valores agregados", 70 | "Aggregation": "Agregação", 71 | "Average": "Média", 72 | "Min": "Mínimo", 73 | "Max": "Máx.", 74 | "Count": "Contar", 75 | "Step size": "Tamanho do passo", 76 | "seconds": "segundos", 77 | "Activate same apps as in main instance": "Ative os mesmos aplicativos da instância principal" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Параметры", 3 | "IP address of your Awtrix Light device": "IP-адрес вашего устройства Awtrix Light", 4 | "Apps": "Программы", 5 | "Custom Apps": "Пользовательские приложения", 6 | "Automatically delete unknown apps": "Автоматически удалять неизвестные приложения", 7 | "Ignore new values after state change (in seconds)": "Игнорировать новые значения после изменения состояния (в секундах)", 8 | "Prevents too many requests for rapid state changes": "Предотвращает слишком много запросов на быстрое изменение состояния", 9 | "Background color": "Фоновый цвет", 10 | "Refresh interval (in seconds)": "Интервал обновления (в секундах)", 11 | "Expert options": "Экспертные опции", 12 | "HTTP timeout": "Тайм-аут HTTP", 13 | "Name": "Имя", 14 | "Just lower case letters (a-z)": "Только строчные буквы (a-z)", 15 | "Icon": "Икона", 16 | "Must be uploaded manually": "Должен быть загружен вручную", 17 | "Duration (sec)": "Продолжительность (сек)", 18 | "Repetitions": "Повторы", 19 | "Text": "Текст", 20 | "%s will be replaced by value and %u by unit": "%s будет заменено на значение, а %u на единицу", 21 | "Remove object or add %s (and %u) to text": "Удалить объект или добавить %s (и %u) к тексту", 22 | "Object": "Объект", 23 | "Should be type of string or number": "Должен быть типом строки или числа", 24 | "Select object or remove %s and %u from text": "Выберите объект или удалите %s и %u из текста", 25 | "Text color": "Цвет текста", 26 | "Decimals": "Десятичные", 27 | "Numeric values will be rounded": "Числовые значения будут округлены", 28 | "Text in rainbow colors": "Текст в цветах радуги", 29 | "Disable text scrolling": "Отключить прокрутку текста", 30 | "Scroll speed": "Скорость прокрутки", 31 | "Source instance": "Исходный экземпляр", 32 | "Logging must be enabled for the selected ID": "Ведение журнала должно быть включено для выбранного идентификатора", 33 | "Define a valid source for history data": "Определите действительный источник для данных истории", 34 | "Select data source": "Выберите источник данных", 35 | "Color": "Цвет", 36 | "Line color of chart": "Цвет линии графика", 37 | "Delete apps when instance is stopped": "Удалить приложения, когда экземпляр остановлен", 38 | "0 = default": "0 = по умолчанию", 39 | "Use settings of other instance": "Использовать настройки другого экземпляра", 40 | "Copy app settings of": "Скопируйте настройки приложения из", 41 | "Select another instance": "Выберите другой экземпляр", 42 | "Text options": "Параметры текста", 43 | "Threshold overrides (type of state must be number)": "Переопределение порога (тип состояния должен быть числом)", 44 | "If value less than": "Если значение меньше", 45 | "If value greater than": "Если значение больше, чем", 46 | "Set icon, text color or background color override": "Установка значка, цвета текста или фонового цвета", 47 | "Connection": "Связь", 48 | "Support this project": "Поддержите этот проект", 49 | "Download screen content": "Загрузить содержимое экрана", 50 | "Download interval (in seconds)": "Интервал загрузки (в секундах)", 51 | "Background options": "Параметры фона", 52 | "Use background effect": "Использовать фоновый эффект", 53 | "Background effect": "Фоновый эффект", 54 | "See all effects": "Посмотреть все эффекты", 55 | "Number format": "Формат номера", 56 | "Own position for each app": "Своя позиция для каждого приложения", 57 | "Position": "Позиция", 58 | "Expert Apps": "Экспертные приложения", 59 | "Username": "Имя пользователя", 60 | "Password": "Пароль", 61 | "Screen": "Экран", 62 | "Round dynamically": "Округлять динамически", 63 | "Adjust digits to avoid scrolling": "Отрегулируйте цифры, чтобы избежать прокрутки", 64 | "Display as": "Отображать как", 65 | "Line": "Линия", 66 | "Bar": "Бар", 67 | "Mode": "Режим", 68 | "Last values": "Последние значения", 69 | "Aggregated values": "Агрегированные значения", 70 | "Aggregation": "Агрегация", 71 | "Average": "Средний", 72 | "Min": "Мин", 73 | "Max": "Макс", 74 | "Count": "Считать", 75 | "Step size": "Размер шага", 76 | "seconds": "секунды", 77 | "Activate same apps as in main instance": "Активируйте те же приложения, что и в основном экземпляре." 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/uk/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "Опції", 3 | "IP address of your Awtrix Light device": "IP-адреса вашого пристрою Awtrix Light", 4 | "Apps": "програми", 5 | "Custom Apps": "Спеціальні програми", 6 | "Automatically delete unknown apps": "Автоматично видаляти невідомі програми", 7 | "Ignore new values after state change (in seconds)": "Ігнорувати нові значення після зміни стану (у секундах)", 8 | "Prevents too many requests for rapid state changes": "Запобігає занадто великій кількості запитів на швидку зміну стану", 9 | "Background color": "Колір фону", 10 | "Refresh interval (in seconds)": "Інтервал оновлення (у секундах)", 11 | "Expert options": "Експертні варіанти", 12 | "HTTP timeout": "Час очікування HTTP", 13 | "Name": "Ім'я", 14 | "Just lower case letters (a-z)": "Тільки малі літери (a-z)", 15 | "Icon": "значок", 16 | "Must be uploaded manually": "Потрібно завантажити вручну", 17 | "Duration (sec)": "Тривалість (сек)", 18 | "Repetitions": "повтори", 19 | "Text": "текст", 20 | "%s will be replaced by value and %u by unit": "%s буде замінено на значення, а %u на одиницю", 21 | "Remove object or add %s (and %u) to text": "Видаліть об’єкт або додайте %s (і %u) до тексту", 22 | "Object": "Об'єкт", 23 | "Should be type of string or number": "Має бути тип рядка або число", 24 | "Select object or remove %s and %u from text": "Виберіть об’єкт або видаліть %s і %u з тексту", 25 | "Text color": "Колір тексту", 26 | "Decimals": "Десяткові дроби", 27 | "Numeric values will be rounded": "Числові значення будуть округлені", 28 | "Text in rainbow colors": "Текст у кольорах веселки", 29 | "Disable text scrolling": "Вимкнути прокручування тексту", 30 | "Scroll speed": "Швидкість прокручування", 31 | "Source instance": "Вихідний екземпляр", 32 | "Logging must be enabled for the selected ID": "Для вибраного ідентифікатора має бути ввімкнено журналювання", 33 | "Define a valid source for history data": "Визначте дійсне джерело для даних історії", 34 | "Select data source": "Виберіть джерело даних", 35 | "Color": "колір", 36 | "Line color of chart": "Колір лінії діаграми", 37 | "Delete apps when instance is stopped": "Видалити програми, коли екземпляр зупинено", 38 | "0 = default": "0 = за замовчуванням", 39 | "Use settings of other instance": "Використовуйте налаштування іншого екземпляра", 40 | "Copy app settings of": "Копіювати налаштування програми з", 41 | "Select another instance": "Виберіть інший екземпляр", 42 | "Text options": "Опції тексту", 43 | "Threshold overrides (type of state must be number)": "Перевизначення порогових значень (тип стану має бути числом)", 44 | "If value less than": "Якщо значення менше", 45 | "If value greater than": "Якщо значення більше ніж", 46 | "Set icon, text color or background color override": "Установити піктограму, змінити колір тексту чи фону", 47 | "Connection": "Підключення", 48 | "Support this project": "Підтримайте цей проект", 49 | "Download screen content": "Завантажити вміст екрана", 50 | "Download interval (in seconds)": "Інтервал завантаження (у секундах)", 51 | "Background options": "Варіанти фону", 52 | "Use background effect": "Використовуйте фоновий ефект", 53 | "Background effect": "фоновий ефект", 54 | "See all effects": "Переглянути всі ефекти", 55 | "Number format": "Числовий формат", 56 | "Own position for each app": "Власна позиція для кожного додатка", 57 | "Position": "Позиція", 58 | "Expert Apps": "Експертні програми", 59 | "Username": "Ім'я користувача", 60 | "Password": "Пароль", 61 | "Screen": "Екран", 62 | "Round dynamically": "Округлення динамічно", 63 | "Adjust digits to avoid scrolling": "Налаштуйте цифри, щоб уникнути прокручування", 64 | "Display as": "Відображати як", 65 | "Line": "лінія", 66 | "Bar": "Бар", 67 | "Mode": "Режим", 68 | "Last values": "Останні значення", 69 | "Aggregated values": "Агреговані значення", 70 | "Aggregation": "Агрегація", 71 | "Average": "Середній", 72 | "Min": "Хв", 73 | "Max": "Макс", 74 | "Count": "Рахувати", 75 | "Step size": "Розмір кроку", 76 | "seconds": "секунд", 77 | "Activate same apps as in main instance": "Активуйте ті самі програми, що й у головному екземплярі" 78 | } 79 | -------------------------------------------------------------------------------- /admin/i18n/zh-cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Options": "选项", 3 | "IP address of your Awtrix Light device": "您的 Awtrix Light 设备的 IP 地址", 4 | "Apps": "应用", 5 | "Custom Apps": "定制应用", 6 | "Automatically delete unknown apps": "自动删除未知应用", 7 | "Ignore new values after state change (in seconds)": "状态更改后忽略新值(以秒为单位)", 8 | "Prevents too many requests for rapid state changes": "防止对快速状态更改的过多请求", 9 | "Background color": "背景颜色", 10 | "Refresh interval (in seconds)": "刷新间隔(以秒为单位)", 11 | "Expert options": "专家选项", 12 | "HTTP timeout": "HTTP 超时", 13 | "Name": "姓名", 14 | "Just lower case letters (a-z)": "仅小写字母 (a-z)", 15 | "Icon": "图标", 16 | "Must be uploaded manually": "必须手动上传", 17 | "Duration (sec)": "持续时间(秒)", 18 | "Repetitions": "重复次数", 19 | "Text": "文本", 20 | "%s will be replaced by value and %u by unit": "%s 将被值替换,%u 将被单位替换", 21 | "Remove object or add %s (and %u) to text": "删除对象或将 %s(和 %u)添加到文本", 22 | "Object": "目的", 23 | "Should be type of string or number": "应该是字符串或数字类型", 24 | "Select object or remove %s and %u from text": "选择对象或从文本中删除 %s 和 %u", 25 | "Text color": "文字颜色", 26 | "Decimals": "小数点", 27 | "Numeric values will be rounded": "数值将被四舍五入", 28 | "Text in rainbow colors": "彩虹色的文字", 29 | "Disable text scrolling": "禁用文本滚动", 30 | "Scroll speed": "滚动速度", 31 | "Source instance": "源实例", 32 | "Logging must be enabled for the selected ID": "必须为所选 ID 启用日志记录", 33 | "Define a valid source for history data": "定义历史数据的有效来源", 34 | "Select data source": "选择数据源", 35 | "Color": "颜色", 36 | "Line color of chart": "图表的线条颜色", 37 | "Delete apps when instance is stopped": "实例停止时删除应用程序", 38 | "0 = default": "0 = 默认", 39 | "Use settings of other instance": "使用其他实例的设置", 40 | "Copy app settings of": "复制应用程序设置", 41 | "Select another instance": "选择另一个实例", 42 | "Text options": "文本选项", 43 | "Threshold overrides (type of state must be number)": "阈值覆盖(状态类型必须是数字)", 44 | "If value less than": "如果值小于", 45 | "If value greater than": "如果值大于", 46 | "Set icon, text color or background color override": "设置图标、文本颜色或背景颜色覆盖", 47 | "Connection": "联系", 48 | "Support this project": "支持这个项目", 49 | "Download screen content": "下载屏幕内容", 50 | "Download interval (in seconds)": "下载间隔(秒)", 51 | "Background options": "背景选项", 52 | "Use background effect": "使用背景效果", 53 | "Background effect": "背景效果", 54 | "See all effects": "查看所有效果", 55 | "Number format": "数字格式", 56 | "Own position for each app": "每个应用程序都有自己的位置", 57 | "Position": "位置", 58 | "Expert Apps": "专家应用程序", 59 | "Username": "用户名", 60 | "Password": "密码", 61 | "Screen": "屏幕", 62 | "Round dynamically": "动态舍入", 63 | "Adjust digits to avoid scrolling": "调整数字以避免滚动", 64 | "Display as": "展示为", 65 | "Line": "线", 66 | "Bar": "酒吧", 67 | "Mode": "模式", 68 | "Last values": "最后值", 69 | "Aggregated values": "合计值", 70 | "Aggregation": "聚合", 71 | "Average": "平均的", 72 | "Min": "最小", 73 | "Max": "最大限度", 74 | "Count": "数数", 75 | "Step size": "一步的大小", 76 | "seconds": "秒", 77 | "Activate same apps as in main instance": "激活与主实例中相同的应用程序" 78 | } 79 | -------------------------------------------------------------------------------- /build/lib/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __create = Object.create; 3 | var __defProp = Object.defineProperty; 4 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 5 | var __getOwnPropNames = Object.getOwnPropertyNames; 6 | var __getProtoOf = Object.getPrototypeOf; 7 | var __hasOwnProp = Object.prototype.hasOwnProperty; 8 | var __export = (target, all) => { 9 | for (var name in all) 10 | __defProp(target, name, { get: all[name], enumerable: true }); 11 | }; 12 | var __copyProps = (to, from, except, desc) => { 13 | if (from && typeof from === "object" || typeof from === "function") { 14 | for (let key of __getOwnPropNames(from)) 15 | if (!__hasOwnProp.call(to, key) && key !== except) 16 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 17 | } 18 | return to; 19 | }; 20 | var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 21 | // If the importer is in node compatibility mode or this is not an ESM 22 | // file that has been converted to a CommonJS file using a Babel- 23 | // compatible transform (i.e. "__esModule" has not been set), then set 24 | // "default" to the CommonJS "module.exports" for node compatibility. 25 | isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 26 | mod 27 | )); 28 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 29 | var api_exports = {}; 30 | __export(api_exports, { 31 | AwtrixApi: () => AwtrixApi 32 | }); 33 | module.exports = __toCommonJS(api_exports); 34 | var import_axios = __toESM(require("axios")); 35 | var AwtrixApi; 36 | ((AwtrixApi2) => { 37 | class Client { 38 | adapter; 39 | axiosInstance = void 0; 40 | apiConnected = false; 41 | lastErrorCode = -1; 42 | constructor(adapter, ipAddress, port, httpTimeout, userName, userPassword) { 43 | this.adapter = adapter; 44 | this.adapter.log.info(`Starting - connecting to http://${ipAddress}:${port}/`); 45 | let httpAuth = void 0; 46 | if (userName) { 47 | httpAuth = { 48 | username: userName, 49 | password: userPassword 50 | }; 51 | } 52 | this.axiosInstance = import_axios.default.create({ 53 | baseURL: `http://${ipAddress}:${port}/api/`, 54 | timeout: httpTimeout * 1e3 || 3e3, 55 | auth: httpAuth, 56 | validateStatus: (status) => { 57 | return [200, 201].indexOf(status) > -1; 58 | }, 59 | responseType: "json" 60 | }); 61 | } 62 | isConnected() { 63 | return this.apiConnected; 64 | } 65 | async getStatsAsync() { 66 | return new Promise((resolve, reject) => { 67 | this.requestAsync("stats", "GET").then(async (response) => { 68 | if (response.status === 200) { 69 | this.apiConnected = true; 70 | resolve(response.data); 71 | } else { 72 | reject(response); 73 | } 74 | }).catch((error) => { 75 | this.apiConnected = false; 76 | reject(error); 77 | }); 78 | }); 79 | } 80 | async removeAppAsync(name) { 81 | return new Promise((resolve, reject) => { 82 | if (this.apiConnected) { 83 | this.appRequestAsync(name).then((response) => { 84 | if (response.status === 200 && response.data === "OK") { 85 | this.adapter.log.debug(`[removeApp] Removed customApp app "${name}"`); 86 | resolve(true); 87 | } else { 88 | reject(`${response.status}: ${response.data}`); 89 | } 90 | }).catch(reject); 91 | } else { 92 | reject("API not connected"); 93 | } 94 | }); 95 | } 96 | async settingsRequestAsync(data) { 97 | return this.requestAsync("settings", "POST", { [data.key]: data.value }); 98 | } 99 | async indicatorRequestAsync(index, data) { 100 | return this.requestAsync(`indicator${index}`, "POST", data); 101 | } 102 | async appRequestAsync(name, data) { 103 | return this.requestAsync(`custom?name=${name}`, "POST", data); 104 | } 105 | async requestAsync(url, method, data) { 106 | return new Promise((resolve, reject) => { 107 | if (data) { 108 | this.adapter.log.debug(`sending "${method}" request to "${url}" with data: ${JSON.stringify(data)}`); 109 | } else { 110 | this.adapter.log.debug(`sending "${method}" request to "${url}" without data`); 111 | } 112 | this.axiosInstance.request({ 113 | url, 114 | method, 115 | data, 116 | headers: { 117 | "Content-Type": typeof data === "string" ? "text/plain" : "application/json" 118 | } 119 | }).then((response) => { 120 | this.adapter.log.debug(`received ${response.status} response from "${url}" with content: ${JSON.stringify(response.data)}`); 121 | this.lastErrorCode = -1; 122 | resolve(response); 123 | }).catch((error) => { 124 | if (error.response) { 125 | if (error.response.status === 401) { 126 | this.adapter.log.warn("Unable to perform request. Looks like the device is protected with username / password. Check instance configuration!"); 127 | } else { 128 | this.adapter.log.warn(`received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`); 129 | } 130 | } else if (error.request) { 131 | if (error.code === this.lastErrorCode) { 132 | this.adapter.log.debug(error.message); 133 | } else { 134 | this.adapter.log.info(`error ${error.code} from ${url}: ${error.message}`); 135 | this.lastErrorCode = error.code; 136 | } 137 | } else { 138 | this.adapter.log.error(error.message); 139 | } 140 | reject(error); 141 | }); 142 | }); 143 | } 144 | } 145 | AwtrixApi2.Client = Client; 146 | })(AwtrixApi || (AwtrixApi = {})); 147 | // Annotate the CommonJS export names for ESM import in node: 148 | 0 && (module.exports = { 149 | AwtrixApi 150 | }); 151 | //# sourceMappingURL=api.js.map 152 | -------------------------------------------------------------------------------- /build/lib/api.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../src/lib/api.ts"], 4 | "sourcesContent": ["import axios, { AxiosInstance, AxiosResponse } from 'axios';\nimport { AwtrixLight } from '../main';\n\nexport namespace AwtrixApi {\n export type App = {\n text?: string;\n textCase?: number;\n topText?: boolean;\n textOffset?: number;\n center?: boolean;\n color?: string;\n gradient?: string;\n blinkText?: number;\n fadeText?: number;\n background?: string;\n rainbow?: boolean;\n icon?: string;\n pushIcon?: number;\n repeat?: number;\n duration?: number;\n bar?: Array;\n line?: Array;\n autoscale?: boolean;\n progress?: number;\n progressC?: string;\n progressBC?: string;\n pos?: number;\n draw?: Array;\n lifetime?: number;\n lifetimeMode?: number;\n noScroll?: boolean;\n scrollSpeed?: number;\n effect?: string;\n effectSettings?: Array;\n save?: boolean;\n };\n\n export type Settings = {\n key: string;\n value: any;\n };\n\n export type Indicator = {\n color?: string;\n blink?: number;\n fade?: number;\n };\n\n export type Moodlight = {\n brightness?: number;\n color?: string;\n };\n\n export class Client {\n private adapter: AwtrixLight;\n private axiosInstance: AxiosInstance | undefined = undefined;\n private apiConnected: boolean = false;\n private lastErrorCode: number = -1;\n\n public constructor(adapter: AwtrixLight, ipAddress: string, port: number, httpTimeout: number, userName: string, userPassword: string) {\n this.adapter = adapter;\n\n this.adapter.log.info(`Starting - connecting to http://${ipAddress}:${port}/`);\n\n let httpAuth: axios.AxiosBasicCredentials | undefined = undefined;\n if (userName) {\n httpAuth = {\n username: userName,\n password: userPassword,\n };\n }\n\n this.axiosInstance = axios.create({\n baseURL: `http://${ipAddress}:${port}/api/`,\n timeout: httpTimeout * 1000 || 3000,\n auth: httpAuth,\n validateStatus: (status) => {\n return [200, 201].indexOf(status) > -1;\n },\n responseType: 'json',\n });\n }\n\n public isConnected(): boolean {\n return this.apiConnected;\n }\n\n public async getStatsAsync(): Promise {\n return new Promise((resolve, reject) => {\n this.requestAsync('stats', 'GET')\n .then(async (response) => {\n if (response.status === 200) {\n this.apiConnected = true;\n resolve(response.data);\n } else {\n reject(response);\n }\n })\n .catch((error) => {\n this.apiConnected = false;\n reject(error);\n });\n });\n }\n\n public async removeAppAsync(name: string): Promise {\n return new Promise((resolve, reject) => {\n if (this.apiConnected) {\n this.appRequestAsync(name)\n .then((response) => {\n if (response.status === 200 && response.data === 'OK') {\n this.adapter.log.debug(`[removeApp] Removed customApp app \"${name}\"`);\n resolve(true);\n } else {\n reject(`${response.status}: ${response.data}`);\n }\n })\n .catch(reject);\n } else {\n reject('API not connected');\n }\n });\n }\n\n public async settingsRequestAsync(data: AwtrixApi.Settings): Promise {\n return this.requestAsync('settings', 'POST', { [data.key]: data.value });\n }\n\n public async indicatorRequestAsync(index: number, data?: AwtrixApi.Indicator): Promise {\n return this.requestAsync(`indicator${index}`, 'POST', data);\n }\n\n public async appRequestAsync(name: string, data?: AwtrixApi.App): Promise {\n return this.requestAsync(`custom?name=${name}`, 'POST', data);\n }\n\n public async requestAsync(url: string, method?: string, data?: object | string): Promise {\n return new Promise((resolve, reject) => {\n if (data) {\n this.adapter.log.debug(`sending \"${method}\" request to \"${url}\" with data: ${JSON.stringify(data)}`);\n } else {\n this.adapter.log.debug(`sending \"${method}\" request to \"${url}\" without data`);\n }\n\n this.axiosInstance!.request({\n url,\n method,\n data,\n headers: {\n 'Content-Type': typeof data === 'string' ? 'text/plain' : 'application/json',\n },\n })\n .then((response) => {\n this.adapter.log.debug(`received ${response.status} response from \"${url}\" with content: ${JSON.stringify(response.data)}`);\n\n // no error - clear up reminder\n this.lastErrorCode = -1;\n\n resolve(response);\n })\n .catch((error) => {\n if (error.response) {\n // The request was made and the server responded with a status code\n\n if (error.response.status === 401) {\n this.adapter.log.warn('Unable to perform request. Looks like the device is protected with username / password. Check instance configuration!');\n } else {\n this.adapter.log.warn(`received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`);\n }\n } else if (error.request) {\n // The request was made but no response was received\n // `error.request` is an instance of XMLHttpRequest in the browser and an instance of\n // http.ClientRequest in node.js\n\n // avoid spamming of the same error when stuck in a reconnection loop\n if (error.code === this.lastErrorCode) {\n this.adapter.log.debug(error.message);\n } else {\n this.adapter.log.info(`error ${error.code} from ${url}: ${error.message}`);\n this.lastErrorCode = error.code;\n }\n } else {\n // Something happened in setting up the request that triggered an Error\n this.adapter.log.error(error.message);\n }\n\n reject(error);\n });\n });\n }\n }\n}\n"], 5 | "mappings": "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", 6 | "names": ["AwtrixApi", "axios"] 7 | } 8 | -------------------------------------------------------------------------------- /build/lib/app-type/abstract.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | var abstract_exports = {}; 20 | __export(abstract_exports, { 21 | AppType: () => AppType 22 | }); 23 | module.exports = __toCommonJS(abstract_exports); 24 | var AppType; 25 | ((AppType2) => { 26 | class AbstractApp { 27 | name; 28 | apiClient; 29 | adapter; 30 | objPrefix; 31 | constructor(apiClient, adapter, name) { 32 | this.name = name; 33 | this.apiClient = apiClient; 34 | this.adapter = adapter; 35 | if (this.adapter.isMainInstance()) { 36 | this.objPrefix = this.adapter.namespace; 37 | } else { 38 | this.objPrefix = this.adapter.config.foreignSettingsInstance; 39 | } 40 | adapter.on("stateChange", this.onStateChange.bind(this)); 41 | adapter.on("objectChange", this.onObjectChange.bind(this)); 42 | } 43 | getName() { 44 | return this.name; 45 | } 46 | isMainInstance() { 47 | return this.adapter.isMainInstance(); 48 | } 49 | getObjIdOwnNamespace(id) { 50 | return this.adapter.removeNamespace(this.isMainInstance() ? id : id.replace(this.objPrefix, this.adapter.namespace)); 51 | } 52 | hasOwnActivateState() { 53 | return this.isMainInstance() || !this.adapter.config.foreignSettingsInstanceActivateApps; 54 | } 55 | async createObjects() { 56 | const appName = this.getName(); 57 | this.adapter.log.debug(`[createObjects] Creating objects for app "${appName}" (${this.isMainInstance() ? "main" : this.objPrefix})`); 58 | if (this.hasOwnActivateState()) { 59 | await this.adapter.extendObject(`apps.${appName}.activate`, { 60 | type: "state", 61 | common: { 62 | name: { 63 | en: "Activate", 64 | de: "Aktivieren", 65 | ru: "\u0410\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u0442\u044C", 66 | pt: "Ativar", 67 | nl: "Activeren", 68 | fr: "Activer", 69 | it: "Attivare", 70 | es: "Activar", 71 | pl: "Aktywuj", 72 | uk: "\u0410\u043A\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438", 73 | "zh-cn": "\u542F\u7528" 74 | }, 75 | type: "boolean", 76 | role: "button", 77 | read: false, 78 | write: true 79 | }, 80 | native: {} 81 | }); 82 | } else { 83 | await this.adapter.delObjectAsync(`apps.${appName}.activate`); 84 | await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.activate`); 85 | } 86 | } 87 | async onStateChange(id, state) { 88 | const appName = this.getName(); 89 | if (id) { 90 | this.adapter.log.debug(`[onStateChange] ${appName}: State change "${id}": ${JSON.stringify(state)}`); 91 | if (state && !state.ack) { 92 | if (id === `${this.hasOwnActivateState() ? this.adapter.namespace : this.objPrefix}.apps.${appName}.activate`) { 93 | if (state.val) { 94 | this.apiClient.requestAsync("switch", "POST", { name: appName }).catch((error) => { 95 | this.adapter.log.warn(`[onStateChange] ${appName}: (switch) Unable to execute action: ${error}`); 96 | }); 97 | } else { 98 | this.adapter.log.warn(`[onStateChange] ${appName}: Received invalid value for state ${id}`); 99 | } 100 | } 101 | } 102 | } 103 | await this.stateChanged(id, state); 104 | } 105 | /* eslint-disable @typescript-eslint/no-unused-vars */ 106 | async stateChanged(id, state) { 107 | } 108 | async onObjectChange(id, obj) { 109 | await this.objectChanged(id, obj); 110 | } 111 | /* eslint-disable @typescript-eslint/no-unused-vars */ 112 | async objectChanged(id, obj) { 113 | } 114 | } 115 | AppType2.AbstractApp = AbstractApp; 116 | })(AppType || (AppType = {})); 117 | // Annotate the CommonJS export names for ESM import in node: 118 | 0 && (module.exports = { 119 | AppType 120 | }); 121 | //# sourceMappingURL=abstract.js.map 122 | -------------------------------------------------------------------------------- /build/lib/app-type/abstract.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../src/lib/app-type/abstract.ts"], 4 | "sourcesContent": ["import { AwtrixLight } from '../../main';\nimport { AwtrixApi } from '../api';\n\nexport namespace AppType {\n export abstract class AbstractApp {\n private name: string;\n\n protected apiClient: AwtrixApi.Client;\n protected adapter: AwtrixLight;\n\n protected objPrefix: string;\n\n public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) {\n this.name = name;\n\n this.apiClient = apiClient;\n this.adapter = adapter;\n\n if (this.adapter.isMainInstance()) {\n this.objPrefix = this.adapter.namespace;\n } else {\n this.objPrefix = this.adapter.config.foreignSettingsInstance;\n }\n\n adapter.on('stateChange', this.onStateChange.bind(this));\n adapter.on('objectChange', this.onObjectChange.bind(this));\n }\n\n public abstract getDescription(): string;\n\n public abstract getIconForObjectTree(): string;\n\n public getName(): string {\n return this.name;\n }\n\n public isMainInstance(): boolean {\n return this.adapter.isMainInstance();\n }\n\n protected getObjIdOwnNamespace(id: string): string {\n return this.adapter.removeNamespace(this.isMainInstance() ? id : id.replace(this.objPrefix, this.adapter.namespace));\n }\n\n private hasOwnActivateState(): boolean {\n return this.isMainInstance() || !this.adapter.config.foreignSettingsInstanceActivateApps;\n }\n\n public async createObjects(): Promise {\n const appName = this.getName();\n\n this.adapter.log.debug(`[createObjects] Creating objects for app \"${appName}\" (${this.isMainInstance() ? 'main' : this.objPrefix})`);\n\n if (this.hasOwnActivateState()) {\n await this.adapter.extendObject(`apps.${appName}.activate`, {\n type: 'state',\n common: {\n name: {\n en: 'Activate',\n de: 'Aktivieren',\n ru: '\u0410\u043A\u0442\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u0442\u044C',\n pt: 'Ativar',\n nl: 'Activeren',\n fr: 'Activer',\n it: 'Attivare',\n es: 'Activar',\n pl: 'Aktywuj',\n uk: '\u0410\u043A\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438',\n 'zh-cn': '\u542F\u7528',\n },\n type: 'boolean',\n role: 'button',\n read: false,\n write: true,\n },\n native: {},\n });\n } else {\n await this.adapter.delObjectAsync(`apps.${appName}.activate`);\n await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.activate`);\n }\n }\n\n private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise {\n const appName = this.getName();\n\n if (id) {\n this.adapter.log.debug(`[onStateChange] ${appName}: State change \"${id}\": ${JSON.stringify(state)}`);\n\n // Handle default states for all apps\n if (state && !state.ack) {\n // activate app\n if (id === `${this.hasOwnActivateState() ? this.adapter.namespace : this.objPrefix}.apps.${appName}.activate`) {\n if (state.val) {\n this.apiClient!.requestAsync('switch', 'POST', { name: appName }).catch((error) => {\n this.adapter.log.warn(`[onStateChange] ${appName}: (switch) Unable to execute action: ${error}`);\n });\n } else {\n this.adapter.log.warn(`[onStateChange] ${appName}: Received invalid value for state ${id}`);\n }\n }\n }\n }\n\n await this.stateChanged(id, state);\n }\n\n /* eslint-disable @typescript-eslint/no-unused-vars */\n protected async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise {\n // override\n }\n\n private async onObjectChange(id: string, obj: ioBroker.Object | null | undefined): Promise {\n await this.objectChanged(id, obj);\n }\n\n /* eslint-disable @typescript-eslint/no-unused-vars */\n protected async objectChanged(id: string, obj: ioBroker.Object | null | undefined): Promise {\n // override\n }\n }\n}\n"], 5 | "mappings": "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", 6 | "names": ["AppType"] 7 | } 8 | -------------------------------------------------------------------------------- /build/lib/app-type/native.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | var native_exports = {}; 20 | __export(native_exports, { 21 | AppType: () => AppType 22 | }); 23 | module.exports = __toCommonJS(native_exports); 24 | var import_abstract = require("./abstract"); 25 | var AppType; 26 | ((AppType2) => { 27 | class Native extends import_abstract.AppType.AbstractApp { 28 | constructor(apiClient, adapter, name) { 29 | super(apiClient, adapter, name); 30 | } 31 | getDescription() { 32 | return "native"; 33 | } 34 | getIconForObjectTree() { 35 | return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ5NS45IDE2Ni42YzMuMiA4LjcgLjUgMTguNC02LjQgMjQuNmwtNDMuMyAzOS40YzEuMSA4LjMgMS43IDE2LjggMS43IDI1LjRzLS42IDE3LjEtMS43IDI1LjRsNDMuMyAzOS40YzYuOSA2LjIgOS42IDE1LjkgNi40IDI0LjZjLTQuNCAxMS45LTkuNyAyMy4zLTE1LjggMzQuM2wtNC43IDguMWMtNi42IDExLTE0IDIxLjQtMjIuMSAzMS4yYy01LjkgNy4yLTE1LjcgOS42LTI0LjUgNi44bC01NS43LTE3LjdjLTEzLjQgMTAuMy0yOC4yIDE4LjktNDQgMjUuNGwtMTIuNSA1Ny4xYy0yIDkuMS05IDE2LjMtMTguMiAxNy44Yy0xMy44IDIuMy0yOCAzLjUtNDIuNSAzLjVzLTI4LjctMS4yLTQyLjUtMy41Yy05LjItMS41LTE2LjItOC43LTE4LjItMTcuOGwtMTIuNS01Ny4xYy0xNS44LTYuNS0zMC42LTE1LjEtNDQtMjUuNEw4My4xIDQyNS45Yy04LjggMi44LTE4LjYgLjMtMjQuNS02LjhjLTguMS05LjgtMTUuNS0yMC4yLTIyLjEtMzEuMmwtNC43LTguMWMtNi4xLTExLTExLjQtMjIuNC0xNS44LTM0LjNjLTMuMi04LjctLjUtMTguNCA2LjQtMjQuNmw0My4zLTM5LjRDNjQuNiAyNzMuMSA2NCAyNjQuNiA2NCAyNTZzLjYtMTcuMSAxLjctMjUuNEwyMi40IDE5MS4yYy02LjktNi4yLTkuNi0xNS45LTYuNC0yNC42YzQuNC0xMS45IDkuNy0yMy4zIDE1LjgtMzQuM2w0LjctOC4xYzYuNi0xMSAxNC0yMS40IDIyLjEtMzEuMmM1LjktNy4yIDE1LjctOS42IDI0LjUtNi44bDU1LjcgMTcuN2MxMy40LTEwLjMgMjguMi0xOC45IDQ0LTI1LjRsMTIuNS01Ny4xYzItOS4xIDktMTYuMyAxOC4yLTE3LjhDMjI3LjMgMS4yIDI0MS41IDAgMjU2IDBzMjguNyAxLjIgNDIuNSAzLjVjOS4yIDEuNSAxNi4yIDguNyAxOC4yIDE3LjhsMTIuNSA1Ny4xYzE1LjggNi41IDMwLjYgMTUuMSA0NCAyNS40bDU1LjctMTcuN2M4LjgtMi44IDE4LjYtLjMgMjQuNSA2LjhjOC4xIDkuOCAxNS41IDIwLjIgMjIuMSAzMS4ybDQuNyA4LjFjNi4xIDExIDExLjQgMjIuNCAxNS44IDM0LjN6TTI1NiAzMzZhODAgODAgMCAxIDAgMC0xNjAgODAgODAgMCAxIDAgMCAxNjB6Ii8+PC9zdmc+"; 36 | } 37 | } 38 | AppType2.Native = Native; 39 | })(AppType || (AppType = {})); 40 | // Annotate the CommonJS export names for ESM import in node: 41 | 0 && (module.exports = { 42 | AppType 43 | }); 44 | //# sourceMappingURL=native.js.map 45 | -------------------------------------------------------------------------------- /build/lib/app-type/native.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../src/lib/app-type/native.ts"], 4 | "sourcesContent": ["import { AwtrixLight } from '../../main';\nimport { AwtrixApi } from '../api';\nimport { AppType as AbstractAppType } from './abstract';\n\nexport namespace AppType {\n export class Native extends AbstractAppType.AbstractApp {\n public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) {\n super(apiClient, adapter, name);\n }\n\n public override getDescription(): string {\n return 'native';\n }\n\n public override getIconForObjectTree(): string {\n return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ5NS45IDE2Ni42YzMuMiA4LjcgLjUgMTguNC02LjQgMjQuNmwtNDMuMyAzOS40YzEuMSA4LjMgMS43IDE2LjggMS43IDI1LjRzLS42IDE3LjEtMS43IDI1LjRsNDMuMyAzOS40YzYuOSA2LjIgOS42IDE1LjkgNi40IDI0LjZjLTQuNCAxMS45LTkuNyAyMy4zLTE1LjggMzQuM2wtNC43IDguMWMtNi42IDExLTE0IDIxLjQtMjIuMSAzMS4yYy01LjkgNy4yLTE1LjcgOS42LTI0LjUgNi44bC01NS43LTE3LjdjLTEzLjQgMTAuMy0yOC4yIDE4LjktNDQgMjUuNGwtMTIuNSA1Ny4xYy0yIDkuMS05IDE2LjMtMTguMiAxNy44Yy0xMy44IDIuMy0yOCAzLjUtNDIuNSAzLjVzLTI4LjctMS4yLTQyLjUtMy41Yy05LjItMS41LTE2LjItOC43LTE4LjItMTcuOGwtMTIuNS01Ny4xYy0xNS44LTYuNS0zMC42LTE1LjEtNDQtMjUuNEw4My4xIDQyNS45Yy04LjggMi44LTE4LjYgLjMtMjQuNS02LjhjLTguMS05LjgtMTUuNS0yMC4yLTIyLjEtMzEuMmwtNC43LTguMWMtNi4xLTExLTExLjQtMjIuNC0xNS44LTM0LjNjLTMuMi04LjctLjUtMTguNCA2LjQtMjQuNmw0My4zLTM5LjRDNjQuNiAyNzMuMSA2NCAyNjQuNiA2NCAyNTZzLjYtMTcuMSAxLjctMjUuNEwyMi40IDE5MS4yYy02LjktNi4yLTkuNi0xNS45LTYuNC0yNC42YzQuNC0xMS45IDkuNy0yMy4zIDE1LjgtMzQuM2w0LjctOC4xYzYuNi0xMSAxNC0yMS40IDIyLjEtMzEuMmM1LjktNy4yIDE1LjctOS42IDI0LjUtNi44bDU1LjcgMTcuN2MxMy40LTEwLjMgMjguMi0xOC45IDQ0LTI1LjRsMTIuNS01Ny4xYzItOS4xIDktMTYuMyAxOC4yLTE3LjhDMjI3LjMgMS4yIDI0MS41IDAgMjU2IDBzMjguNyAxLjIgNDIuNSAzLjVjOS4yIDEuNSAxNi4yIDguNyAxOC4yIDE3LjhsMTIuNSA1Ny4xYzE1LjggNi41IDMwLjYgMTUuMSA0NCAyNS40bDU1LjctMTcuN2M4LjgtMi44IDE4LjYtLjMgMjQuNSA2LjhjOC4xIDkuOCAxNS41IDIwLjIgMjIuMSAzMS4ybDQuNyA4LjFjNi4xIDExIDExLjQgMjIuNCAxNS44IDM0LjN6TTI1NiAzMzZhODAgODAgMCAxIDAgMC0xNjAgODAgODAgMCAxIDAgMCAxNjB6Ii8+PC9zdmc+';\n }\n }\n}\n"], 5 | "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,sBAA2C;AAEpC,IAAU;AAAA,CAAV,CAAUA,aAAV;AAAA,EACI,MAAM,eAAe,gBAAAC,QAAgB,YAAY;AAAA,IAC7C,YAAY,WAA6B,SAAsB,MAAc;AAChF,YAAM,WAAW,SAAS,IAAI;AAAA,IAClC;AAAA,IAEgB,iBAAyB;AACrC,aAAO;AAAA,IACX;AAAA,IAEgB,uBAA+B;AAC3C,aAAO;AAAA,IACX;AAAA,EACJ;AAZO,EAAAD,SAAM;AAAA,GADA;", 6 | "names": ["AppType", "AbstractAppType"] 7 | } 8 | -------------------------------------------------------------------------------- /build/lib/app-type/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | var user_exports = {}; 20 | __export(user_exports, { 21 | AppType: () => AppType 22 | }); 23 | module.exports = __toCommonJS(user_exports); 24 | var import_abstract = require("./abstract"); 25 | var AppType; 26 | ((AppType2) => { 27 | class UserApp extends import_abstract.AppType.AbstractApp { 28 | definition; 29 | ignoreNewValueForAppInTimeRange; 30 | isVisible; 31 | constructor(apiClient, adapter, definition) { 32 | super(apiClient, adapter, definition.name); 33 | this.definition = definition; 34 | this.ignoreNewValueForAppInTimeRange = adapter.config.ignoreNewValueForAppInTimeRange; 35 | this.isVisible = false; 36 | } 37 | async init() { 38 | const appName = this.getName(); 39 | const appVisibleState = await this.adapter.getForeignStateAsync(`${this.objPrefix}.apps.${appName}.visible`); 40 | this.isVisible = appVisibleState ? !!appVisibleState.val : true; 41 | if (appVisibleState && !(appVisibleState == null ? void 0 : appVisibleState.ack)) { 42 | await this.adapter.setState(`apps.${appName}.visible`, { val: this.isVisible, ack: true, c: "init" }); 43 | } 44 | return this.isVisible; 45 | } 46 | async refresh() { 47 | if (!this.isVisible && this.apiClient.isConnected()) { 48 | const appName = this.getName(); 49 | this.apiClient.removeAppAsync(appName).catch((error) => { 50 | this.adapter.log.warn(`[refreshApp] Unable to remove hidden app "${appName}": ${error}`); 51 | }); 52 | } 53 | return this.isVisible && this.apiClient.isConnected(); 54 | } 55 | async createObjects() { 56 | await super.createObjects(); 57 | const appName = this.getName(); 58 | await this.adapter.extendObject(`apps.${appName}.visible`, { 59 | type: "state", 60 | common: { 61 | name: { 62 | en: "Visible", 63 | de: "Sichtbar", 64 | ru: "\u0412\u0438\u0434\u0438\u043C\u044B\u0439", 65 | pt: "Vis\xEDvel", 66 | nl: "Vertaling", 67 | fr: "Visible", 68 | it: "Visibile", 69 | es: "Visible", 70 | pl: "Widoczny", 71 | uk: "\u0412\u0438\u0431\u0440\u0430\u043D\u0456", 72 | "zh-cn": "\u4E0D\u53EF\u6297\u8FA9" 73 | }, 74 | type: "boolean", 75 | role: "switch.enable", 76 | read: true, 77 | write: this.isMainInstance(), 78 | def: true 79 | }, 80 | native: {} 81 | }); 82 | if (!this.isMainInstance()) { 83 | await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.visible`); 84 | } 85 | } 86 | async unloadAsync() { 87 | if (this.adapter.config.removeAppsOnStop) { 88 | this.adapter.log.info(`[onUnload] Deleting app on awtrix light with name "${this.definition.name}"`); 89 | try { 90 | await this.apiClient.removeAppAsync(this.definition.name).catch((error) => { 91 | this.adapter.log.warn(`Unable to remove unknown app "${this.definition.name}": ${error}`); 92 | }); 93 | } catch (error) { 94 | this.adapter.log.error(`[onUnload] Unable to delete app ${this.definition.name}: ${error}`); 95 | } 96 | } 97 | } 98 | async stateChanged(id, state) { 99 | if (id && state && !state.ack) { 100 | const appName = this.getName(); 101 | const idOwnNamespace = this.getObjIdOwnNamespace(id); 102 | if (id === `${this.objPrefix}.apps.${appName}.visible`) { 103 | if (state.val !== this.isVisible) { 104 | this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app ${appName} changed to ${state.val}`); 105 | this.isVisible = !!state.val; 106 | await this.refresh(); 107 | await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix}` }); 108 | } else { 109 | this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app "${appName}" IGNORED (not changed): ${state.val}`); 110 | await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix} (unchanged)` }); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | AppType2.UserApp = UserApp; 117 | })(AppType || (AppType = {})); 118 | // Annotate the CommonJS export names for ESM import in node: 119 | 0 && (module.exports = { 120 | AppType 121 | }); 122 | //# sourceMappingURL=user.js.map 123 | -------------------------------------------------------------------------------- /build/lib/app-type/user.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../src/lib/app-type/user.ts"], 4 | "sourcesContent": ["import { AwtrixLight } from '../../main';\nimport { DefaultApp } from '../adapter-config';\nimport { AwtrixApi } from '../api';\nimport { AppType as AbstractAppType } from './abstract';\n\nexport namespace AppType {\n export abstract class UserApp extends AbstractAppType.AbstractApp {\n private definition: DefaultApp;\n\n protected ignoreNewValueForAppInTimeRange: number;\n protected isVisible: boolean;\n\n public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, definition: DefaultApp) {\n super(apiClient, adapter, definition.name);\n\n this.definition = definition;\n this.ignoreNewValueForAppInTimeRange = adapter.config.ignoreNewValueForAppInTimeRange;\n this.isVisible = false;\n }\n\n public async init(): Promise {\n const appName = this.getName();\n const appVisibleState = await this.adapter.getForeignStateAsync(`${this.objPrefix}.apps.${appName}.visible`);\n this.isVisible = appVisibleState ? !!appVisibleState.val : true;\n\n // Ack if changed while instance was stopped\n if (appVisibleState && !appVisibleState?.ack) {\n await this.adapter.setState(`apps.${appName}.visible`, { val: this.isVisible, ack: true, c: 'init' });\n }\n\n return this.isVisible;\n }\n\n public async refresh(): Promise {\n if (!this.isVisible && this.apiClient.isConnected()) {\n // Hide app automatically\n const appName = this.getName();\n this.apiClient.removeAppAsync(appName).catch((error) => {\n this.adapter.log.warn(`[refreshApp] Unable to remove hidden app \"${appName}\": ${error}`);\n });\n }\n\n return this.isVisible && this.apiClient.isConnected();\n }\n\n public async createObjects(): Promise {\n await super.createObjects();\n\n const appName = this.getName();\n\n await this.adapter.extendObject(`apps.${appName}.visible`, {\n type: 'state',\n common: {\n name: {\n en: 'Visible',\n de: 'Sichtbar',\n ru: '\u0412\u0438\u0434\u0438\u043C\u044B\u0439',\n pt: 'Vis\u00EDvel',\n nl: 'Vertaling',\n fr: 'Visible',\n it: 'Visibile',\n es: 'Visible',\n pl: 'Widoczny',\n uk: '\u0412\u0438\u0431\u0440\u0430\u043D\u0456',\n 'zh-cn': '\u4E0D\u53EF\u6297\u8FA9',\n },\n type: 'boolean',\n role: 'switch.enable',\n read: true,\n write: this.isMainInstance(),\n def: true,\n },\n native: {},\n });\n\n if (!this.isMainInstance()) {\n await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.visible`);\n }\n }\n\n public async unloadAsync(): Promise {\n if (this.adapter.config.removeAppsOnStop) {\n this.adapter.log.info(`[onUnload] Deleting app on awtrix light with name \"${this.definition.name}\"`);\n\n try {\n await this.apiClient.removeAppAsync(this.definition.name).catch((error) => {\n this.adapter.log.warn(`Unable to remove unknown app \"${this.definition.name}\": ${error}`);\n });\n } catch (error) {\n this.adapter.log.error(`[onUnload] Unable to delete app ${this.definition.name}: ${error}`);\n }\n }\n }\n\n protected override async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise {\n // Handle all states for user apps\n if (id && state && !state.ack) {\n const appName = this.getName();\n const idOwnNamespace = this.getObjIdOwnNamespace(id);\n\n if (id === `${this.objPrefix}.apps.${appName}.visible`) {\n if (state.val !== this.isVisible) {\n this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app ${appName} changed to ${state.val}`);\n\n this.isVisible = !!state.val;\n\n await this.refresh();\n await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix}` });\n } else {\n this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app \"${appName}\" IGNORED (not changed): ${state.val}`);\n\n await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix} (unchanged)` });\n }\n }\n }\n }\n }\n}\n"], 5 | "mappings": "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", 6 | "names": ["AppType", "AbstractAppType"] 7 | } 8 | -------------------------------------------------------------------------------- /build/lib/app-type/user/history.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | var history_exports = {}; 20 | __export(history_exports, { 21 | AppType: () => AppType 22 | }); 23 | module.exports = __toCommonJS(history_exports); 24 | var import_user = require("../user"); 25 | var AppType; 26 | ((AppType2) => { 27 | class History extends import_user.AppType.UserApp { 28 | appDefinition; 29 | isValidSourceInstance; 30 | isValidObjId; 31 | refreshTimeout; 32 | constructor(apiClient, adapter, definition) { 33 | super(apiClient, adapter, definition); 34 | this.appDefinition = definition; 35 | this.isValidSourceInstance = false; 36 | this.isValidObjId = false; 37 | this.refreshTimeout = void 0; 38 | } 39 | getDescription() { 40 | return "history"; 41 | } 42 | getIconForObjectTree() { 43 | return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTE2MCA4MGMwLTI2LjUgMjEuNS00OCA0OC00OGwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMzUyYzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTM1MnpNMCAyNzJjMC0yNi41IDIxLjUtNDggNDgtNDhsMzIgMGMyNi41IDAgNDggMjEuNSA0OCA0OGwwIDE2MGMwIDI2LjUtMjEuNSA0OC00OCA0OGwtMzIgMGMtMjYuNSAwLTQ4LTIxLjUtNDgtNDhMMCAyNzJ6TTM2OCA5NmwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMjg4YzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTI4OGMwLTI2LjUgMjEuNS00OCA0OC00OHoiLz48L3N2Zz4="; 44 | } 45 | async init() { 46 | var _a, _b, _c; 47 | if (this.appDefinition.sourceInstance) { 48 | const sourceInstanceObj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.appDefinition.sourceInstance}`); 49 | if (sourceInstanceObj && ((_a = sourceInstanceObj.common) == null ? void 0 : _a.getHistory)) { 50 | const sourceInstanceAliveState = await this.adapter.getForeignStateAsync(`system.adapter.${this.appDefinition.sourceInstance}.alive`); 51 | if (sourceInstanceAliveState && sourceInstanceAliveState.val) { 52 | this.adapter.log.debug(`[initHistoryApp] Found valid source instance for history data: ${this.appDefinition.sourceInstance}`); 53 | this.isValidSourceInstance = true; 54 | } else { 55 | this.adapter.log.warn(`[initHistoryApp] Unable to get history data of "${this.appDefinition.sourceInstance}": instance not running (stopped)`); 56 | } 57 | } else { 58 | this.adapter.log.warn(`[initHistoryApp] Unable to get history data of "${this.appDefinition.sourceInstance}": no valid source for getHistory()`); 59 | } 60 | } 61 | if (this.appDefinition.objId) { 62 | this.adapter.log.debug(`[initHistoryApp] getting history data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}" from ${this.appDefinition.sourceInstance}`); 63 | try { 64 | if (this.isValidSourceInstance) { 65 | const sourceObj = await this.adapter.getForeignObjectAsync(this.appDefinition.objId); 66 | if (sourceObj && Object.prototype.hasOwnProperty.call((_c = (_b = sourceObj == null ? void 0 : sourceObj.common) == null ? void 0 : _b.custom) != null ? _c : {}, this.appDefinition.sourceInstance)) { 67 | this.isValidObjId = true; 68 | } else { 69 | this.adapter.log.info( 70 | `[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": logging is not configured for this object` 71 | ); 72 | } 73 | } else { 74 | this.adapter.log.info(`[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": source invalid or unavailable`); 75 | } 76 | } catch (error) { 77 | this.adapter.log.error(`[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": ${error}`); 78 | } 79 | } 80 | return super.init(); 81 | } 82 | async refresh() { 83 | var _a; 84 | let refreshed = false; 85 | if (await super.refresh() && this.isValidSourceInstance && this.isValidObjId) { 86 | const itemCount = this.appDefinition.icon ? 11 : 16; 87 | const options = { 88 | start: 1, 89 | end: Date.now(), 90 | limit: itemCount, 91 | returnNewestEntries: true, 92 | ignoreNull: 0, 93 | removeBorderValues: true, 94 | ack: true 95 | }; 96 | if (this.appDefinition.mode == "aggregate") { 97 | options.aggregate = this.appDefinition.aggregation; 98 | options.step = this.appDefinition.step ? this.appDefinition.step * 1e3 : 3600; 99 | } else { 100 | options.aggregate = "none"; 101 | } 102 | const historyData = await this.adapter.sendToAsync(this.appDefinition.sourceInstance, "getHistory", { 103 | id: this.appDefinition.objId, 104 | options 105 | }); 106 | const graphData = historyData == null ? void 0 : historyData.result.filter((state) => typeof state.val === "number" && state.ack).map((state) => Math.round(state.val)).slice(itemCount * -1); 107 | this.adapter.log.debug( 108 | `[refreshHistoryApp] Data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}: ${JSON.stringify(historyData)} - filtered: ${JSON.stringify(graphData)}` 109 | ); 110 | if (graphData.length > 0) { 111 | const moreOptions = {}; 112 | if (this.appDefinition.duration > 0) { 113 | moreOptions.duration = this.appDefinition.duration; 114 | } 115 | if (this.appDefinition.repeat > 0) { 116 | moreOptions.repeat = this.appDefinition.repeat; 117 | } 118 | if (this.appDefinition.display == "bar") { 119 | moreOptions.bar = graphData; 120 | } else { 121 | moreOptions.line = graphData; 122 | } 123 | await this.apiClient.appRequestAsync(this.appDefinition.name, { 124 | color: this.appDefinition.lineColor || "#FF0000", 125 | background: this.appDefinition.backgroundColor || "#000000", 126 | autoscale: true, 127 | icon: this.appDefinition.icon, 128 | lifetime: this.adapter.config.historyAppsRefreshInterval + 60, 129 | // Remove app if there is no update in configured interval (+ buffer) 130 | pos: this.appDefinition.position, 131 | ...moreOptions 132 | }).catch((error) => { 133 | this.adapter.log.warn(`(custom?name=${this.appDefinition.name}) Unable to create app "${this.appDefinition.name}": ${error}`); 134 | }); 135 | refreshed = true; 136 | } else { 137 | this.adapter.log.debug(`[refreshHistoryApp] Going to remove app "${this.appDefinition.name}" (no history data)`); 138 | await this.apiClient.removeAppAsync(this.appDefinition.name).catch((error) => { 139 | this.adapter.log.warn(`[refreshHistoryApp] Unable to remove app "${this.appDefinition.name}" (no history data): ${error}`); 140 | }); 141 | } 142 | } 143 | this.adapter.log.debug(`re-creating history apps timeout (${(_a = this.adapter.config.historyAppsRefreshInterval) != null ? _a : 300} seconds)`); 144 | this.refreshTimeout = this.refreshTimeout || this.adapter.setTimeout( 145 | () => { 146 | this.refreshTimeout = void 0; 147 | this.refresh(); 148 | }, 149 | this.adapter.config.historyAppsRefreshInterval * 1e3 || 5 * 60 * 1e3 150 | ); 151 | return refreshed; 152 | } 153 | async unloadAsync() { 154 | if (this.refreshTimeout) { 155 | this.adapter.log.debug(`clearing history app timeout for "${this.getName()}"`); 156 | this.adapter.clearTimeout(this.refreshTimeout); 157 | } 158 | await super.unloadAsync(); 159 | } 160 | } 161 | AppType2.History = History; 162 | })(AppType || (AppType = {})); 163 | // Annotate the CommonJS export names for ESM import in node: 164 | 0 && (module.exports = { 165 | AppType 166 | }); 167 | //# sourceMappingURL=history.js.map 168 | -------------------------------------------------------------------------------- /build/lib/app-type/user/history.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../../../src/lib/app-type/user/history.ts"], 4 | "sourcesContent": ["import { AwtrixLight } from '../../../main';\nimport { HistoryApp } from '../../adapter-config';\nimport { AwtrixApi } from '../../api';\nimport { AppType as UserAppType } from '../user';\n\nexport namespace AppType {\n export type HistoryOptions = {\n start: number;\n end: number;\n limit: number;\n aggregate?: 'none' | 'average' | 'min' | 'max' | 'count';\n step?: number;\n returnNewestEntries: boolean;\n ignoreNull: number;\n removeBorderValues: boolean;\n ack: boolean;\n };\n\n export class History extends UserAppType.UserApp {\n private appDefinition: HistoryApp;\n private isValidSourceInstance: boolean;\n private isValidObjId: boolean;\n private refreshTimeout: ioBroker.Timeout | undefined;\n\n public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, definition: HistoryApp) {\n super(apiClient, adapter, definition);\n\n this.appDefinition = definition;\n this.isValidSourceInstance = false;\n this.isValidObjId = false;\n this.refreshTimeout = undefined;\n }\n\n public override getDescription(): string {\n return 'history';\n }\n\n public override getIconForObjectTree(): string {\n return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTE2MCA4MGMwLTI2LjUgMjEuNS00OCA0OC00OGwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMzUyYzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTM1MnpNMCAyNzJjMC0yNi41IDIxLjUtNDggNDgtNDhsMzIgMGMyNi41IDAgNDggMjEuNSA0OCA0OGwwIDE2MGMwIDI2LjUtMjEuNSA0OC00OCA0OGwtMzIgMGMtMjYuNSAwLTQ4LTIxLjUtNDgtNDhMMCAyNzJ6TTM2OCA5NmwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMjg4YzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTI4OGMwLTI2LjUgMjEuNS00OCA0OC00OHoiLz48L3N2Zz4=';\n }\n\n public override async init(): Promise {\n if (this.appDefinition.sourceInstance) {\n const sourceInstanceObj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.appDefinition.sourceInstance}`);\n\n if (sourceInstanceObj && sourceInstanceObj.common?.getHistory) {\n const sourceInstanceAliveState = await this.adapter.getForeignStateAsync(`system.adapter.${this.appDefinition.sourceInstance}.alive`);\n\n if (sourceInstanceAliveState && sourceInstanceAliveState.val) {\n this.adapter.log.debug(`[initHistoryApp] Found valid source instance for history data: ${this.appDefinition.sourceInstance}`);\n\n this.isValidSourceInstance = true;\n } else {\n this.adapter.log.warn(`[initHistoryApp] Unable to get history data of \"${this.appDefinition.sourceInstance}\": instance not running (stopped)`);\n }\n } else {\n this.adapter.log.warn(`[initHistoryApp] Unable to get history data of \"${this.appDefinition.sourceInstance}\": no valid source for getHistory()`);\n }\n }\n\n if (this.appDefinition.objId) {\n this.adapter.log.debug(`[initHistoryApp] getting history data for app \"${this.appDefinition.name}\" of \"${this.appDefinition.objId}\" from ${this.appDefinition.sourceInstance}`);\n\n try {\n if (this.isValidSourceInstance) {\n const sourceObj = await this.adapter.getForeignObjectAsync(this.appDefinition.objId);\n\n if (sourceObj && Object.prototype.hasOwnProperty.call(sourceObj?.common?.custom ?? {}, this.appDefinition.sourceInstance)) {\n this.isValidObjId = true;\n } else {\n this.adapter.log.info(\n `[initHistoryApp] Unable to get data for app \"${this.appDefinition.name}\" of \"${this.appDefinition.objId}\": logging is not configured for this object`,\n );\n }\n } else {\n this.adapter.log.info(`[initHistoryApp] Unable to get data for app \"${this.appDefinition.name}\" of \"${this.appDefinition.objId}\": source invalid or unavailable`);\n }\n } catch (error) {\n this.adapter.log.error(`[initHistoryApp] Unable to get data for app \"${this.appDefinition.name}\" of \"${this.appDefinition.objId}\": ${error}`);\n }\n }\n\n return super.init();\n }\n\n public override async refresh(): Promise {\n let refreshed = false;\n\n if ((await super.refresh()) && this.isValidSourceInstance && this.isValidObjId) {\n const itemCount = this.appDefinition.icon ? 11 : 16; // can display 11 values with icon or 16 values without icon\n\n const options: HistoryOptions = {\n start: 1,\n end: Date.now(),\n limit: itemCount,\n returnNewestEntries: true,\n ignoreNull: 0,\n removeBorderValues: true,\n ack: true,\n };\n\n if (this.appDefinition.mode == 'aggregate') {\n options.aggregate = this.appDefinition.aggregation;\n options.step = this.appDefinition.step ? this.appDefinition.step * 1000 : 3600;\n } else {\n // mode = last\n options.aggregate = 'none';\n }\n\n const historyData = await this.adapter.sendToAsync(this.appDefinition.sourceInstance, 'getHistory', {\n id: this.appDefinition.objId,\n options,\n });\n const graphData = (historyData as any)?.result\n .filter((state: ioBroker.State) => typeof state.val === 'number' && state.ack)\n .map((state: ioBroker.State) => Math.round(state.val as number))\n .slice(itemCount * -1);\n\n this.adapter.log.debug(\n `[refreshHistoryApp] Data for app \"${this.appDefinition.name}\" of \"${this.appDefinition.objId}: ${JSON.stringify(historyData)} - filtered: ${JSON.stringify(graphData)}`,\n );\n\n if (graphData.length > 0) {\n const moreOptions: AwtrixApi.App = {};\n\n // Duration\n if (this.appDefinition.duration > 0) {\n moreOptions.duration = this.appDefinition.duration;\n }\n\n // Repeat\n if (this.appDefinition.repeat > 0) {\n moreOptions.repeat = this.appDefinition.repeat;\n }\n\n // Bar or line graph\n if (this.appDefinition.display == 'bar') {\n moreOptions.bar = graphData;\n } else {\n moreOptions.line = graphData;\n }\n\n await this.apiClient!.appRequestAsync(this.appDefinition.name, {\n color: this.appDefinition.lineColor || '#FF0000',\n background: this.appDefinition.backgroundColor || '#000000',\n autoscale: true,\n icon: this.appDefinition.icon,\n lifetime: this.adapter.config.historyAppsRefreshInterval + 60, // Remove app if there is no update in configured interval (+ buffer)\n pos: this.appDefinition.position,\n ...moreOptions,\n }).catch((error) => {\n this.adapter.log.warn(`(custom?name=${this.appDefinition.name}) Unable to create app \"${this.appDefinition.name}\": ${error}`);\n });\n\n refreshed = true;\n } else {\n this.adapter.log.debug(`[refreshHistoryApp] Going to remove app \"${this.appDefinition.name}\" (no history data)`);\n\n await this.apiClient!.removeAppAsync(this.appDefinition.name).catch((error) => {\n this.adapter.log.warn(`[refreshHistoryApp] Unable to remove app \"${this.appDefinition.name}\" (no history data): ${error}`);\n });\n }\n }\n\n this.adapter.log.debug(`re-creating history apps timeout (${this.adapter.config.historyAppsRefreshInterval ?? 300} seconds)`);\n this.refreshTimeout =\n this.refreshTimeout ||\n this.adapter.setTimeout(\n () => {\n this.refreshTimeout = undefined;\n this.refresh();\n },\n this.adapter.config.historyAppsRefreshInterval * 1000 || 5 * 60 * 1000,\n );\n\n return refreshed;\n }\n\n public override async unloadAsync(): Promise {\n if (this.refreshTimeout) {\n this.adapter.log.debug(`clearing history app timeout for \"${this.getName()}\"`);\n this.adapter.clearTimeout(this.refreshTimeout);\n }\n\n await super.unloadAsync();\n }\n }\n}\n"], 5 | "mappings": "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", 6 | "names": ["AppType", "UserAppType"] 7 | } 8 | -------------------------------------------------------------------------------- /build/lib/color-convert.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | var color_convert_exports = {}; 20 | __export(color_convert_exports, { 21 | rgb565to888: () => rgb565to888, 22 | rgb565to888Str: () => rgb565to888Str 23 | }); 24 | module.exports = __toCommonJS(color_convert_exports); 25 | function rgb565to888(val) { 26 | const r = (val & 16711680) >> 16; 27 | const g = (val & 65280) >> 8; 28 | const b = val & 255; 29 | return r << 16 | g << 8 | b; 30 | } 31 | function rgb565to888Str(val) { 32 | return "#" + rgb565to888(val).toString(16).toUpperCase().padStart(6, "0"); 33 | } 34 | // Annotate the CommonJS export names for ESM import in node: 35 | 0 && (module.exports = { 36 | rgb565to888, 37 | rgb565to888Str 38 | }); 39 | //# sourceMappingURL=color-convert.js.map 40 | -------------------------------------------------------------------------------- /build/lib/color-convert.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../../src/lib/color-convert.ts"], 4 | "sourcesContent": ["export function rgb565to888(val: number): number {\n const r = (val & 0xff0000) >> 16;\n const g = (val & 0x00ff00) >> 8;\n const b = val & 0x0000ff;\n\n return (r << 16) | (g << 8) | b;\n}\n\nexport function rgb565to888Str(val: number): string {\n return '#' + rgb565to888(val).toString(16).toUpperCase().padStart(6, '0');\n}\n"], 5 | "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,YAAY,KAAqB;AAC7C,QAAM,KAAK,MAAM,aAAa;AAC9B,QAAM,KAAK,MAAM,UAAa;AAC9B,QAAM,IAAI,MAAM;AAEhB,SAAQ,KAAK,KAAO,KAAK,IAAK;AAClC;AAEO,SAAS,eAAe,KAAqB;AAChD,SAAO,MAAM,YAAY,GAAG,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,SAAS,GAAG,GAAG;AAC5E;", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /docs/de/README.md: -------------------------------------------------------------------------------- 1 | ![Logo](../../admin/awtrix-light.png) 2 | 3 | # ioBroker.awtrix-light 4 | 5 | ## Anforderungen 6 | 7 | - nodejs 20 (oder neuer) 8 | - js-controller 6.0.0 (oder neuer) 9 | - Admin Adapter 7.4.10 (oder neuer) 10 | - _Awtrix 3_ Gerät mit Firmware-Version _0.98_ (oder neuer) - z.B. Ulanzi TC001 11 | 12 | Hier kaufen: [Aliexpress.com](https://haus-auto.com/p/ali/UlanziTC001) oder hier: [ulanzi.de](https://haus-auto.com/p/ula/UlanziTC001) (Affiliate-Links) 13 | 14 | ## Erste Schritte 15 | 16 | 1. Flashe die Firmware auf das Gerät und füge es zu deinem lokalen Netzwerk per WLAN hinzu - siehe [Dokumentation](https://blueforcer.github.io/awtrix3/#/quickstart) 17 | 2. Installiere den awtrix-light Adapter im ioBroker (und erstelle eine neue Instanz) 18 | 3. Öffne die Instanz-Konfiguration und hinterlege die IP-Adresse des Gerätes im lokalen Netzwerk 19 | 20 | ## FAQ (häufig gestellte Fragen) 21 | 22 | **Kann ich den Adapter verwenden, um die Standard-Apps zu deaktivieren (wie den Batteriestand oder die Sensordaten)?** 23 | 24 | Nein, dieses Feature wurde in der awtrix-light Firmware mittlerweile entfernt. Nutze das Menu auf dem Gerät selbst um diese Apps dauerhaft zu verstecken. 25 | 26 | **Kann man Logikwerte (true/false) mit anderen Texten ersetzen?** 27 | 28 | Erstelle dafür einfach einen Alias in `alias.0` vom Typ `string` (Zeichenkette) und konvertiere den Logikwert mit einer Lesefunktion in einen beliebigen anderen Wert (beispielsweise `val ? 'offen' : 'geschlossen'`). *Das ist ein Standard-Feature vom ioBroker und hat nichts direkt mit diesem Adapter zu tun.* 29 | 30 | **Wie kann ich zur aktuellsten Firmware-Version wechseln?** 31 | 32 | Nutze einfach das [Menu auf dem Gerät](https://blueforcer.github.io/awtrix3/#/onscreen) um zum Punkt `update` zu navigieren. Den Rest erledigt die Uhr dann selbst. Es ist nicht nötig, den Web-Flasher erneut zu verwenden (außer, ein Firware-Update erfordert dies explizit). 33 | 34 | **Das Gerät wird heiß während es geladen wird.** 35 | 36 | Das Hardware-Design ist leider nicht optimal. Es wird empfohlen, ein möglichst schwaches Netzteil zu verwenden, welches maximal 1A liefern kann. 37 | 38 | **Kann man den Akku aus dem Gerät entfernen?** 39 | 40 | Ja, es gibt diese Möglichkeit. Allerdings muss das Gerät dazu mit einem Heißluftföhn geöffnet werden, da die Frontscheibe verklebt ist. Außerdem ist es nötig einen [Step-Down-Converter zu verlöten](https://github.com/Blueforcer/awtrix3/issues/67#issuecomment-1595418765), damit alles funktioniert. 41 | 42 | **Kann man die Apps auf dem Gerät anders sortieren?** 43 | 44 | Im Standard werden die Apps in die gleichen Reihenfolge angezeigt, wie sie auch in den Instanz-Einstellungen angelegt wurden. Bewege einfach die Apps nach oben oder unten um die Position zu verändern. Apps mit historischen Daten / Graphen sind dabei hinter den anderen benutzerdefinierten Apps positioniert. 45 | 46 | Sollen eigene Positionen festgelegt werden, können die benutzerdefinierten Positionen in den Experten-Optionen aktiviert werden. Danach ist es möglich, für jede App eine numerische Position zu vergeben. 47 | 48 | **Kann ein anderes Zahlenformat hinterlegt werden?** 49 | 50 | Alle Zustände vom Typ Zahl (common.type `number`) werden so formatiert, wie es im ioBroker konfiguriert ist. Das Standard-Format des Systems kann mit einer Experten-Einstellung überschrieben werden (seit Adapter-Version 0.7.1). Zahlen können in den folgenden Formaten dargestellt werden: 51 | 52 | - System-Standard 53 | - `xx.xxx,xx` 54 | - `xx,xxx.xx` (US-Format) 55 | - `xxxxx,xx` 56 | - `xxxxx.xx` (US-Format) 57 | 58 | **Kann man den Zugriff auf die Weboberfläche der awtrix-light beschränken?** 59 | 60 | Ja, seit Firware-Version 0.82 kann der Zugriff mit einem Benutzernamen und Passwort geschützt werden. Seit Adapter-Version 0.8.0 können diese Benutzerdaten ebenfalls in den Instanz-Einstellungen hinterlegt werden. 61 | 62 | **Wie funktioniert die halten-Option bei Benachrichtigungen?** 63 | 64 | Wenn eine Benachrichtigung mit der Option `hold: true` gesendet wird, bleibt der Text auf dem Display so lange stehen, bis die Benachrichtigung bestätigt wird. Das kann entweder über den mittleren Taster auf dem Gerät passieren, oder indem der Zustand `notification.dismiss` auf `true` gesetzt wird. 65 | 66 | **Einige Zustandsänderungen werden nich sofort dargestellt.** 67 | 68 | Falls ein Zustand sehr oft geändert wird (z.B. jede Sekunde), werden einige Änderungen ignoriert und nicht übertragen, damit die Last auf dem Gerät gering gehalten wird. Dafür hat jede App eine eigene "Block-Zeit", welche global in den Instanz-Einstellungen konfiguriert werden kann. Die Standard-Zeit ist 3 Sekunden. Es ist nicht empfohlen, ein Wert kleiner als 3 zu setzen. 69 | 70 | ## Identische Apps auf mehreren Geräten 71 | 72 | Falls mehrere awtrix-light Geräte mit den gleichen Apps angesteuert werden sollen, **muss eine eigene Instanz für jedes Gerät angelegt werden.** Allerdings kann in den Instanzeinstellungen der weiteren Geräte dann festgelegt werden, dass die Apps aus einer anderen Instanz übernommen werden sollen. 73 | 74 | Beispiel 75 | 76 | 1. Konfiguriere alle gewünschten Apps in der Instanz `awtrix-light.0` 77 | 2. Lege eine weitere Instanz für das zweite Gerät an (`awtrix-light.1`) 78 | 3. Wähle `awtrix-light.0` in den Instanz-Einstellungen von `awtrix-light.1` um die gleichen Apps auf dem zweiten Gerät darzustellen 79 | 80 | Seit Version 0.15.0 (und neuer) wird die Sichtbarkeit von benutzerdefinierten Apps und alle Inhalte der Experten-Apps auch auf andere Geräte übertragen, welche die App-Einstellungen kopieren. Im Beispiel oben werden z.B. die Apps der Instanz `awtrix-light.1` ebenfalls versteckt, sobald die Sichtbarkeit der App in der Hauptinstanz `awtrix-light.0` geändert wird. Das gleiche gilt für alle Inhalte der Experten-Apps. 81 | 82 | ## Blockly und JavaScript 83 | 84 | `sendTo` / messagebox kann genutzt werden um 85 | 86 | - eine einmalige Notification / Benachrichtigung darzustellen (mit Text, Ton, Symbol, ...) 87 | - einen Ton abzuspielen 88 | 89 | ### Benachrichtigungen 90 | 91 | Sende eine einmalige Benachrichtigung an das Gerät: 92 | 93 | ```javascript 94 | sendTo('awtrix-light.0', 'notification', { text: 'haus-automatisierung.com', repeat: 1, stack: true, wakeup: true, hold: false }, (res) => { 95 | if (res && res.error) { 96 | console.error(res.error); 97 | } 98 | }); 99 | ``` 100 | 101 | Das Nachrichten-Objekt unterstützt dabei alle Optionen, welche in der Firmware verfügbar sind. Siehe [Dokumentation](https://blueforcer.github.io/awtrix3/#/api?id=json-properties) für Details. 102 | 103 | *Außerdem kann ein Blockly-Block verwendet werden um die Benachrichtigung zu erstellen (dort werden nicht alle verfügbaren Optionen angeboten).* 104 | 105 | ### Töne 106 | 107 | **Die Sound-Dateien müssen im RTTTL-Format im Ordner MELODIES abgelegt werden. Die Dateiendung für diese Sounds ist .txt. Beim Abspielen der Sounds darf die Dateiendung nicht mit übergeben werden!** 108 | 109 | Um eine (vorher angelegte) Ton-Datei `beispiel.txt` abzuspielen: 110 | 111 | ```javascript 112 | sendTo('awtrix-light.0', 'sound', { sound: 'beispiel' }, (res) => { 113 | if (res && res.error) { 114 | console.error(res.error); 115 | } 116 | }); 117 | ``` 118 | 119 | Das Nachrichten-Objekt unterstützt dabei alle Optionen, welche in der Firmware verfügbar sind. Siehe [Dokumentation](https://blueforcer.github.io/awtrix3/#/api?id=sound-playback) für Details. 120 | 121 | *Es kann ein Blockly-Block verwendet werden, um diesen Aufruf noch einfacher zu verwenden.* 122 | 123 | Um einen eigenen Klingelton abzuspielen: 124 | 125 | ```javascript 126 | sendTo('awtrix-light.0', 'rtttl', 'Beep: d=32,o=7,b=120: a,P,c#', (res) => { 127 | if (res && res.error) { 128 | console.error(res.error); 129 | } 130 | }); 131 | ``` 132 | 133 | ## Apps 134 | 135 | **App-Namen dürfen nur Kleinbuchstaben (a-z) enthalten und müssen eindeutig sein. Keine Zahlen, keine Sonderzeichen, keine Leerzeichen.** 136 | 137 | Die folgenden App-Namen sind von den internen apps reserviert und können nicht verwendet werden: `Time`, `Date`, `Temperature`, `Humidity`, `Battery`. 138 | 139 | - Mit dem `activate`-Zustand jeder App kann diese in den Vordergrund geholt werden 140 | - Diese Zustände haben die Rolle `button` und erlauben nur den boolschen Wert `true` (andere Werte führen zu einer Warnung im Log) 141 | 142 | Jede selbst angelegte App hat einen Zustand mit der ID `apps..visible`. Wenn dieser Zustand auf `false` (falsch) gesetzt wird, wird die App vom Gerät entfernt und nicht mehr dargestellt. Dies ist nützlich, um bestimmte Apps z.B. nur tagsüber oder in bestimmten Zeiträumen darzustellen. 143 | 144 | ### Benutzerdefinierte Apps 145 | 146 | - `%s` ist ein Platzhalter für den Zustands-Wert 147 | - `%u` ist ein Platzhalter für die Einheit des Zustandes (z.B. `°C`) 148 | 149 | Diese Platzhalter können in den Texten benutzerdefinierter Apps verwendet werden (z.B. `Außentemperatur: %s %u`). 150 | 151 | **Benutzerdefinierte Apps stelle nur bestätigte Werte dar! Steuere-Werte mit `ack: false` werden ignoriert (um doppelte Anfragen an das Gerät zu vermeiden und um sicherzustellen, dass die dargestellten Werte gültig sind)!** 152 | 153 | Der ausgewählte Zustand sollte vom Datentyp Zeichenkette `string` oder Zahl `number` sein. Andere Typen (wie `boolean`) werden auch unterstützt, aber generieren Warnungen. Es wird empfohlen, einen Alias mit einer Konvertierungsfunktion zu verwenden um Logikwerte mit Text zu ersetzen (z.B. `val ? 'an' : 'aus'` oder `val ? 'offen' : 'geschlossen'`). Siehe ioBroker-Dokumentation für Details. *Dieses Standard-Feature hat nichts mit dem Adapter zu tun.* 154 | 155 | Die folgenden Kombinationen führen zu einer Warnung im Log: 156 | 157 | - Eine benutzerdefinierte App mit einer gewählten Objekt-ID enthält nicht den Platzhalter `%s` im Text 158 | - Eine benutzerdefinierte App wird mit einer gewählten Objekt-ID ohne Einheit in `common.unit` angelegt, aber `%u` ist im Text enthalten 159 | - Es wird keine Objekt-ID ausgewählt, aber `%s` im Text verwendet 160 | 161 | ### Historische Apps / Graphen 162 | 163 | TODO 164 | 165 | **In den Graphen werden nur bestätigte Werte dargestellt. Steuere-Werte mit `ack: false` werden gefiltert und ignoriert!** 166 | 167 | ### Experten Apps 168 | 169 | Experten-Apps sind seit Adapter-Version 0.10.0 verfügbar. Diese Apps erlauben es, alle Werte manuell über Zustände zu setzen und diese mit eigenen Logiken zu steuern. Um eine neue Experten-App zu erstellen: 170 | 171 | - Öffne das Tab Expertenoptionen in den Instanz-Einstellungen 172 | - Erstelle eine neue Experten-App mit einem frei wählbaren Namen (z.B. `test`) 173 | - Speichere die Instanz-Einstellungen 174 | 175 | Danach werden alle steuerbaren Zustände der App `test` unter `awtrix-light.0.apps.test` erstellt. Um die jeweiligen Werte einer App zu verändern, kann einfach der Wert der Zustände `icon`, `text`, usw. mit eigenen Scripts (z.B. JavaScript oder Blockly) gesetzt werden. 176 | 177 | Beispiel: [Wetter-App](weather-app.md) 178 | 179 | #### Basisobjekte 180 | 181 | *Benötigt Adapter-Version 2.0.0 (und neuer)* 182 | 183 | Das Basisobjekt ist eine grundlegende Definition für eine Awtrix-App, um alle existierenden Optionen setzen zu können. *Das Basisobjekt wird mit allen anderen Attributen der Experten-App erweitert.* 184 | 185 | Beispiel: Du möchtest den Regenbogen-Effekt auf der Experten-App nutzen, aber es existiert kein vordefiniter Datenpunkt, um diese Funktion direkt zu nutzen. In diesem Fall kann das Attribut im Basis-Objekt definiert werden (als JSON): `{ "rainbow": true }`. 186 | 187 | Siehe [Dokumentation](https://blueforcer.github.io/awtrix3/#/api?id=custom-apps-and-notifications) für alle verfügbaren Attribute. 188 | 189 | ## Native Apps verstecken 190 | 191 | Um die Standard-Apps auf dem Gerät zu verstecken (wie die Temperatur oder die Luftfeuchtigkeit): Nutze das Menu auf dem Gerät selbst! Siehe [Dokumentation](https://blueforcer.github.io/awtrix3/#/onscreen) für Details. 192 | -------------------------------------------------------------------------------- /docs/de/weather-app.md: -------------------------------------------------------------------------------- 1 | ![Logo](../../admin/awtrix-light.png) 2 | 3 | # ioBroker.awtrix-light 4 | 5 | ## Wetter-App (Experten-App) 6 | 7 | Dieses Script zeigt in einer Experten-App das richtige Icon zur aktuellen Wetterlage an. Dafür findet ein Mapping der Icons von [ioBroker.openweathermap](https://github.com/ioBroker/ioBroker.openweathermap/tree/master) zu Icons auf dem Gerät statt. 8 | 9 | *Danke an Andy200877 aus dem ioBroker-Forum für die Idee.* 10 | 11 | ### Icons 12 | 13 | Lade die folgenden Icons über die Weboberfläche auf das Gerät: 14 | 15 | - `11201` (01d clear sky) - klarer Himmel Tag 16 | - `52163` (01n clear sky) - klarer Himmel Nacht 17 | - `22315` (02d few clouds) - ein paar Wolken Tag (11-25% Wolken) 18 | - `26088` (02n few clouds) - ein paar Wolken Nacht (11-25% Wolken) 19 | - `22378` (03d scattered clouds) - aufgelockerte Bewölkung Tag (25-50% Wolken) 20 | - `21907` (03n scattered clouds) - aufgelockerte Bewölkung Nacht (25-50% Wolken) 21 | - `13852` (04d broken clouds) - bewölkt Tag (51-100%) 22 | - `52159` (04n broken clouds) - bewölkt Nacht (51-100%) 23 | - `43706` (09d shower rain) - Regenschauer Tag 24 | - `43739` (09n shower rain) - Regenschauer Nacht 25 | - `22257` (10d rain) - Regen Tag 26 | - ` 72` (10n rain) - Regen Nacht 27 | - `43733` (11d thunderstorm) - Gewitter Tag 28 | - `43748` (11n thunderstorm) - Gewitter Nacht 29 | - `43732` (13d snow) - Schnee Tag 30 | - `26090` (13n snow) - Schnee Nacht 31 | - `43708` (50d mist) - Nebel Tag 32 | - `43741` (50n mist) - Nebel Nacht 33 | 34 | ### Neue Experten-App 35 | 36 | Erstellt eine neue Experten-App mit dem Namen `weather`. 37 | 38 | ### Script 39 | 40 | ```javascript 41 | // v0.2 42 | const displayTemp = true; 43 | 44 | const appName = 'weather'; 45 | const objIdIcon = 'openweathermap.0.forecast.current.icon'; 46 | const objIdText = 'openweathermap.0.forecast.current.state'; 47 | const objIdTemp = 'openweathermap.0.forecast.current.temperature'; 48 | 49 | const iconMapping = { 50 | '01d': '11201', // clear sky day 51 | '01n': '52163', // clear sky night 52 | '02d': '22315', // few clouds day 53 | '02n': '26088', // few clouds night 54 | '03d': '22378', // scattered clouds day 55 | '03n': '21907', // scattered clouds night 56 | '04d': '13852', // broken clouds day 57 | '04n': '52159', // broken clouds night 58 | '09d': '43706', // shower rain day 59 | '09n': '43739', // shower rain night 60 | '10d': '22257', // rain day 61 | '10n': '72', // rain night 62 | '11d': '43733', // thunderstorm day 63 | '11n': '43748', // thunderstorm night 64 | '13d': '43732', // snow day 65 | '13n': '26090', // snow night 66 | '50d': '43708', // mist day 67 | '50n': '43741', // mist night 68 | }; 69 | 70 | async function refreshExpertApp() { 71 | try { 72 | const iconState = await getStateAsync(objIdIcon); 73 | if (iconState && iconState.ack && iconState.val) { 74 | const icon = /([0-9]{2}[d|n]{1})/.exec(iconState.val)[0]; 75 | if (iconMapping[icon]) { 76 | await setStateAsync(`awtrix-light.0.apps.${appName}.icon`, { val: iconMapping[icon] }); 77 | } 78 | } 79 | 80 | let temp = 0; 81 | const tempState = await getStateAsync(objIdTemp); 82 | if (tempState && tempState.ack && tempState.val) { 83 | temp = tempState.val; 84 | } 85 | 86 | if (temp > 30) { 87 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#bd2020' }); 88 | } else if (temp < 0) { 89 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#236fd9' }); 90 | } else { 91 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#ffffff' }); 92 | } 93 | 94 | const textState = await getStateAsync(objIdText); 95 | if (textState && textState.ack && textState.val) { 96 | if (displayTemp) { 97 | await setStateAsync(`awtrix-light.0.apps.${appName}.text`, { val: `${textState.val} - ${formatValue(temp, 2)} °C` }); 98 | } else { 99 | await setStateAsync(`awtrix-light.0.apps.${appName}.text`, { val: textState.val }); 100 | } 101 | } 102 | } catch (err) { 103 | console.error(err); 104 | } 105 | } 106 | 107 | on({ id: [objIdIcon, objIdText, objIdTemp], change: 'ne' }, refreshExpertApp); 108 | 109 | // Init on startup 110 | refreshExpertApp(); 111 | ``` 112 | -------------------------------------------------------------------------------- /docs/en/README.md: -------------------------------------------------------------------------------- 1 | ![Logo](../../admin/awtrix-light.png) 2 | 3 | # ioBroker.awtrix-light 4 | 5 | ## Requirements 6 | 7 | - nodejs 20 (or later) 8 | - js-controller 6.0.0 (or later) 9 | - Admin Adapter 7.4.10 (or later) 10 | - _Awtrix 3_ device with firmware _0.98_ (or later) - e.g. Ulanzi TC001 11 | 12 | Buy here: [Aliexpress.com](https://haus-auto.com/p/ali/UlanziTC001) or here: [ulanzi.de](https://haus-auto.com/p/ula/UlanziTC001) (Affiliate-Links) 13 | 14 | ## Getting started 15 | 16 | 1. Flash the firmware on your device and add it to your WiFi network - see [documentation](https://blueforcer.github.io/awtrix3/#/quickstart) 17 | 2. Install the awtrix-light adapter in ioBroker (and add a new instance) 18 | 3. Open the instance configuration and enter the IP address of the device in your local network 19 | 20 | ## FAQ 21 | 22 | **Can I use the adapter to disable the native apps (like battery state and sensor data)?** 23 | 24 | No, this feature has been removed in the awtrix light firmware. Please use the on screen menu to hide these apps. 25 | 26 | **Is it possible to replace boolean values with other text (not true/false)?** 27 | 28 | Just create an alias in `alias.0` of type `string` and convert your `boolean` value into any other text with a read function (like `val ? 'open' : 'closed'`). *This is an ioBroker feature and not related to this adapter.* 29 | 30 | **How can I update to the latest firmware version?** 31 | 32 | Just use the [onscreen menu](https://blueforcer.github.io/awtrix3/#/onscreen) and navigate to `update`. No need to use the web flasher again. 33 | 34 | **The device is getting hot while charging.** 35 | 36 | The hardware design is not the best. Please use a power supply which deliveres max. 1A. 37 | 38 | **Is it possible to remove the battery from the device?** 39 | 40 | Yes, but you have to open the case with a heat gun (since the front glued to the case) and [modify the PCB with a step down converter](https://github.com/Blueforcer/awtrix3/issues/67#issuecomment-1595418765). 41 | 42 | **Is it possible to re-order apps?** 43 | 44 | By default, apps are displayed in the same order as in the instance configuration. Just move an app up or down to change it's position. History apps are always positioned after all custom apps! 45 | 46 | To set custom positions for each app, the expert option `custom positions` has to be enabled. After that, it is possible to define a position on each app. 47 | 48 | **Can I define a custom number format?** 49 | 50 | All states (of common.type `number`) are formatted as configured in the system settings of ioBroker. It is possible to override the system format (since adapter version 0.7.1) by using an expert option. Numbers can be formatted in the following styles: 51 | 52 | - System default 53 | - `xx.xxx,xx` 54 | - `xx,xxx.xx` (US-Format) 55 | - `xxxxx,xx` 56 | - `xxxxx.xx` (US-Format) 57 | 58 | **Is it possible to protect web ui of the device?** 59 | 60 | Yes, since firware version 0.82 it is possible to configure a user name and a password to protect the access. Since adapter version 0.8.0 it is also possible to enter these credentials in the instance configuration. 61 | 62 | **How does the hold option in notifications work?** 63 | 64 | When sending a notification with `hold: true`, the text will stay on the display until the notification will be confirmed. This can either happen with a press on the middle button of the device, or by setting the state `notification.dismiss` to `true`. 65 | 66 | **Some state changes are not displayed immediately.** 67 | 68 | If your states changes very often (like every second), some changes will be ignored to prevent frequent requests to the device. Each app has a global "block time" which is configurable in the instance configuration. The default block time is 3 seconds. It is not recommended to set a lower value than 3. 69 | 70 | ## Same apps on multiple devices 71 | 72 | If you have multiple awtrix-light devices, **it is required to create a new instance for each device.** But it is possible to copy all app settings of another instance if you want to display the same information on all devices. Just select the other instance in the app configuration tab. 73 | 74 | Example: 75 | 76 | 1. Configure all apps in instance `awtrix-light.0` 77 | 2. Create a new instance for the second device (`awtrix-light.1`) 78 | 3. Choose `awtrix-light.0` in the instance configuration of `awtrix-light.1` to use the same apps on the second device 79 | 80 | Since version 0.15.0 (and later) the visibility of custom apps and contents of expert apps are also applied to other devices (when app settings are copied). In the example above the apps of `awtrix-light.1` will be hidden automatically if the visibility state of an app in instance `awtrix-light.0` changes. 81 | 82 | ## Blockly and JavaScript 83 | 84 | `sendTo` / message box can be used to 85 | 86 | - send one time notifications (with text, sound, icon, ...) 87 | - play a custom sound 88 | 89 | ### Notifications 90 | 91 | Send a "one time" notification to your device: 92 | 93 | ```javascript 94 | sendTo('awtrix-light.0', 'notification', { text: 'haus-automatisierung.com', repeat: 1, stack: true, wakeup: true, hold: false }, (res) => { 95 | if (res && res.error) { 96 | console.error(res.error); 97 | } 98 | }); 99 | ``` 100 | 101 | The message object supports all available options of the firmware. See [documentation](https://blueforcer.github.io/awtrix3/#/api?id=json-properties) for details. 102 | 103 | *You can also use a Blockly block to send a notification (doesn't provide all available options).* 104 | 105 | ### Sounds 106 | 107 | **The sound files must be saved as RTTTL fomat in the folder MELODIES. The file extension of these files is .txt. When playing those files, the file extension must not be provided.** 108 | 109 | To play a (previously created) sound file `example.txt`: 110 | 111 | ```javascript 112 | sendTo('awtrix-light.0', 'sound', { sound: 'example' }, (res) => { 113 | if (res && res.error) { 114 | console.error(res.error); 115 | } 116 | }); 117 | ``` 118 | 119 | The message object supports all available options of the firmware. See [documentation](https://blueforcer.github.io/awtrix3/#/api?id=sound-playback) for details. 120 | 121 | *You can also use a Blockly block to play a sound.* 122 | 123 | To play a custom ringtone: 124 | 125 | ```javascript 126 | sendTo('awtrix-light.0', 'rtttl', 'Beep: d=32,o=7,b=120: a,P,c#', (res) => { 127 | if (res && res.error) { 128 | console.error(res.error); 129 | } 130 | }); 131 | ``` 132 | 133 | ## Apps 134 | 135 | **App names must be lowercase (a-z) and unique. No numbers, no capital letters, no special characters, no whitespaces.** 136 | 137 | The following names are used by internal apps and cannot be used: `Time`, `Date`, `Temperature`, `Humidity`, `Battery`. 138 | 139 | - You can use the state `activate` of each app to bring that app to front 140 | - This state has the role `button` and allows just the value `true` (other values will raise a warning) 141 | 142 | Each custom and history app has a state `apps..visible`. If this state is set to `false`, the app will be removed from the device and no further updates are pushed. This is useful, if a certain app should only be displayed during day time or in a given time range. 143 | 144 | ### Custom apps 145 | 146 | - `%s` is a placeholder for the state value 147 | - `%u` is a placeholder for the unit of the state object (e.g. `°C`) 148 | 149 | It is possible to define a custom text with those placeholders (e.g. `Outside: %s %u`). 150 | 151 | **Custom apps just display acknowledged values! Control states with `ack: false` are ignored (to prevent duplicate requests and to ensure that values are valid / confirmed)!** 152 | 153 | The selected state should have the data type `string` or `number`. Other tyes (like `boolean`) are also supported but raise a warning. It is recommended to use an alias state with a convert function to replace a boolean value with text (e.g. `val ? 'on' : 'off'` or `val ? 'open' : 'closed'`). See ioBroker documentation for details. *This standard feature is not related to this adapter.* 154 | 155 | The following combinations will lead to a warning in the log: 156 | 157 | - A custom app with a selected object id of a state, but `%s` is missing in the text 158 | - A custom app with a selected object id of a state without a unit `common.unit`, but `%u` is used in the text 159 | - A custom app without a selected object, but `%s` has been used in the text 160 | 161 | ### History apps / graphs 162 | 163 | TODO 164 | 165 | **History apps just display acknowledged history values! Control states with `ack: false` are filtered and ignored!** 166 | 167 | ### Expert apps 168 | 169 | Expert apps are available since apdater version 0.10.0. They allow to set all values manually and to implement your own logic by controlling all data via states. To create a new expert app 170 | 171 | - Go to expert options in instance settings 172 | - Create a new expert app by choosing a name (e.g. `test`) 173 | - Save and close the instance settings 174 | 175 | After that, all controllable states for the app name `test` will be created in `awtrix-light.0.apps.test`. Just set values of `icon`, `text` and other states by using your own scripts and logic (e.g. JavaScript or Blockly). 176 | 177 | Example: [Weather App](weather-app.md) 178 | 179 | #### Base Object 180 | 181 | *Requires adapter version 2.0.0 (or newer)* 182 | 183 | The base object is a basic defition of an awtrix app to allow all possible attributes. *The base object will be extended with other attributes of the expert app.* 184 | 185 | Example: If you want to use the rainbow effect, but there is no state to set this feature directly, you can define this in the base object (as JSON): `{ "rainbow": true }`. 186 | 187 | See [documentation](https://blueforcer.github.io/awtrix3/#/api?id=custom-apps-and-notifications) for available attributes. 188 | 189 | ## Hide native apps 190 | 191 | If you want to disable/hide a native app (like battery, temperature or humidity): Use the on screen menu on the device! See [documentation](https://blueforcer.github.io/awtrix3/#/onscreen) for details. 192 | -------------------------------------------------------------------------------- /docs/en/weather-app.md: -------------------------------------------------------------------------------- 1 | ![Logo](../../admin/awtrix-light.png) 2 | 3 | # ioBroker.awtrix-light 4 | 5 | ## Weather App (Expert App) 6 | 7 | This script displays the correct icons for the current weather conditions in an expert app. To achieve this, the icons of [ioBroker.openweathermap](https://github.com/ioBroker/ioBroker.openweathermap) are mapped to icons stored on the device. 8 | 9 | *Thanks to Andy200877 (ioBroker forums) for the idea.* 10 | 11 | ### Icons 12 | 13 | Load the following icons on your device by using the web interface of the device: 14 | 15 | - `11201` (01d clear sky) - Clear sky day 16 | - `52163` (01n clear sky) - Clear sky night 17 | - `22315` (02d few clouds) - Few clouds day 18 | - `26088` (02n few clouds) - Few clouds night 19 | - `22378` (03d scattered clouds) - Scattered clouds day 20 | - `21907` (03n scattered clouds) - Scattered clouds night 21 | - `13852` (04d broken clouds) - Broken clouds day 22 | - `52159` (04n broken clouds) - Broken clouds night 23 | - `43706` (09d shower rain) - Shower rain day 24 | - `43739` (09n shower rain) - Shower rain night 25 | - `22257` (10d rain) - Rain day 26 | - ` 72` (10n rain) - Rain night 27 | - `43733` (11d thunderstorm) - Thunderstorm day 28 | - `43748` (11n thunderstorm) - Thunderstorm night 29 | - `43732` (13d snow) - Snow day 30 | - `26090` (13n snow) - Snow night 31 | - `43708` (50d mist) - Mist day 32 | - `43741` (50n mist) - Mist night 33 | 34 | ### New expert app 35 | 36 | Create a new expert app with the name `weather`. 37 | 38 | ### Script 39 | 40 | ```javascript 41 | // v0.2 42 | const displayTemp = true; 43 | 44 | const appName = 'weather'; 45 | const objIdIcon = 'openweathermap.0.forecast.current.icon'; 46 | const objIdText = 'openweathermap.0.forecast.current.state'; 47 | const objIdTemp = 'openweathermap.0.forecast.current.temperature'; 48 | 49 | const iconMapping = { 50 | '01d': '11201', // clear sky day 51 | '01n': '52163', // clear sky night 52 | '02d': '22315', // few clouds day 53 | '02n': '26088', // few clouds night 54 | '03d': '22378', // scattered clouds day 55 | '03n': '21907', // scattered clouds night 56 | '04d': '13852', // broken clouds day 57 | '04n': '52159', // broken clouds night 58 | '09d': '43706', // shower rain day 59 | '09n': '43739', // shower rain night 60 | '10d': '22257', // rain day 61 | '10n': '72', // rain night 62 | '11d': '43733', // thunderstorm day 63 | '11n': '43748', // thunderstorm night 64 | '13d': '43732', // snow day 65 | '13n': '26090', // snow night 66 | '50d': '43708', // mist day 67 | '50n': '43741', // mist night 68 | }; 69 | 70 | async function refreshExpertApp() { 71 | try { 72 | const iconState = await getStateAsync(objIdIcon); 73 | if (iconState && iconState.ack && iconState.val) { 74 | const icon = /([0-9]{2}[d|n]{1})/.exec(iconState.val)[0]; 75 | if (iconMapping[icon]) { 76 | await setStateAsync(`awtrix-light.0.apps.${appName}.icon`, { val: iconMapping[icon] }); 77 | } 78 | } 79 | 80 | let temp = 0; 81 | const tempState = await getStateAsync(objIdTemp); 82 | if (tempState && tempState.ack && tempState.val) { 83 | temp = tempState.val; 84 | } 85 | 86 | if (temp > 30) { 87 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#bd2020' }); 88 | } else if (temp < 0) { 89 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#236fd9' }); 90 | } else { 91 | await setStateAsync(`awtrix-light.0.apps.${appName}.textColor`, { val: '#ffffff' }); 92 | } 93 | 94 | const textState = await getStateAsync(objIdText); 95 | if (textState && textState.ack && textState.val) { 96 | if (displayTemp) { 97 | await setStateAsync(`awtrix-light.0.apps.${appName}.text`, { val: `${textState.val} - ${formatValue(temp, 2)} °C` }); 98 | } else { 99 | await setStateAsync(`awtrix-light.0.apps.${appName}.text`, { val: textState.val }); 100 | } 101 | } 102 | } catch (err) { 103 | console.error(err); 104 | } 105 | } 106 | 107 | on({ id: [objIdIcon, objIdText, objIdTemp], change: 'ne' }, refreshExpertApp); 108 | 109 | // Init on startup 110 | refreshExpertApp(); 111 | ``` 112 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const tsParser = require("@typescript-eslint/parser"); 2 | const js = require("@eslint/js"); 3 | 4 | const { 5 | FlatCompat, 6 | } = require("@eslint/eslintrc"); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | recommendedConfig: js.configs.recommended, 11 | allConfig: js.configs.all 12 | }); 13 | 14 | module.exports = [{ 15 | ignores: [ 16 | ".dev-server/**", 17 | "**/build/", 18 | "**/.prettierrc.js", 19 | "**/.eslintrc.js", 20 | "admin/blockly.js", 21 | "admin/words.js", 22 | "test/**", 23 | "eslint.config.cjs" 24 | ], 25 | }, ...compat.extends("plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"), { 26 | plugins: {}, 27 | 28 | languageOptions: { 29 | parser: tsParser, 30 | ecmaVersion: 2022, 31 | sourceType: "module", 32 | 33 | parserOptions: { 34 | project: "./tsconfig.json", 35 | }, 36 | }, 37 | 38 | rules: { 39 | "@typescript-eslint/no-parameter-properties": "off", 40 | "@typescript-eslint/no-explicit-any": "off", 41 | 42 | "@typescript-eslint/no-use-before-define": ["error", { 43 | functions: false, 44 | typedefs: false, 45 | classes: false, 46 | }], 47 | 48 | "@typescript-eslint/no-unused-vars": ["error", { 49 | ignoreRestSiblings: true, 50 | argsIgnorePattern: "^_", 51 | }], 52 | 53 | "@typescript-eslint/explicit-function-return-type": ["warn", { 54 | allowExpressions: true, 55 | allowTypedFunctionExpressions: true, 56 | }], 57 | 58 | "@typescript-eslint/no-object-literal-type-assertion": "off", 59 | "@typescript-eslint/interface-name-prefix": "off", 60 | "@typescript-eslint/no-non-null-assertion": "off", 61 | "@typescript-eslint/no-namespace": "off", 62 | "no-var": "error", 63 | "prefer-const": "error", 64 | "no-trailing-spaces": "error", 65 | }, 66 | }, { 67 | files: ["**/*.test.ts"], 68 | 69 | rules: { 70 | "@typescript-eslint/explicit-function-return-type": "off", 71 | }, 72 | }]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.awtrix-light", 3 | "version": "2.0.0", 4 | "description": "Integrate your Awtrix Light device (e.g. Ulanzi TC001) via HTTP ", 5 | "author": { 6 | "name": "Matthias Kleine", 7 | "email": "info@haus-automatisierung.com" 8 | }, 9 | "homepage": "https://github.com/klein0r/ioBroker.awtrix-light", 10 | "license": "MIT", 11 | "keywords": [ 12 | "ioBroker", 13 | "Smart Home", 14 | "home automation", 15 | "awtrix", 16 | "ulanzi", 17 | "tc001", 18 | "pixel-clock" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/klein0r/ioBroker.awtrix-light" 23 | }, 24 | "engines": { 25 | "node": ">=20" 26 | }, 27 | "dependencies": { 28 | "@iobroker/adapter-core": "^3.2.3", 29 | "axios": "^1.9.0" 30 | }, 31 | "devDependencies": { 32 | "@alcalzone/release-script": "^3.8.0", 33 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 34 | "@alcalzone/release-script-plugin-license": "^3.7.0", 35 | "@alcalzone/release-script-plugin-manual-review": "^3.7.0", 36 | "@eslint/eslintrc": "^3.3.1", 37 | "@eslint/js": "^9.27.0", 38 | "@iobroker/adapter-dev": "^1.4.0", 39 | "@iobroker/testing": "^5.0.4", 40 | "@tsconfig/node20": "^20.1.5", 41 | "@types/chai": "^5.2.2", 42 | "@types/chai-as-promised": "^8.0.2", 43 | "@types/mocha": "^10.0.10", 44 | "@types/node": "^22.15.21", 45 | "@types/proxyquire": "^1.3.31", 46 | "@types/sinon": "^17.0.4", 47 | "@types/sinon-chai": "^4.0.0", 48 | "@typescript-eslint/eslint-plugin": "^8.32.1", 49 | "@typescript-eslint/parser": "^8.32.1", 50 | "chai": "^4.5.0", 51 | "chai-as-promised": "^8.0.1", 52 | "eslint": "^9.27.0", 53 | "eslint-config-prettier": "^10.1.5", 54 | "eslint-plugin-prettier": "^5.4.0", 55 | "mocha": "^11.5.0", 56 | "prettier": "^3.5.3", 57 | "proxyquire": "^2.1.3", 58 | "rimraf": "^6.0.1", 59 | "sinon": "^20.0.0", 60 | "sinon-chai": "^3.7.0", 61 | "source-map-support": "^0.5.21", 62 | "ts-node": "^10.9.2", 63 | "typescript": "~5.8.3" 64 | }, 65 | "main": "build/main.js", 66 | "files": [ 67 | "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}", 68 | "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", 69 | "build/", 70 | "www/", 71 | "io-package.json", 72 | "LICENSE" 73 | ], 74 | "scripts": { 75 | "prebuild": "rimraf build", 76 | "build": "build-adapter ts", 77 | "watch": "build-adapter ts --watch", 78 | "prebuild:ts": "rimraf build", 79 | "build:ts": "build-adapter ts", 80 | "watch:ts": "build-adapter ts --watch", 81 | "test:ts": "mocha --config test/mocharc.custom.json src/**/*.test.ts", 82 | "test:package": "mocha test/package --exit", 83 | "test:integration": "mocha test/integration --exit", 84 | "test": "npm run test:ts && npm run test:package", 85 | "check": "tsc --noEmit", 86 | "lint": "eslint src/", 87 | "translate": "translate-adapter", 88 | "release": "release-script", 89 | "release-patch": "release-script patch --yes", 90 | "release-minor": "release-script minor --yes", 91 | "release-major": "release-script major --yes" 92 | }, 93 | "bugs": { 94 | "url": "https://github.com/klein0r/ioBroker.awtrix-light/issues" 95 | }, 96 | "readmeFilename": "README.md" 97 | } 98 | -------------------------------------------------------------------------------- /src/lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | 3 | export type DefaultApp = { 4 | name: string; 5 | position: number; 6 | }; 7 | 8 | export type CustomApp = DefaultApp & { 9 | icon: string; 10 | duration: number; 11 | repeat: number; 12 | text: string; 13 | objId: string; 14 | decimals: number; 15 | dynamicRound: boolean; 16 | rainbow: boolean; 17 | textColor: string; 18 | noScroll: boolean; 19 | scrollSpeed: number; 20 | useBackgroundEffect: boolean; 21 | backgroundColor: string; 22 | backgroundEffect: string; 23 | 24 | thresholdLtActive: boolean; 25 | thresholdLtValue: number; 26 | thresholdLtIcon: string; 27 | thresholdLtTextColor: string; 28 | thresholdLtBackgroundColor: string; 29 | 30 | thresholdGtActive: boolean; 31 | thresholdGtValue: number; 32 | thresholdGtIcon: string; 33 | thresholdGtTextColor: string; 34 | thresholdGtBackgroundColor: string; 35 | }; 36 | 37 | export type HistoryApp = DefaultApp & { 38 | icon: string; 39 | duration: number; 40 | repeat: number; 41 | sourceInstance: string; 42 | objId: string; 43 | lineColor: string; 44 | backgroundColor: string; 45 | display: 'bar' | 'line'; 46 | mode: 'last' | 'aggregate'; 47 | aggregation?: 'average' | 'min' | 'max' | 'count'; 48 | step?: number; 49 | }; 50 | 51 | export type ExpertApp = DefaultApp & { 52 | name: string; 53 | }; 54 | 55 | // Augment the globally declared type ioBroker.AdapterConfig 56 | declare global { 57 | namespace ioBroker { 58 | interface AdapterConfig { 59 | awtrixIp: string; 60 | userName: string; 61 | userPassword: string; 62 | downloadScreenContent: boolean; 63 | downloadScreenContentInterval: number; 64 | foreignSettingsInstance: string; 65 | foreignSettingsInstanceActivateApps: boolean; 66 | customApps: Array; 67 | ignoreNewValueForAppInTimeRange: number; 68 | historyApps: Array; 69 | historyAppsBackgroundColor: string; 70 | historyAppsRefreshInterval: number; 71 | autoDeleteForeignApps: boolean; 72 | removeAppsOnStop: boolean; 73 | httpTimeout: number; 74 | numberFormat: string; 75 | customPositions: boolean; 76 | expertApps: Array; 77 | } 78 | } 79 | } 80 | 81 | // this is required so the above AdapterConfig is found by TypeScript / type checking 82 | export {}; 83 | -------------------------------------------------------------------------------- /src/lib/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosResponse } from 'axios'; 2 | import { AwtrixLight } from '../main'; 3 | 4 | export namespace AwtrixApi { 5 | export type App = { 6 | text?: string; 7 | textCase?: number; 8 | topText?: boolean; 9 | textOffset?: number; 10 | center?: boolean; 11 | color?: string; 12 | gradient?: string; 13 | blinkText?: number; 14 | fadeText?: number; 15 | background?: string; 16 | rainbow?: boolean; 17 | icon?: string; 18 | pushIcon?: number; 19 | repeat?: number; 20 | duration?: number; 21 | bar?: Array; 22 | line?: Array; 23 | autoscale?: boolean; 24 | progress?: number; 25 | progressC?: string; 26 | progressBC?: string; 27 | pos?: number; 28 | draw?: Array; 29 | lifetime?: number; 30 | lifetimeMode?: number; 31 | noScroll?: boolean; 32 | scrollSpeed?: number; 33 | effect?: string; 34 | effectSettings?: Array; 35 | save?: boolean; 36 | }; 37 | 38 | export type Settings = { 39 | key: string; 40 | value: any; 41 | }; 42 | 43 | export type Indicator = { 44 | color?: string; 45 | blink?: number; 46 | fade?: number; 47 | }; 48 | 49 | export type Moodlight = { 50 | brightness?: number; 51 | color?: string; 52 | }; 53 | 54 | export class Client { 55 | private adapter: AwtrixLight; 56 | private axiosInstance: AxiosInstance | undefined = undefined; 57 | private apiConnected: boolean = false; 58 | private lastErrorCode: number = -1; 59 | 60 | public constructor(adapter: AwtrixLight, ipAddress: string, port: number, httpTimeout: number, userName: string, userPassword: string) { 61 | this.adapter = adapter; 62 | 63 | this.adapter.log.info(`Starting - connecting to http://${ipAddress}:${port}/`); 64 | 65 | let httpAuth: axios.AxiosBasicCredentials | undefined = undefined; 66 | if (userName) { 67 | httpAuth = { 68 | username: userName, 69 | password: userPassword, 70 | }; 71 | } 72 | 73 | this.axiosInstance = axios.create({ 74 | baseURL: `http://${ipAddress}:${port}/api/`, 75 | timeout: httpTimeout * 1000 || 3000, 76 | auth: httpAuth, 77 | validateStatus: (status) => { 78 | return [200, 201].indexOf(status) > -1; 79 | }, 80 | responseType: 'json', 81 | }); 82 | } 83 | 84 | public isConnected(): boolean { 85 | return this.apiConnected; 86 | } 87 | 88 | public async getStatsAsync(): Promise { 89 | return new Promise((resolve, reject) => { 90 | this.requestAsync('stats', 'GET') 91 | .then(async (response) => { 92 | if (response.status === 200) { 93 | this.apiConnected = true; 94 | resolve(response.data); 95 | } else { 96 | reject(response); 97 | } 98 | }) 99 | .catch((error) => { 100 | this.apiConnected = false; 101 | reject(error); 102 | }); 103 | }); 104 | } 105 | 106 | public async removeAppAsync(name: string): Promise { 107 | return new Promise((resolve, reject) => { 108 | if (this.apiConnected) { 109 | this.appRequestAsync(name) 110 | .then((response) => { 111 | if (response.status === 200 && response.data === 'OK') { 112 | this.adapter.log.debug(`[removeApp] Removed customApp app "${name}"`); 113 | resolve(true); 114 | } else { 115 | reject(`${response.status}: ${response.data}`); 116 | } 117 | }) 118 | .catch(reject); 119 | } else { 120 | reject('API not connected'); 121 | } 122 | }); 123 | } 124 | 125 | public async settingsRequestAsync(data: AwtrixApi.Settings): Promise { 126 | return this.requestAsync('settings', 'POST', { [data.key]: data.value }); 127 | } 128 | 129 | public async indicatorRequestAsync(index: number, data?: AwtrixApi.Indicator): Promise { 130 | return this.requestAsync(`indicator${index}`, 'POST', data); 131 | } 132 | 133 | public async appRequestAsync(name: string, data?: AwtrixApi.App): Promise { 134 | return this.requestAsync(`custom?name=${name}`, 'POST', data); 135 | } 136 | 137 | public async requestAsync(url: string, method?: string, data?: object | string): Promise { 138 | return new Promise((resolve, reject) => { 139 | if (data) { 140 | this.adapter.log.debug(`sending "${method}" request to "${url}" with data: ${JSON.stringify(data)}`); 141 | } else { 142 | this.adapter.log.debug(`sending "${method}" request to "${url}" without data`); 143 | } 144 | 145 | this.axiosInstance!.request({ 146 | url, 147 | method, 148 | data, 149 | headers: { 150 | 'Content-Type': typeof data === 'string' ? 'text/plain' : 'application/json', 151 | }, 152 | }) 153 | .then((response) => { 154 | this.adapter.log.debug(`received ${response.status} response from "${url}" with content: ${JSON.stringify(response.data)}`); 155 | 156 | // no error - clear up reminder 157 | this.lastErrorCode = -1; 158 | 159 | resolve(response); 160 | }) 161 | .catch((error) => { 162 | if (error.response) { 163 | // The request was made and the server responded with a status code 164 | 165 | if (error.response.status === 401) { 166 | this.adapter.log.warn('Unable to perform request. Looks like the device is protected with username / password. Check instance configuration!'); 167 | } else { 168 | this.adapter.log.warn(`received ${error.response.status} response from ${url} with content: ${JSON.stringify(error.response.data)}`); 169 | } 170 | } else if (error.request) { 171 | // The request was made but no response was received 172 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of 173 | // http.ClientRequest in node.js 174 | 175 | // avoid spamming of the same error when stuck in a reconnection loop 176 | if (error.code === this.lastErrorCode) { 177 | this.adapter.log.debug(error.message); 178 | } else { 179 | this.adapter.log.info(`error ${error.code} from ${url}: ${error.message}`); 180 | this.lastErrorCode = error.code; 181 | } 182 | } else { 183 | // Something happened in setting up the request that triggered an Error 184 | this.adapter.log.error(error.message); 185 | } 186 | 187 | reject(error); 188 | }); 189 | }); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/lib/app-type/abstract.ts: -------------------------------------------------------------------------------- 1 | import { AwtrixLight } from '../../main'; 2 | import { AwtrixApi } from '../api'; 3 | 4 | export namespace AppType { 5 | export abstract class AbstractApp { 6 | private name: string; 7 | 8 | protected apiClient: AwtrixApi.Client; 9 | protected adapter: AwtrixLight; 10 | 11 | protected objPrefix: string; 12 | 13 | public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) { 14 | this.name = name; 15 | 16 | this.apiClient = apiClient; 17 | this.adapter = adapter; 18 | 19 | if (this.adapter.isMainInstance()) { 20 | this.objPrefix = this.adapter.namespace; 21 | } else { 22 | this.objPrefix = this.adapter.config.foreignSettingsInstance; 23 | } 24 | 25 | adapter.on('stateChange', this.onStateChange.bind(this)); 26 | adapter.on('objectChange', this.onObjectChange.bind(this)); 27 | } 28 | 29 | public abstract getDescription(): string; 30 | 31 | public abstract getIconForObjectTree(): string; 32 | 33 | public getName(): string { 34 | return this.name; 35 | } 36 | 37 | public isMainInstance(): boolean { 38 | return this.adapter.isMainInstance(); 39 | } 40 | 41 | protected getObjIdOwnNamespace(id: string): string { 42 | return this.adapter.removeNamespace(this.isMainInstance() ? id : id.replace(this.objPrefix, this.adapter.namespace)); 43 | } 44 | 45 | private hasOwnActivateState(): boolean { 46 | return this.isMainInstance() || !this.adapter.config.foreignSettingsInstanceActivateApps; 47 | } 48 | 49 | public async createObjects(): Promise { 50 | const appName = this.getName(); 51 | 52 | this.adapter.log.debug(`[createObjects] Creating objects for app "${appName}" (${this.isMainInstance() ? 'main' : this.objPrefix})`); 53 | 54 | if (this.hasOwnActivateState()) { 55 | await this.adapter.extendObject(`apps.${appName}.activate`, { 56 | type: 'state', 57 | common: { 58 | name: { 59 | en: 'Activate', 60 | de: 'Aktivieren', 61 | ru: 'Активировать', 62 | pt: 'Ativar', 63 | nl: 'Activeren', 64 | fr: 'Activer', 65 | it: 'Attivare', 66 | es: 'Activar', 67 | pl: 'Aktywuj', 68 | uk: 'Активувати', 69 | 'zh-cn': '启用', 70 | }, 71 | type: 'boolean', 72 | role: 'button', 73 | read: false, 74 | write: true, 75 | }, 76 | native: {}, 77 | }); 78 | } else { 79 | await this.adapter.delObjectAsync(`apps.${appName}.activate`); 80 | await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.activate`); 81 | } 82 | } 83 | 84 | private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise { 85 | const appName = this.getName(); 86 | 87 | if (id) { 88 | this.adapter.log.debug(`[onStateChange] ${appName}: State change "${id}": ${JSON.stringify(state)}`); 89 | 90 | // Handle default states for all apps 91 | if (state && !state.ack) { 92 | // activate app 93 | if (id === `${this.hasOwnActivateState() ? this.adapter.namespace : this.objPrefix}.apps.${appName}.activate`) { 94 | if (state.val) { 95 | this.apiClient!.requestAsync('switch', 'POST', { name: appName }).catch((error) => { 96 | this.adapter.log.warn(`[onStateChange] ${appName}: (switch) Unable to execute action: ${error}`); 97 | }); 98 | } else { 99 | this.adapter.log.warn(`[onStateChange] ${appName}: Received invalid value for state ${id}`); 100 | } 101 | } 102 | } 103 | } 104 | 105 | await this.stateChanged(id, state); 106 | } 107 | 108 | /* eslint-disable @typescript-eslint/no-unused-vars */ 109 | protected async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise { 110 | // override 111 | } 112 | 113 | private async onObjectChange(id: string, obj: ioBroker.Object | null | undefined): Promise { 114 | await this.objectChanged(id, obj); 115 | } 116 | 117 | /* eslint-disable @typescript-eslint/no-unused-vars */ 118 | protected async objectChanged(id: string, obj: ioBroker.Object | null | undefined): Promise { 119 | // override 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/lib/app-type/native.ts: -------------------------------------------------------------------------------- 1 | import { AwtrixLight } from '../../main'; 2 | import { AwtrixApi } from '../api'; 3 | import { AppType as AbstractAppType } from './abstract'; 4 | 5 | export namespace AppType { 6 | export class Native extends AbstractAppType.AbstractApp { 7 | public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) { 8 | super(apiClient, adapter, name); 9 | } 10 | 11 | public override getDescription(): string { 12 | return 'native'; 13 | } 14 | 15 | public override getIconForObjectTree(): string { 16 | return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ5NS45IDE2Ni42YzMuMiA4LjcgLjUgMTguNC02LjQgMjQuNmwtNDMuMyAzOS40YzEuMSA4LjMgMS43IDE2LjggMS43IDI1LjRzLS42IDE3LjEtMS43IDI1LjRsNDMuMyAzOS40YzYuOSA2LjIgOS42IDE1LjkgNi40IDI0LjZjLTQuNCAxMS45LTkuNyAyMy4zLTE1LjggMzQuM2wtNC43IDguMWMtNi42IDExLTE0IDIxLjQtMjIuMSAzMS4yYy01LjkgNy4yLTE1LjcgOS42LTI0LjUgNi44bC01NS43LTE3LjdjLTEzLjQgMTAuMy0yOC4yIDE4LjktNDQgMjUuNGwtMTIuNSA1Ny4xYy0yIDkuMS05IDE2LjMtMTguMiAxNy44Yy0xMy44IDIuMy0yOCAzLjUtNDIuNSAzLjVzLTI4LjctMS4yLTQyLjUtMy41Yy05LjItMS41LTE2LjItOC43LTE4LjItMTcuOGwtMTIuNS01Ny4xYy0xNS44LTYuNS0zMC42LTE1LjEtNDQtMjUuNEw4My4xIDQyNS45Yy04LjggMi44LTE4LjYgLjMtMjQuNS02LjhjLTguMS05LjgtMTUuNS0yMC4yLTIyLjEtMzEuMmwtNC43LTguMWMtNi4xLTExLTExLjQtMjIuNC0xNS44LTM0LjNjLTMuMi04LjctLjUtMTguNCA2LjQtMjQuNmw0My4zLTM5LjRDNjQuNiAyNzMuMSA2NCAyNjQuNiA2NCAyNTZzLjYtMTcuMSAxLjctMjUuNEwyMi40IDE5MS4yYy02LjktNi4yLTkuNi0xNS45LTYuNC0yNC42YzQuNC0xMS45IDkuNy0yMy4zIDE1LjgtMzQuM2w0LjctOC4xYzYuNi0xMSAxNC0yMS40IDIyLjEtMzEuMmM1LjktNy4yIDE1LjctOS42IDI0LjUtNi44bDU1LjcgMTcuN2MxMy40LTEwLjMgMjguMi0xOC45IDQ0LTI1LjRsMTIuNS01Ny4xYzItOS4xIDktMTYuMyAxOC4yLTE3LjhDMjI3LjMgMS4yIDI0MS41IDAgMjU2IDBzMjguNyAxLjIgNDIuNSAzLjVjOS4yIDEuNSAxNi4yIDguNyAxOC4yIDE3LjhsMTIuNSA1Ny4xYzE1LjggNi41IDMwLjYgMTUuMSA0NCAyNS40bDU1LjctMTcuN2M4LjgtMi44IDE4LjYtLjMgMjQuNSA2LjhjOC4xIDkuOCAxNS41IDIwLjIgMjIuMSAzMS4ybDQuNyA4LjFjNi4xIDExIDExLjQgMjIuNCAxNS44IDM0LjN6TTI1NiAzMzZhODAgODAgMCAxIDAgMC0xNjAgODAgODAgMCAxIDAgMCAxNjB6Ii8+PC9zdmc+'; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/app-type/user.ts: -------------------------------------------------------------------------------- 1 | import { AwtrixLight } from '../../main'; 2 | import { DefaultApp } from '../adapter-config'; 3 | import { AwtrixApi } from '../api'; 4 | import { AppType as AbstractAppType } from './abstract'; 5 | 6 | export namespace AppType { 7 | export abstract class UserApp extends AbstractAppType.AbstractApp { 8 | private definition: DefaultApp; 9 | 10 | protected ignoreNewValueForAppInTimeRange: number; 11 | protected isVisible: boolean; 12 | 13 | public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, definition: DefaultApp) { 14 | super(apiClient, adapter, definition.name); 15 | 16 | this.definition = definition; 17 | this.ignoreNewValueForAppInTimeRange = adapter.config.ignoreNewValueForAppInTimeRange; 18 | this.isVisible = false; 19 | } 20 | 21 | public async init(): Promise { 22 | const appName = this.getName(); 23 | const appVisibleState = await this.adapter.getForeignStateAsync(`${this.objPrefix}.apps.${appName}.visible`); 24 | this.isVisible = appVisibleState ? !!appVisibleState.val : true; 25 | 26 | // Ack if changed while instance was stopped 27 | if (appVisibleState && !appVisibleState?.ack) { 28 | await this.adapter.setState(`apps.${appName}.visible`, { val: this.isVisible, ack: true, c: 'init' }); 29 | } 30 | 31 | return this.isVisible; 32 | } 33 | 34 | public async refresh(): Promise { 35 | if (!this.isVisible && this.apiClient.isConnected()) { 36 | // Hide app automatically 37 | const appName = this.getName(); 38 | this.apiClient.removeAppAsync(appName).catch((error) => { 39 | this.adapter.log.warn(`[refreshApp] Unable to remove hidden app "${appName}": ${error}`); 40 | }); 41 | } 42 | 43 | return this.isVisible && this.apiClient.isConnected(); 44 | } 45 | 46 | public async createObjects(): Promise { 47 | await super.createObjects(); 48 | 49 | const appName = this.getName(); 50 | 51 | await this.adapter.extendObject(`apps.${appName}.visible`, { 52 | type: 'state', 53 | common: { 54 | name: { 55 | en: 'Visible', 56 | de: 'Sichtbar', 57 | ru: 'Видимый', 58 | pt: 'Visível', 59 | nl: 'Vertaling', 60 | fr: 'Visible', 61 | it: 'Visibile', 62 | es: 'Visible', 63 | pl: 'Widoczny', 64 | uk: 'Вибрані', 65 | 'zh-cn': '不可抗辩', 66 | }, 67 | type: 'boolean', 68 | role: 'switch.enable', 69 | read: true, 70 | write: this.isMainInstance(), 71 | def: true, 72 | }, 73 | native: {}, 74 | }); 75 | 76 | if (!this.isMainInstance()) { 77 | await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.visible`); 78 | } 79 | } 80 | 81 | public async unloadAsync(): Promise { 82 | if (this.adapter.config.removeAppsOnStop) { 83 | this.adapter.log.info(`[onUnload] Deleting app on awtrix light with name "${this.definition.name}"`); 84 | 85 | try { 86 | await this.apiClient.removeAppAsync(this.definition.name).catch((error) => { 87 | this.adapter.log.warn(`Unable to remove unknown app "${this.definition.name}": ${error}`); 88 | }); 89 | } catch (error) { 90 | this.adapter.log.error(`[onUnload] Unable to delete app ${this.definition.name}: ${error}`); 91 | } 92 | } 93 | } 94 | 95 | protected override async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise { 96 | // Handle all states for user apps 97 | if (id && state && !state.ack) { 98 | const appName = this.getName(); 99 | const idOwnNamespace = this.getObjIdOwnNamespace(id); 100 | 101 | if (id === `${this.objPrefix}.apps.${appName}.visible`) { 102 | if (state.val !== this.isVisible) { 103 | this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app ${appName} changed to ${state.val}`); 104 | 105 | this.isVisible = !!state.val; 106 | 107 | await this.refresh(); 108 | await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix}` }); 109 | } else { 110 | this.adapter.log.debug(`[onStateChange] ${appName}: Visibility of app "${appName}" IGNORED (not changed): ${state.val}`); 111 | 112 | await this.adapter.setState(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix} (unchanged)` }); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/lib/app-type/user/history.ts: -------------------------------------------------------------------------------- 1 | import { AwtrixLight } from '../../../main'; 2 | import { HistoryApp } from '../../adapter-config'; 3 | import { AwtrixApi } from '../../api'; 4 | import { AppType as UserAppType } from '../user'; 5 | 6 | export namespace AppType { 7 | export type HistoryOptions = { 8 | start: number; 9 | end: number; 10 | limit: number; 11 | aggregate?: 'none' | 'average' | 'min' | 'max' | 'count'; 12 | step?: number; 13 | returnNewestEntries: boolean; 14 | ignoreNull: number; 15 | removeBorderValues: boolean; 16 | ack: boolean; 17 | }; 18 | 19 | export class History extends UserAppType.UserApp { 20 | private appDefinition: HistoryApp; 21 | private isValidSourceInstance: boolean; 22 | private isValidObjId: boolean; 23 | private refreshTimeout: ioBroker.Timeout | undefined; 24 | 25 | public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, definition: HistoryApp) { 26 | super(apiClient, adapter, definition); 27 | 28 | this.appDefinition = definition; 29 | this.isValidSourceInstance = false; 30 | this.isValidObjId = false; 31 | this.refreshTimeout = undefined; 32 | } 33 | 34 | public override getDescription(): string { 35 | return 'history'; 36 | } 37 | 38 | public override getIconForObjectTree(): string { 39 | return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTE2MCA4MGMwLTI2LjUgMjEuNS00OCA0OC00OGwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMzUyYzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTM1MnpNMCAyNzJjMC0yNi41IDIxLjUtNDggNDgtNDhsMzIgMGMyNi41IDAgNDggMjEuNSA0OCA0OGwwIDE2MGMwIDI2LjUtMjEuNSA0OC00OCA0OGwtMzIgMGMtMjYuNSAwLTQ4LTIxLjUtNDgtNDhMMCAyNzJ6TTM2OCA5NmwzMiAwYzI2LjUgMCA0OCAyMS41IDQ4IDQ4bDAgMjg4YzAgMjYuNS0yMS41IDQ4LTQ4IDQ4bC0zMiAwYy0yNi41IDAtNDgtMjEuNS00OC00OGwwLTI4OGMwLTI2LjUgMjEuNS00OCA0OC00OHoiLz48L3N2Zz4='; 40 | } 41 | 42 | public override async init(): Promise { 43 | if (this.appDefinition.sourceInstance) { 44 | const sourceInstanceObj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.appDefinition.sourceInstance}`); 45 | 46 | if (sourceInstanceObj && sourceInstanceObj.common?.getHistory) { 47 | const sourceInstanceAliveState = await this.adapter.getForeignStateAsync(`system.adapter.${this.appDefinition.sourceInstance}.alive`); 48 | 49 | if (sourceInstanceAliveState && sourceInstanceAliveState.val) { 50 | this.adapter.log.debug(`[initHistoryApp] Found valid source instance for history data: ${this.appDefinition.sourceInstance}`); 51 | 52 | this.isValidSourceInstance = true; 53 | } else { 54 | this.adapter.log.warn(`[initHistoryApp] Unable to get history data of "${this.appDefinition.sourceInstance}": instance not running (stopped)`); 55 | } 56 | } else { 57 | this.adapter.log.warn(`[initHistoryApp] Unable to get history data of "${this.appDefinition.sourceInstance}": no valid source for getHistory()`); 58 | } 59 | } 60 | 61 | if (this.appDefinition.objId) { 62 | this.adapter.log.debug(`[initHistoryApp] getting history data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}" from ${this.appDefinition.sourceInstance}`); 63 | 64 | try { 65 | if (this.isValidSourceInstance) { 66 | const sourceObj = await this.adapter.getForeignObjectAsync(this.appDefinition.objId); 67 | 68 | if (sourceObj && Object.prototype.hasOwnProperty.call(sourceObj?.common?.custom ?? {}, this.appDefinition.sourceInstance)) { 69 | this.isValidObjId = true; 70 | } else { 71 | this.adapter.log.info( 72 | `[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": logging is not configured for this object`, 73 | ); 74 | } 75 | } else { 76 | this.adapter.log.info(`[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": source invalid or unavailable`); 77 | } 78 | } catch (error) { 79 | this.adapter.log.error(`[initHistoryApp] Unable to get data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}": ${error}`); 80 | } 81 | } 82 | 83 | return super.init(); 84 | } 85 | 86 | public override async refresh(): Promise { 87 | let refreshed = false; 88 | 89 | if ((await super.refresh()) && this.isValidSourceInstance && this.isValidObjId) { 90 | const itemCount = this.appDefinition.icon ? 11 : 16; // can display 11 values with icon or 16 values without icon 91 | 92 | const options: HistoryOptions = { 93 | start: 1, 94 | end: Date.now(), 95 | limit: itemCount, 96 | returnNewestEntries: true, 97 | ignoreNull: 0, 98 | removeBorderValues: true, 99 | ack: true, 100 | }; 101 | 102 | if (this.appDefinition.mode == 'aggregate') { 103 | options.aggregate = this.appDefinition.aggregation; 104 | options.step = this.appDefinition.step ? this.appDefinition.step * 1000 : 3600; 105 | } else { 106 | // mode = last 107 | options.aggregate = 'none'; 108 | } 109 | 110 | const historyData = await this.adapter.sendToAsync(this.appDefinition.sourceInstance, 'getHistory', { 111 | id: this.appDefinition.objId, 112 | options, 113 | }); 114 | const graphData = (historyData as any)?.result 115 | .filter((state: ioBroker.State) => typeof state.val === 'number' && state.ack) 116 | .map((state: ioBroker.State) => Math.round(state.val as number)) 117 | .slice(itemCount * -1); 118 | 119 | this.adapter.log.debug( 120 | `[refreshHistoryApp] Data for app "${this.appDefinition.name}" of "${this.appDefinition.objId}: ${JSON.stringify(historyData)} - filtered: ${JSON.stringify(graphData)}`, 121 | ); 122 | 123 | if (graphData.length > 0) { 124 | const moreOptions: AwtrixApi.App = {}; 125 | 126 | // Duration 127 | if (this.appDefinition.duration > 0) { 128 | moreOptions.duration = this.appDefinition.duration; 129 | } 130 | 131 | // Repeat 132 | if (this.appDefinition.repeat > 0) { 133 | moreOptions.repeat = this.appDefinition.repeat; 134 | } 135 | 136 | // Bar or line graph 137 | if (this.appDefinition.display == 'bar') { 138 | moreOptions.bar = graphData; 139 | } else { 140 | moreOptions.line = graphData; 141 | } 142 | 143 | await this.apiClient!.appRequestAsync(this.appDefinition.name, { 144 | color: this.appDefinition.lineColor || '#FF0000', 145 | background: this.appDefinition.backgroundColor || '#000000', 146 | autoscale: true, 147 | icon: this.appDefinition.icon, 148 | lifetime: this.adapter.config.historyAppsRefreshInterval + 60, // Remove app if there is no update in configured interval (+ buffer) 149 | pos: this.appDefinition.position, 150 | ...moreOptions, 151 | }).catch((error) => { 152 | this.adapter.log.warn(`(custom?name=${this.appDefinition.name}) Unable to create app "${this.appDefinition.name}": ${error}`); 153 | }); 154 | 155 | refreshed = true; 156 | } else { 157 | this.adapter.log.debug(`[refreshHistoryApp] Going to remove app "${this.appDefinition.name}" (no history data)`); 158 | 159 | await this.apiClient!.removeAppAsync(this.appDefinition.name).catch((error) => { 160 | this.adapter.log.warn(`[refreshHistoryApp] Unable to remove app "${this.appDefinition.name}" (no history data): ${error}`); 161 | }); 162 | } 163 | } 164 | 165 | this.adapter.log.debug(`re-creating history apps timeout (${this.adapter.config.historyAppsRefreshInterval ?? 300} seconds)`); 166 | this.refreshTimeout = 167 | this.refreshTimeout || 168 | this.adapter.setTimeout( 169 | () => { 170 | this.refreshTimeout = undefined; 171 | this.refresh(); 172 | }, 173 | this.adapter.config.historyAppsRefreshInterval * 1000 || 5 * 60 * 1000, 174 | ); 175 | 176 | return refreshed; 177 | } 178 | 179 | public override async unloadAsync(): Promise { 180 | if (this.refreshTimeout) { 181 | this.adapter.log.debug(`clearing history app timeout for "${this.getName()}"`); 182 | this.adapter.clearTimeout(this.refreshTimeout); 183 | } 184 | 185 | await super.unloadAsync(); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/lib/color-convert.test.ts: -------------------------------------------------------------------------------- 1 | describe('Test color convertions', function () { 2 | /* 3 | it('rgb565 to rgb888', function () { 4 | this.timeout(5000); 5 | 6 | expect(rgb565to888(0xffff)).to.be.equal(0xffffff); 7 | expect(rgb565to888(0xef5d)).to.be.equal(0xefebef); 8 | expect(rgb565to888(0xf800)).to.be.equal(0xff0000); 9 | expect(rgb565to888(0x7e0)).to.be.equal(0x00ff00); 10 | expect(rgb565to888(0x1f)).to.be.equal(0x0000ff); 11 | }); 12 | 13 | it('rgb565 to rgb888 hex-string', function () { 14 | this.timeout(5000); 15 | 16 | expect(rgb565to888StrSvg(0xffff)).to.be.equal('#FFFFFF'); 17 | expect(rgb565to888StrSvg(0xef5d)).to.be.equal('#EFEBEF'); 18 | expect(rgb565to888StrSvg(0xf800)).to.be.equal('#FF0000'); 19 | expect(rgb565to888StrSvg(0x7e0)).to.be.equal('#00FF00'); 20 | expect(rgb565to888StrSvg(0x1f)).to.be.equal('#0000FF'); 21 | }); 22 | */ 23 | }); 24 | -------------------------------------------------------------------------------- /src/lib/color-convert.ts: -------------------------------------------------------------------------------- 1 | export function rgb565to888(val: number): number { 2 | const r = (val & 0xff0000) >> 16; 3 | const g = (val & 0x00ff00) >> 8; 4 | const b = val & 0x0000ff; 5 | 6 | return (r << 16) | (g << 8) | b; 7 | } 8 | 9 | export function rgb565to888Str(val: number): string { 10 | return '#' + rgb565to888(val).toString(16).toUpperCase().padStart(6, '0'); 11 | } 12 | -------------------------------------------------------------------------------- /src/main.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a dummy TypeScript test file using chai and mocha 3 | * 4 | * It's automatically excluded from npm and its build output is excluded from both git and npm. 5 | * It is advised to test all your modules with accompanying *.test.ts-files 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | // import { functionToTest } from "./moduleToTest"; 10 | 11 | describe('module to test => function to test', () => { 12 | // initializing logic 13 | const expected = 5; 14 | 15 | it(`should return ${expected}`, () => { 16 | const result = 5; 17 | // assign result a value from functionToTest 18 | expect(result).to.equal(expected); 19 | // or using the should() syntax 20 | result.should.equal(expected); 21 | }); 22 | // ... more tests => it 23 | }); 24 | 25 | // ... more test suites => describe 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Makes ts-node ignore warnings, so mocha --watch does work 4 | process.env.TS_NODE_IGNORE_WARNINGS = 'TRUE'; 5 | // Sets the correct tsconfig for testing 6 | process.env.TS_NODE_PROJECT = 'tsconfig.json'; 7 | // Make ts-node respect the "include" key in tsconfig.json 8 | process.env.TS_NODE_FILES = 'TRUE'; 9 | 10 | // Don't silently swallow unhandled rejections 11 | process.on('unhandledRejection', (e) => { 12 | throw e; 13 | }); 14 | 15 | // enable the should interface with sinon 16 | // and load chai-as-promised and sinon-chai by default 17 | const sinonChai = require('sinon-chai'); 18 | const chaiAsPromised = require('chai-as-promised'); 19 | const { should, use } = require('chai'); 20 | 21 | should(); 22 | use(sinonChai); 23 | use(chaiAsPromised); 24 | -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": ["test/mocha.setup.js", "ts-node/register", "source-map-support/register"], 3 | "watch-files": ["src/**/*.test.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": false 5 | }, 6 | "include": ["./**/*.js"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | // Specialized tsconfig to only compile .ts-files in the src dir 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "allowJs": false, 6 | "checkJs": false, 7 | "noEmit": false, 8 | "declaration": false 9 | }, 10 | "include": ["src/**/*.ts"], 11 | "exclude": ["src/**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Root tsconfig to set the settings and power editor support for all TS files 2 | { 3 | // To update the compilation target, install a different version of @tsconfig/node... and reference it here 4 | // https://github.com/tsconfig/bases#node-16-tsconfigjson 5 | "extends": "@tsconfig/node20/tsconfig.json", 6 | "compilerOptions": { 7 | // do not compile anything, this file is just to configure type checking 8 | // the compilation is configured in tsconfig.build.json 9 | "noEmit": true, 10 | 11 | // check JS files, but do not compile them => tsconfig.build.json 12 | "allowJs": true, 13 | "checkJs": true, 14 | 15 | "noEmitOnError": true, 16 | "outDir": "./build/", 17 | "removeComments": false, 18 | 19 | // This is necessary for the automatic typing of the adapter config 20 | "resolveJsonModule": true, 21 | 22 | // If you want to disable the stricter type checks (not recommended), uncomment the following line 23 | "strict": true, 24 | // And enable some of those features for more fine-grained control 25 | // "strictNullChecks": true, 26 | // "strictPropertyInitialization": true, 27 | // "strictBindCallApply": true, 28 | "noImplicitAny": true, 29 | // "noUnusedLocals": true, 30 | // "noUnusedParameters": true, 31 | // Uncomment this if you want the old behavior of catch variables being `any` 32 | "useUnknownInCatchVariables": false, 33 | 34 | "sourceMap": true, 35 | "inlineSourceMap": false 36 | }, 37 | "include": ["src/**/*.ts"], 38 | "exclude": ["build/**", "node_modules/**", "widgets/**"] 39 | } 40 | --------------------------------------------------------------------------------