├── .prettierignore ├── .eslintignore ├── .DS_Store ├── .vscode ├── extensions.json └── settings.json ├── admin ├── open-epaper-link.png ├── i18n │ ├── zh-cn │ │ └── translations.json │ ├── en │ │ └── translations.json │ ├── nl │ │ └── translations.json │ ├── uk │ │ └── translations.json │ ├── pl │ │ └── translations.json │ ├── ru │ │ └── translations.json │ ├── de │ │ └── translations.json │ ├── fr │ │ └── translations.json │ ├── pt │ │ └── translations.json │ ├── es │ │ └── translations.json │ └── it │ │ └── translations.json └── jsonConfig.json5 ├── .releaseconfig.json ├── test ├── tsconfig.json ├── mocharc.custom.json ├── package.js ├── integration.js ├── .eslintrc.json └── mocha.setup.js ├── .prettierrc.js ├── .gitignore ├── tsconfig.build.json ├── src ├── lib │ ├── adapter-config.d.ts │ └── objectDefinitions.ts ├── main.test.ts └── main.ts ├── .github ├── dependabot.yml ├── auto-merge.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── dependabot-auto-merge.yml │ └── test-and-release.yml ├── LICENSE ├── .create-adapter.json ├── tsconfig.json ├── .eslintrc.js ├── package.json ├── README.md └── io-package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | build/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .prettierrc.js 3 | **/.eslintrc.js 4 | admin/words.js -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrozmotiX/ioBroker.open-epaper-link/HEAD/.DS_Store -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /admin/open-epaper-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrozmotiX/ioBroker.open-epaper-link/HEAD/admin/open-epaper-link.png -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license", "manual-review"], 3 | "exec": { 4 | "before_commit": "npm run build" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": false 5 | }, 6 | "include": ["./**/*.js"] 7 | } 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | useTabs: true, 7 | tabWidth: 4, 8 | endOfLine: 'lf', 9 | }; 10 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.integration(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | 3 | // Augment the globally declared type ioBroker.AdapterConfig 4 | declare global { 5 | namespace ioBroker { 6 | interface AdapterConfig { 7 | option1: boolean; 8 | option2: string; 9 | } 10 | } 11 | } 12 | 13 | // this is required so the above AdapterConfig is found by TypeScript / type checking 14 | export {}; 15 | -------------------------------------------------------------------------------- /.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 | - DrozmotiX 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 | - DrozmotiX 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "eslint.enable": true, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "[typescript]": { 7 | "editor.codeActionsOnSave": { 8 | "source.organizeImports": true 9 | } 10 | }, 11 | "json.schemas": [ 12 | { 13 | "fileMatch": ["io-package.json"], 14 | "url": "https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/schemas/io-package.json" 15 | }, 16 | { 17 | "fileMatch": ["admin/jsonConfig.json", "admin/jsonCustom.json", "admin/jsonTab.json"], 18 | "url": "https://raw.githubusercontent.com/ioBroker/adapter-react-v5/main/schemas/jsonConfig.json" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": ["eslint:recommended"], 9 | "rules": { 10 | "indent": [ 11 | "error", 12 | "tab", 13 | { 14 | "SwitchCase": 1 15 | } 16 | ], 17 | "no-console": "off", 18 | "no-unused-vars": [ 19 | "error", 20 | { 21 | "ignoreRestSiblings": true, 22 | "argsIgnorePattern": "^_" 23 | } 24 | ], 25 | "no-var": "error", 26 | "no-trailing-spaces": "error", 27 | "prefer-const": "error", 28 | "quotes": [ 29 | "error", 30 | "single", 31 | { 32 | "avoidEscape": true, 33 | "allowTemplateLiterals": true 34 | } 35 | ], 36 | "semi": ["error", "always"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /admin/i18n/zh-cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "open-epaper-link 的适配器设置", 3 | "lbl_addUpdateAP": "添加接入点", 4 | "ttp_addUpdateAP": "将新的接入点添加到适配器", 5 | "hlp_addUpdateAP": "接入点(别名)名称", 6 | "lblDeleteAP": "删除接入点", 7 | "ttpDeleteAP": "必须选择接入点 IP", 8 | "hlpDeleteAP": "删除接入点", 9 | "lblDevicesTable": "连接的接入点", 10 | "ttlApName": "切入点", 11 | "ttpApName": "(别名)接入点名称", 12 | "ttlIP-Address": "接入点 IP 地址", 13 | "ttpIP-Address": "接入点 IP 地址", 14 | "ttlConnectState": "连接状态", 15 | "ttpConnectState": "与 Access Point 的 Websocket 连接状态", 16 | "lblLoadAccessPoints": "通过适配器加载所有已知接入点", 17 | "ttpLoadAccessPoints": "向后端发送命令以加载所有当前已知的接入点及其连接状态", 18 | "lblTxtNoButtonsIfNotActive": "适配器必须正在运行才能显示或添加连接的接入点", 19 | "lblApName": "接入点名称", 20 | "hlpApName": "lbl加载接入点", 21 | "lblApIP": "接入点 IP 地址", 22 | "ttpApIP": "接入点 IP 地址", 23 | "hlpApIP": "接入点 IP 地址" 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '...' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots & Logfiles** 23 | If applicable, add screenshots and logfiles to help explain your problem. 24 | 25 | **Versions:** 26 | - Adapter version: 27 | - JS-Controller version: 28 | - Node version: 29 | - Operating system: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 DutchmanNL 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 | -------------------------------------------------------------------------------- /.create-adapter.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": true, 3 | "target": "directory", 4 | "adapterName": "open-epaper-link", 5 | "title": "OpenEPaperLink", 6 | "description": "Alternative firmware and protocol for the ZBS243-based Electronic Shelf Labels - ESL / price tags by Solum / Samsung. It can be used to setup E-Paper tags and supply them with content.", 7 | "keywords": ["epaper", "openepaper", "openepaperlink"], 8 | "contributors": ["ticaki", "@slimline33"], 9 | "expert": "yes", 10 | "features": ["adapter"], 11 | "adminFeatures": [], 12 | "type": "hardware", 13 | "startMode": "daemon", 14 | "connectionType": "local", 15 | "dataSource": "push", 16 | "connectionIndicator": "yes", 17 | "language": "TypeScript", 18 | "nodeVersion": "18", 19 | "adminUi": "json", 20 | "tools": ["ESLint", "Prettier"], 21 | "releaseScript": "yes", 22 | "devServer": "no", 23 | "indentation": "Tab", 24 | "quotes": "single", 25 | "es6class": "yes", 26 | "authorName": "DutchmanNL", 27 | "authorGithub": "DrozmotiX", 28 | "authorEmail": "oss@DrozmotiX.eu", 29 | "gitRemoteProtocol": "HTTPS", 30 | "gitCommit": "yes", 31 | "defaultBranch": "main", 32 | "license": "MIT License", 33 | "dependabot": "yes", 34 | "creatorVersion": "2.5.0" 35 | } 36 | -------------------------------------------------------------------------------- /admin/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Adapter settings for open-epaper-link", 3 | "lbl_addUpdateAP" : "Add AccessPoint", 4 | "ttp_addUpdateAP" : "Add new AccessPoint to adapter", 5 | "hlp_addUpdateAP" : "AccessPoint (Alias) Name", 6 | "lblDeleteAP" : "Remove AccessPoint", 7 | "ttpDeleteAP" : "AccessPoint IP must be selected", 8 | "hlpDeleteAP" : "Remove AccessPoint", 9 | "lblDevicesTable" : "Connected AccessPoints", 10 | "ttlApName" : "Access Point", 11 | "ttlIP-Address" : "AccessPoint IP-Address", 12 | "ttpIP-Address" : "AccessPoint IP-Address", 13 | "ttlConnectState" : "Connection status", 14 | "ttpConnectState" : "Websocket connection status with Access Point", 15 | "lblLoadAccessPoints": "Load all by adpter known Access Points", 16 | "ttpLoadAccessPoints": "Sends command to backend to load all current known Access Points and their connection state", 17 | "lblTxtNoButtonsIfNotActive": "Adapter must be running to show or add connected Access Points", 18 | "lblApName": "AccessPoint Name", 19 | "ttpApName": "(Alias) Name of AccessPoint)", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "AccessPoint IP-Address", 22 | "ttpApIP": "AccessPoint IP-Address", 23 | "hlpApIP": "AccessPoint IP-Address" 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.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 | # In order to use this, you need to go to https://github.com/settings/tokens and 23 | # create a Personal Access Token with the permission "public_repo". 24 | # Enter this token in your repository settings under "Secrets" and name it AUTO_MERGE_TOKEN 25 | github-token: ${{ secrets.AUTO_MERGE_TOKEN }} 26 | # By default, squash and merge, so Github chooses nice commit messages 27 | command: squash and merge 28 | -------------------------------------------------------------------------------- /admin/i18n/nl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Adapterinstellingen voor open-epaper-link", 3 | "lbl_addUpdateAP": "AccessPoint toevoegen", 4 | "ttp_addUpdateAP": "Voeg een nieuw AccessPoint toe aan de adapter", 5 | "hlp_addUpdateAP": "AccessPoint-naam (alias).", 6 | "lblDeleteAP": "AccessPoint verwijderen", 7 | "ttpDeleteAP": "AccessPoint IP moet worden geselecteerd", 8 | "hlpDeleteAP": "AccessPoint verwijderen", 9 | "lblDevicesTable": "Verbonden toegangspunten", 10 | "ttlApName": "Toegangspunt", 11 | "ttpApName": "(alias) Naam van AccessPoint", 12 | "ttlIP-Address": "AccessPoint IP-adres", 13 | "ttpIP-Address": "AccessPoint IP-adres", 14 | "ttlConnectState": "Verbindingsstatus", 15 | "ttpConnectState": "Websocket-verbindingsstatus met toegangspunt", 16 | "lblLoadAccessPoints": "Laad alle door de adpter bekende toegangspunten", 17 | "ttpLoadAccessPoints": "Stuurt een opdracht naar de backend om alle huidige bekende toegangspunten en hun verbindingsstatus te laden", 18 | "lblTxtNoButtonsIfNotActive": "De adapter moet actief zijn om aangesloten toegangspunten weer te geven of toe te voegen", 19 | "lblApName": "Toegangspuntnaam", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "AccessPoint IP-adres", 22 | "ttpApIP": "AccessPoint IP-adres", 23 | "hlpApIP": "AccessPoint IP-adres" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/uk/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Налаштування адаптера для open-epaper-link", 3 | "lbl_addUpdateAP": "Додайте точку доступу", 4 | "ttp_addUpdateAP": "Додайте нову точку доступу до адаптера", 5 | "hlp_addUpdateAP": "Назва точки доступу (псевдонім).", 6 | "lblDeleteAP": "Видалити AccessPoint", 7 | "ttpDeleteAP": "Необхідно вибрати IP точки доступу", 8 | "hlpDeleteAP": "Видалити AccessPoint", 9 | "lblDevicesTable": "Підключені точки доступу", 10 | "ttlApName": "Точка доступу", 11 | "ttpApName": "(Псевдонім) Назва точки доступу", 12 | "ttlIP-Address": "IP-адреса точки доступу", 13 | "ttpIP-Address": "IP-адреса точки доступу", 14 | "ttlConnectState": "Статус підключення", 15 | "ttpConnectState": "Статус підключення Websocket до точки доступу", 16 | "lblLoadAccessPoints": "Завантажити всі відомі точки доступу адаптера", 17 | "ttpLoadAccessPoints": "Надсилає команду серверній частині, щоб завантажити всі поточні відомості про точки доступу та стан їх з’єднань", 18 | "lblTxtNoButtonsIfNotActive": "Щоб показати або додати підключені точки доступу, адаптер має бути запущено", 19 | "lblApName": "Ім'я точки доступу", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "IP-адреса точки доступу", 22 | "ttpApIP": "IP-адреса точки доступу", 23 | "hlpApIP": "IP-адреса точки доступу" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/pl/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Ustawienia adaptera dla open-epaper-link", 3 | "lbl_addUpdateAP": "Dodaj punkt dostępowy", 4 | "ttp_addUpdateAP": "Dodaj nowy AccessPoint do adaptera", 5 | "hlp_addUpdateAP": "Nazwa punktu dostępu (alias).", 6 | "lblDeleteAP": "Usuń AccessPoint", 7 | "ttpDeleteAP": "Należy wybrać adres IP punktu dostępowego", 8 | "hlpDeleteAP": "Usuń AccessPoint", 9 | "lblDevicesTable": "Połączone punkty dostępowe", 10 | "ttlApName": "Punkt dostępu", 11 | "ttpApName": "(Alias) Nazwa punktu dostępowego", 12 | "ttlIP-Address": "Adres IP punktu dostępowego", 13 | "ttpIP-Address": "Adres IP punktu dostępowego", 14 | "ttlConnectState": "Status połączenia", 15 | "ttpConnectState": "Stan połączenia Websocket z punktem dostępowym", 16 | "lblLoadAccessPoints": "Załaduj wszystkie znane punkty dostępu według adaptera", 17 | "ttpLoadAccessPoints": "Wysyła polecenie do backendu, aby załadować wszystkie znane punkty dostępu i stan ich połączeń", 18 | "lblTxtNoButtonsIfNotActive": "Aby wyświetlić lub dodać podłączone punkty dostępowe, adapter musi być uruchomiony", 19 | "lblApName": "Nazwa punktu dostępu", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "Adres IP punktu dostępowego", 22 | "ttpApIP": "Adres IP punktu dostępowego", 23 | "hlpApIP": "Adres IP punktu dostępowego" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Настройки адаптера для open-epaper-link", 3 | "lbl_addUpdateAP": "Добавить точку доступа", 4 | "ttp_addUpdateAP": "Добавьте новую точку доступа к адаптеру", 5 | "hlp_addUpdateAP": "Имя точки доступа (псевдоним)", 6 | "lblDeleteAP": "Удалить точку доступа", 7 | "ttpDeleteAP": "IP-адрес точки доступа должен быть выбран.", 8 | "hlpDeleteAP": "Удалить точку доступа", 9 | "lblDevicesTable": "Подключенные точки доступа", 10 | "ttlApName": "Точка доступа", 11 | "ttpApName": "(Псевдоним) Имя точки доступа", 12 | "ttlIP-Address": "IP-адрес точки доступа", 13 | "ttpIP-Address": "IP-адрес точки доступа", 14 | "ttlConnectState": "Статус подключения", 15 | "ttpConnectState": "Статус соединения Websocket с точкой доступа", 16 | "lblLoadAccessPoints": "Загрузка всех известных точек доступа адаптера", 17 | "ttpLoadAccessPoints": "Отправляет команду на серверную часть для загрузки всех текущих известных точек доступа и состояния их соединений.", 18 | "lblTxtNoButtonsIfNotActive": "Адаптер должен быть запущен, чтобы отображать или добавлять подключенные точки доступа.", 19 | "lblApName": "Имя точки доступа", 20 | "hlpApName": "лбллоадаксесспоинтс", 21 | "lblApIP": "IP-адрес точки доступа", 22 | "ttpApIP": "IP-адрес точки доступа", 23 | "hlpApIP": "IP-адрес точки доступа" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Adaptereinstellungen für open-epaper-link", 3 | "lbl_addUpdateAP": "AccessPoint hinzufügen", 4 | "ttp_addUpdateAP": "Fügen Sie dem Adapter einen neuen AccessPoint hinzu", 5 | "hlp_addUpdateAP": "AccessPoint-(Alias-)Name", 6 | "lblDeleteAP": "AccessPoint entfernen", 7 | "ttpDeleteAP": "AccessPoint IP muss ausgewählt werden", 8 | "hlpDeleteAP": "AccessPoint entfernen", 9 | "lblDevicesTable": "Verbundene AccessPoints", 10 | "ttlApName": "Zugangspunkt", 11 | "ttpApName": "(Alias) Name des AccessPoints", 12 | "ttlIP-Address": "AccessPoint IP-Adresse", 13 | "ttpIP-Address": "AccessPoint IP-Adresse", 14 | "ttlConnectState": "Verbindungsstatus", 15 | "ttpConnectState": "Websocket-Verbindungsstatus mit Access Point", 16 | "lblLoadAccessPoints": "Laden Sie alle vom Adapter bekannten Access Points", 17 | "ttpLoadAccessPoints": "Sendet einen Befehl an das Backend, um alle aktuell bekannten Access Points und deren Verbindungsstatus zu laden", 18 | "lblTxtNoButtonsIfNotActive": "Der Adapter muss ausgeführt werden, um verbundene Access Points anzuzeigen oder hinzuzufügen", 19 | "lblApName": "AccessPoint-Name", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "AccessPoint IP-Adresse", 22 | "ttpApIP": "AccessPoint IP-Adresse", 23 | "hlpApIP": "AccessPoint IP-Adresse" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Paramètres de l'adaptateur pour open-epaper-link", 3 | "lbl_addUpdateAP": "Ajouter un point d'accès", 4 | "ttp_addUpdateAP": "Ajouter un nouveau point d'accès à l'adaptateur", 5 | "hlp_addUpdateAP": "Nom du point d'accès (alias)", 6 | "lblDeleteAP": "Supprimer le point d'accès", 7 | "ttpDeleteAP": "AccessPoint IP doit être sélectionné", 8 | "hlpDeleteAP": "Supprimer le point d'accès", 9 | "lblDevicesTable": "Points d'accès connectés", 10 | "ttlApName": "Point d'accès", 11 | "ttpApName": "(Alias) Nom du point d'accès", 12 | "ttlIP-Address": "Adresse IP du point d'accès", 13 | "ttpIP-Address": "Adresse IP du point d'accès", 14 | "ttlConnectState": "Statut de connexion", 15 | "ttpConnectState": "État de la connexion Websocket avec Access Point", 16 | "lblLoadAccessPoints": "Tout charger par adaptateur de points d'accès connus", 17 | "ttpLoadAccessPoints": "Envoie une commande au backend pour charger tous les points d'accès connus actuels et l'état de leurs connexions", 18 | "lblTxtNoButtonsIfNotActive": "L'adaptateur doit être en cours d'exécution pour afficher ou ajouter des points d'accès connectés", 19 | "lblApName": "Nom du point d'accès", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "Adresse IP du point d'accès", 22 | "ttpApIP": "Adresse IP du point d'accès", 23 | "hlpApIP": "Adresse IP du point d'accès" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/pt/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Configurações do adaptador para open-epaper-link", 3 | "lbl_addUpdateAP": "Adicionar ponto de acesso", 4 | "ttp_addUpdateAP": "Adicione novo AccessPoint ao adaptador", 5 | "hlp_addUpdateAP": "Nome do ponto de acesso (alias)", 6 | "lblDeleteAP": "Remover ponto de acesso", 7 | "ttpDeleteAP": "O IP do ponto de acesso deve ser selecionado", 8 | "hlpDeleteAP": "Remover ponto de acesso", 9 | "lblDevicesTable": "Pontos de acesso conectados", 10 | "ttlApName": "Ponto de acesso", 11 | "ttpApName": "(Alias) Nome do Ponto de Acesso", 12 | "ttlIP-Address": "Endereço IP do ponto de acesso", 13 | "ttpIP-Address": "Endereço IP do ponto de acesso", 14 | "ttlConnectState": "Status da conexão", 15 | "ttpConnectState": "Status da conexão Websocket com ponto de acesso", 16 | "lblLoadAccessPoints": "Carregue tudo por adaptador de pontos de acesso conhecidos", 17 | "ttpLoadAccessPoints": "Envia comando ao back-end para carregar todos os pontos de acesso conhecidos atuais e o estado de suas conexões", 18 | "lblTxtNoButtonsIfNotActive": "O adaptador deve estar em execução para mostrar ou adicionar pontos de acesso conectados", 19 | "lblApName": "Nome do ponto de acesso", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "Endereço IP do ponto de acesso", 22 | "ttpApIP": "Endereço IP do ponto de acesso", 23 | "hlpApIP": "Endereço IP do ponto de acesso" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Configuración del adaptador para open-epaper-link", 3 | "lbl_addUpdateAP": "Agregar punto de acceso", 4 | "ttp_addUpdateAP": "Agregar un nuevo punto de acceso al adaptador", 5 | "hlp_addUpdateAP": "Nombre del punto de acceso (alias)", 6 | "lblDeleteAP": "Eliminar punto de acceso", 7 | "ttpDeleteAP": "Se debe seleccionar la IP de AccessPoint", 8 | "hlpDeleteAP": "Eliminar punto de acceso", 9 | "lblDevicesTable": "Puntos de acceso conectados", 10 | "ttlApName": "Punto de acceso", 11 | "ttpApName": "(Alias) Nombre del punto de acceso", 12 | "ttlIP-Address": "Dirección IP de AccessPoint", 13 | "ttpIP-Address": "Dirección IP de AccessPoint", 14 | "ttlConnectState": "Estado de conexión", 15 | "ttpConnectState": "Estado de conexión de Websocket con punto de acceso", 16 | "lblLoadAccessPoints": "Cargar todo por adaptador de puntos de acceso conocidos", 17 | "ttpLoadAccessPoints": "Envía un comando al backend para cargar todos los puntos de acceso conocidos actuales y el estado de sus conexiones.", 18 | "lblTxtNoButtonsIfNotActive": "El adaptador debe estar ejecutándose para mostrar o agregar puntos de acceso conectados", 19 | "lblApName": "Nombre del punto de acceso", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "Dirección IP de AccessPoint", 22 | "ttpApIP": "Dirección IP de AccessPoint", 23 | "hlpApIP": "Dirección IP de AccessPoint" 24 | } 25 | -------------------------------------------------------------------------------- /admin/i18n/it/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "open-epaper-link adapter settings": "Impostazioni dell'adattatore per open-epaper-link", 3 | "lbl_addUpdateAP": "Aggiungi punto di accesso", 4 | "ttp_addUpdateAP": "Aggiungi un nuovo AccessPoint all'adattatore", 5 | "hlp_addUpdateAP": "Nome del punto di accesso (alias).", 6 | "lblDeleteAP": "Rimuovi punto di accesso", 7 | "ttpDeleteAP": "È necessario selezionare l'IP AccessPoint", 8 | "hlpDeleteAP": "Rimuovi punto di accesso", 9 | "lblDevicesTable": "Punti di accesso connessi", 10 | "ttlApName": "Punto di accesso", 11 | "ttpApName": "(Alias) Nome dell'AccessPoint", 12 | "ttlIP-Address": "Indirizzo IP del punto di accesso", 13 | "ttpIP-Address": "Indirizzo IP del punto di accesso", 14 | "ttlConnectState": "Stato della connessione", 15 | "ttpConnectState": "Stato della connessione Websocket con Access Point", 16 | "lblLoadAccessPoints": "Carica tutto tramite i punti di accesso conosciuti dell'adattatore", 17 | "ttpLoadAccessPoints": "Invia il comando al backend per caricare tutti gli access point attualmente conosciuti e lo stato delle loro connessioni", 18 | "lblTxtNoButtonsIfNotActive": "L'adattatore deve essere in esecuzione per mostrare o aggiungere punti di accesso connessi", 19 | "lblApName": "Nome del punto di accesso", 20 | "hlpApName": "lblLoadAccessPoints", 21 | "lblApIP": "Indirizzo IP del punto di accesso", 22 | "ttpApIP": "Indirizzo IP del punto di accesso", 23 | "hlpApIP": "Indirizzo IP del punto di accesso" 24 | } 25 | -------------------------------------------------------------------------------- /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-18-tsconfigjson 5 | "extends": "@tsconfig/node18/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": false, 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 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, // Don't look outside this project for inherited configs 3 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 4 | parserOptions: { 5 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features 6 | sourceType: 'module', // Allows for the use of imports 7 | project: './tsconfig.json', 8 | }, 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 11 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 12 | ], 13 | plugins: [], 14 | rules: { 15 | '@typescript-eslint/no-parameter-properties': 'off', 16 | '@typescript-eslint/no-explicit-any': 'off', 17 | '@typescript-eslint/no-use-before-define': [ 18 | 'error', 19 | { 20 | functions: false, 21 | typedefs: false, 22 | classes: false, 23 | }, 24 | ], 25 | '@typescript-eslint/no-unused-vars': [ 26 | 'error', 27 | { 28 | ignoreRestSiblings: true, 29 | argsIgnorePattern: '^_', 30 | }, 31 | ], 32 | '@typescript-eslint/explicit-function-return-type': [ 33 | 'warn', 34 | { 35 | allowExpressions: true, 36 | allowTypedFunctionExpressions: true, 37 | }, 38 | ], 39 | '@typescript-eslint/no-object-literal-type-assertion': 'off', 40 | '@typescript-eslint/interface-name-prefix': 'off', 41 | '@typescript-eslint/no-non-null-assertion': 'off', // This is necessary for Map.has()/get()! 42 | 'no-var': 'error', 43 | 'prefer-const': 'error', 44 | 'no-trailing-spaces': 'error', 45 | }, 46 | overrides: [ 47 | { 48 | files: ['*.test.ts'], 49 | rules: { 50 | '@typescript-eslint/explicit-function-return-type': 'off', 51 | }, 52 | }, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.open-epaper-link", 3 | "version": "0.1.0", 4 | "description": "ioBroker integration for alternative firmware and protocol for the ZBS243-based Electronic Shelf Labels - ESL / price tags by Solum / Samsung. It can be used to setup E-Paper tags and supply them with content.", 5 | "author": { 6 | "name": "DutchmanNL", 7 | "email": "oss@DrozmotiX.eu" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "ticaki" 12 | }, 13 | { 14 | "name": "slimline33" 15 | } 16 | ], 17 | "homepage": "https://github.com/DrozmotiX/ioBroker.open-epaper-link", 18 | "license": "MIT", 19 | "keywords": [ 20 | "epaper", 21 | "openepaper", 22 | "openepaperlink" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/DrozmotiX/ioBroker.open-epaper-link.git" 27 | }, 28 | "engines": { 29 | "node": ">= 16" 30 | }, 31 | "dependencies": { 32 | "@iobroker/adapter-core": "^3.0.4", 33 | "iobroker-jsonexplorer": "^0.1.14", 34 | "websocket": "^1.0.34", 35 | "ws": "^8.14.2" 36 | }, 37 | "devDependencies": { 38 | "@alcalzone/release-script": "^3.6.0", 39 | "@alcalzone/release-script-plugin-iobroker": "^3.6.0", 40 | "@alcalzone/release-script-plugin-license": "^3.5.9", 41 | "@alcalzone/release-script-plugin-manual-review": "^3.5.9", 42 | "@iobroker/adapter-dev": "^1.2.0", 43 | "@iobroker/dev-server": "^0.7.1", 44 | "@iobroker/testing": "^4.1.0", 45 | "@tsconfig/node18": "^18.2.2", 46 | "@types/chai": "^4.3.11", 47 | "@types/chai-as-promised": "^7.1.8", 48 | "@types/mocha": "^10.0.6", 49 | "@types/node": "^20.10.0", 50 | "@types/proxyquire": "^1.3.31", 51 | "@types/sinon": "^17.0.2", 52 | "@types/sinon-chai": "^3.2.12", 53 | "@types/ws": "^8.5.10", 54 | "@typescript-eslint/eslint-plugin": "^6.12.0", 55 | "@typescript-eslint/parser": "^6.12.0", 56 | "chai": "^4.3.10", 57 | "chai-as-promised": "^7.1.1", 58 | "eslint": "^8.54.0", 59 | "eslint-config-prettier": "^9.0.0", 60 | "eslint-plugin-prettier": "^5.0.1", 61 | "mocha": "^10.2.0", 62 | "prettier": "^3.1.0", 63 | "proxyquire": "^2.1.3", 64 | "rimraf": "^5.0.5", 65 | "sinon": "^17.0.1", 66 | "sinon-chai": "^3.7.0", 67 | "source-map-support": "^0.5.21", 68 | "ts-node": "^10.9.1", 69 | "typescript": "~5.3.2" 70 | }, 71 | "main": "build/main.js", 72 | "files": [ 73 | "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}", 74 | "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", 75 | "build/", 76 | "www/", 77 | "io-package.json", 78 | "LICENSE" 79 | ], 80 | "scripts": { 81 | "startDev": "dev-server watch --noStart", 82 | "prebuild": "rimraf build", 83 | "build": "build-adapter ts", 84 | "watch": "build-adapter ts --watch", 85 | "prebuild:ts": "rimraf build", 86 | "build:ts": "build-adapter ts", 87 | "watch:ts": "build-adapter ts --watch", 88 | "test:ts": "mocha --config test/mocharc.custom.json src/**/*.test.ts", 89 | "test:package": "mocha test/package --exit", 90 | "test:integration": "mocha test/integration --exit", 91 | "test": "npm run test:ts && npm run test:package", 92 | "check": "tsc --noEmit", 93 | "lint": "eslint --ext .ts src/", 94 | "translate": "translate-adapter", 95 | "release": "release-script" 96 | }, 97 | "bugs": { 98 | "url": "https://github.com/DrozmotiX/ioBroker.open-epaper-link/issues" 99 | }, 100 | "readmeFilename": "README.md" 101 | } 102 | -------------------------------------------------------------------------------- /.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 | - "main" 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: '18.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: [16.x, 18.x, 20.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 | # TODO: To enable automatic npm releases, create a token on npmjs.org 57 | # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options 58 | # Then uncomment the following block: 59 | 60 | # Deploys the final package to NPM 61 | deploy: 62 | needs: [check-and-lint, adapter-tests] 63 | 64 | # Trigger this step only when a commit on any branch is tagged with a version number 65 | if: | 66 | contains(github.event.head_commit.message, '[skip ci]') == false && 67 | github.event_name == 'push' && 68 | startsWith(github.ref, 'refs/tags/v') 69 | 70 | runs-on: ubuntu-latest 71 | 72 | # Write permissions are required to create Github releases 73 | permissions: 74 | contents: write 75 | 76 | steps: 77 | - uses: ioBroker/testing-action-deploy@v1 78 | with: 79 | node-version: '16.x' 80 | # Uncomment the following line if your adapter cannot be installed using 'npm ci' 81 | # install-command: 'npm install' 82 | npm-token: ${{ secrets.NPM_TOKEN }} 83 | github-token: ${{ secrets.AUTOMERGER }} 84 | 85 | # # When using Sentry for error reporting, Sentry can be informed about new releases 86 | # # To enable create a API-Token in Sentry (User settings, API keys) 87 | # # Enter this token as a GitHub secret (with name SENTRY_AUTH_TOKEN) in the repository options 88 | # # Then uncomment and customize the following block: 89 | # sentry: true 90 | # sentry-token: ${{ secrets.SENTRY_AUTH_TOKEN }} 91 | # sentry-project: "iobroker-open-epaper-link" 92 | # sentry-version-prefix: "iobroker.open-epaper-link" 93 | # sentry-sourcemap-paths: "build/" 94 | # # If your sentry project is linked to a GitHub repository, you can enable the following option 95 | # # sentry-github-integration: true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo-iobroker-oepl_5 2 | 3 | # ioBroker.open-epaper-link 4 | 5 | [![NPM version](https://img.shields.io/npm/v/iobroker.open-epaper-link.svg)](https://www.npmjs.com/package/iobroker.open-epaper-link) 6 | [![Downloads](https://img.shields.io/npm/dm/iobroker.open-epaper-link.svg)](https://www.npmjs.com/package/iobroker.open-epaper-link) 7 | ![Number of Installations](https://iobroker.live/badges/open-epaper-link-installed.svg) 8 | ![Current version in stable repository](https://iobroker.live/badges/open-epaper-link-stable.svg) 9 | 10 | [![NPM](https://nodei.co/npm/iobroker.open-epaper-link.png?downloads=true)](https://nodei.co/npm/iobroker.open-epaper-link/) 11 | 12 | **Tests:** ![Test and Release](https://github.com/DrozmotiX/ioBroker.open-epaper-link/workflows/Test%20and%20Release/badge.svg) 13 | 14 | ## open-epaper-link adapter for ioBroker 15 | 16 | Alternative firmware and protocol for the ZBS243-based Electronic Shelf Labels - ESL / price tags by Solum / Samsung. It can be used to setup E-Paper tags and supply them with content. 17 | See GitHub Project https://github.com/jjwbruijn/OpenEPaperLink 18 | 19 | ![image](https://github.com/DrozmotiX/iobroker.open-epaper-link/assets/3323812/7670ef2b-ab15-47c0-8bf8-dc70a9bdbf32) 20 | 21 | The adapter facilitates communication between the OEPL Access Point and iobroker to seamlessly integrate and interact. The resources offer crucial insights for efficient system operation. 22 | 23 | It integrates all OpenEPaperLink displays and Access Points into the iobroker object structure, organizing them based on their MAC addresses. 24 | 25 | Within the iobroker system, a central "openepaperlink" folder groups all connected devices, each with its unique structure for targeted management. 26 | 27 | This integration ensures a clear representation of all OpenEPaperLink devices in the iobroker system, allowing efficient management and control through the iobroker interface. 28 | 29 | **For more information about OpenEPaperLink, valuable insights can be found at:** 30 | 31 | https://www.openepaperlink.de and https://github.com/jjwbruijn/OpenEPaperLink 32 | 33 | **Additionally, comprehensive tutorials and demonstrations about OpenEPaperLink are available in various YouTube videos by ATC1441, including:** 34 | 35 | https://www.youtube.com/watch?v=Etonkolz9Bs and https://www.youtube.com/watch?v=98fOzZs__fc 36 | 37 | These resources offer a wealth of information and guidance on understanding and utilizing OpenEPaperLink effectively. 38 | 39 | > [!WARNING] Development status 40 | > This repository currently only has a basic "meta" adapter source and admin configuration framework which will be translated to a working solution during the next weeks. 41 | 42 | ## Changelog 43 | 47 | 48 | ### **WORK IN PROGRESS** 49 | * (DutchmanNL) Ensure correct folder root for tag states 50 | 51 | ### 0.1.0 (2023-11-26) 52 | * (ticaki / DutchmanNL) initial release 53 | * (DutchmanNL) Connect to Access Points and receive their data 54 | * (DutchmanNL) Object structure to reflect Access Points and their connected tags 55 | 56 | ## License 57 | MIT License 58 | 59 | Copyright (c) 2023 DutchmanNL 60 | 61 | Permission is hereby granted, free of charge, to any person obtaining a copy 62 | of this software and associated documentation files (the "Software"), to deal 63 | in the Software without restriction, including without limitation the rights 64 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 65 | copies of the Software, and to permit persons to whom the Software is 66 | furnished to do so, subject to the following conditions: 67 | 68 | The above copyright notice and this permission notice shall be included in all 69 | copies or substantial portions of the Software. 70 | 71 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 72 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | SOFTWARE. 78 | -------------------------------------------------------------------------------- /admin/jsonConfig.json5: -------------------------------------------------------------------------------- 1 | { 2 | "i18n": true, 3 | "type": "panel", 4 | "items": { 5 | 6 | // Enable/disable autodiscovery and store setting in adapter config object 7 | // Currenlty not implemented 8 | // "autodiscovery": { 9 | // "newLine": true, 10 | // "disabled": "true", 11 | // "type": "checkbox", 12 | // "sm": 4, 13 | // "md": 4, 14 | // "label": "lblAutoDiscovery" 15 | // }, 16 | 17 | // Refresh table showing all devices 18 | // Sends message to backend to get all current known devices, their configuration and connection status 19 | "loadAccessPoints": { 20 | "newLine": true, 21 | "type": "sendTo", 22 | "hidden": "!_alive", 23 | "command": "loadAccessPoints", 24 | "jsonData": "{ \"date\": \"${data}\"}", 25 | "label": "lblLoadAccessPoints", 26 | "tooltip": "ttpLoadAccessPoints", 27 | "useNative": true, 28 | "variant": "outlined", 29 | "showProcess" : true, 30 | "xs": 12, 31 | "sm": 12, 32 | "md": 12, 33 | "lg": 12 34 | }, 35 | 36 | // Text to explain adapter must be running to load this table 37 | "details-1": { 38 | "newLine": true, 39 | "type": "staticText", 40 | "text": "lblTxtNoButtonsIfNotActive", 41 | "hidden": "_alive", 42 | "xs": 12, 43 | "sm": 12, 44 | "md": 12, 45 | "lg": 12 46 | }, 47 | 48 | // Input fields to ADD / Modify devices 49 | "apName": { 50 | "freeSolo": true, 51 | "newLine": true, 52 | "type": "autocompleteSendTo", 53 | "command": "getApName", 54 | "jsonData": "{ \"service\": \"${data.apName}\" }", 55 | "label": "lblApName", 56 | "default": "", 57 | "tooltip": "ttpApName", 58 | "help": "hlpApName", 59 | "xs": 6, 60 | "sm": 6, 61 | "md": 6, 62 | "lg": 6 63 | }, 64 | "apIP": { 65 | "freeSolo": true, 66 | "type": "autocompleteSendTo", 67 | "command": "getApIP", 68 | "jsonData": "{ \"apIP\": \"${data.apIP}\" }", 69 | "label": "lblApIP", 70 | "default": "", 71 | "tooltip": "ttpApIP", 72 | "help": "hlpApIP", 73 | "xs": 6, 74 | "sm": 6, 75 | "md": 6, 76 | "lg": 6 77 | }, 78 | 79 | // Button do ADD / Modify devices, sends 80 | // Sends device Name and IP 81 | "_addUpdateAP": { 82 | "newLine": true, 83 | "type": "sendTo", 84 | "label": "lbl_addUpdateAP", 85 | "variant": "outlined", 86 | "showProcess" : true, 87 | "command": "_addUpdateAP", 88 | "jsonData": "{ \"apName\": \"${data.apName}\", \"apIP\": \"${data.apIP}\" }", 89 | "useNative": true, 90 | "disabled": "data.apName==='' || data.apIP===''", 91 | "hidden": "!_alive", 92 | "tooltip": "ttp_addUpdateAP", 93 | "help": "hlp_addUpdateAP", 94 | "xs": 6, 95 | "sm": 6, 96 | "md": 6, 97 | "lg": 6 98 | }, 99 | 100 | "deleteAP": { 101 | "type": "sendTo", 102 | "label": "lblDeleteAP", 103 | "variant": "outlined", 104 | "showProcess" : true, 105 | "command": "deleteAP", 106 | "jsonData": "{ \"apName\": \"${data.apName}\", \"apIP\": \"${data.apIP}\" }", 107 | "useNative": true, 108 | "disabled": "data.apIP===''", 109 | "hidden": "!_alive", 110 | "tooltip": "ttpDeleteAP", 111 | "help": "hlpDeleteAP", 112 | "xs": 6, 113 | "sm": 6, 114 | "md": 6, 115 | "lg": 6, 116 | "confirm" : { 117 | "text" : "Are you sure to delete this AP ? Alle related states will be removed", 118 | "title" : "Confirm device Deletion", 119 | "ok" : "Delete Device", 120 | "cancel" : "Cancel", 121 | "type" : "warning" 122 | } 123 | }, 124 | 125 | // Table (provided by backend) of all devices and their connection status 126 | "accessPointTable": { 127 | "type": "table", 128 | "noDelete": true, 129 | "newLine": true, 130 | "xs": 12, 131 | "sm": 12, 132 | "md": 5, 133 | "lg": 12, 134 | "hidden": "data._templateTable[0].template == 'template.NewMessage'", 135 | "label": "lblDevicesTable", 136 | "showSecondAddAt": 5, 137 | "items": [ 138 | { 139 | "type": "text", 140 | "readOnly" : true, 141 | "attr": "apName", 142 | "width": "15% ", 143 | "title": "ttlApName", 144 | "tooltip": "ttpApName", 145 | "filter": false, 146 | "sort": false, 147 | "default": "", 148 | "validatorNoSaveOnError": true 149 | }, 150 | { 151 | "type": "text", 152 | "attr": "ip", 153 | "readOnly" : true, 154 | "width": "15% ", 155 | "title": "ttlIP-Address", 156 | "tooltip": "ttpIP-Address", 157 | "filter": false, 158 | "sort": false, 159 | "default": "", 160 | "validatorNoSaveOnError": true 161 | }, 162 | { 163 | "type": "text", 164 | "readOnly" : true, 165 | "attr": "connectState", 166 | "width": "15% ", 167 | "title": "ttlConnectState", 168 | "tooltip": "ttpConnectState", 169 | "filter": false, 170 | "sort": false, 171 | "default": "", 172 | "validatorNoSaveOnError": true 173 | } 174 | 175 | ] 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/lib/objectDefinitions.ts: -------------------------------------------------------------------------------- 1 | type MyObjects = Record; 2 | type StateAttr = { 3 | [key: string]: { 4 | def?: boolean | string | number | null; 5 | name?: string; 6 | states?: string | Record | string[]; 7 | type: ioBroker.CommonType; 8 | role?: string; 9 | /** Unit of the state */ 10 | unit?: string; 11 | /** if this state is writable */ 12 | write?: boolean; 13 | }; 14 | }; 15 | 16 | interface MyObjectsDefinitions extends Omit { 17 | common: MyStateCommon; 18 | } 19 | 20 | interface MyStateCommon extends Partial { 21 | name?: string; 22 | } 23 | 24 | const stateAttrb: StateAttr = { 25 | currtime: { 26 | name: 'Current Time', 27 | role: 'indicator.alarm', 28 | type: 'number', 29 | write: true, 30 | def: 0, 31 | }, 32 | wifissid: { 33 | type: 'string', 34 | role: 'info', 35 | write: false, 36 | }, 37 | mac: { 38 | type: 'string', 39 | name: 'MAC Address', 40 | role: 'state', 41 | write: false, 42 | }, 43 | hash: { 44 | type: 'string', 45 | name: 'Hash', 46 | role: 'state', 47 | write: false, 48 | }, 49 | lastseen: { 50 | type: 'number', 51 | name: 'Last Seen', 52 | role: 'state', 53 | write: false, 54 | }, 55 | nextupdate: { 56 | type: 'number', 57 | name: 'Next Update', 58 | role: 'state', 59 | write: false, 60 | }, 61 | nextcheckin: { 62 | type: 'number', 63 | name: 'Next Check-in', 64 | role: 'state', 65 | write: false, 66 | }, 67 | pending: { 68 | type: 'boolean', 69 | name: 'Pending', 70 | role: 'state', 71 | write: false, 72 | }, 73 | alias: { 74 | type: 'string', 75 | name: 'Alias', 76 | role: 'state', 77 | write: false, 78 | }, 79 | contentMode: { 80 | type: 'number', 81 | name: 'Content Mode', 82 | role: 'state', 83 | write: false, 84 | }, 85 | LQI: { 86 | type: 'number', 87 | name: 'Link Quality Indicator (LQI)', 88 | 89 | role: 'state', 90 | write: false, 91 | }, 92 | RSSI: { 93 | type: 'number', 94 | name: 'Received Signal Strength Indicator (RSSI)', 95 | role: 'state', 96 | write: false, 97 | }, 98 | temperature: { 99 | type: 'number', 100 | name: 'Temperature', 101 | role: 'state', 102 | write: false, 103 | }, 104 | batteryMv: { 105 | type: 'number', 106 | name: 'Battery Voltage', 107 | role: 'state', 108 | write: false, 109 | }, 110 | hwType: { 111 | type: 'number', 112 | name: 'Hardware Type', 113 | role: 'state', 114 | write: false, 115 | }, 116 | wakeupReason: { 117 | type: 'number', 118 | name: 'Wakeup Reason', 119 | role: 'state', 120 | write: false, 121 | }, 122 | capabilities: { 123 | type: 'number', 124 | name: 'Capabilities', 125 | role: 'state', 126 | write: false, 127 | }, 128 | modecfgjson: { 129 | type: 'string', 130 | name: 'Mode Configuration JSON', 131 | role: 'state', 132 | write: false, 133 | }, 134 | isexternal: { 135 | type: 'boolean', 136 | name: 'Is External', 137 | role: 'state', 138 | write: false, 139 | }, 140 | apip: { 141 | type: 'string', 142 | name: 'API IP', 143 | role: 'state', 144 | write: false, 145 | }, 146 | rotate: { 147 | type: 'number', 148 | name: 'Rotate', 149 | role: 'state', 150 | write: false, 151 | }, 152 | lut: { 153 | type: 'number', 154 | name: 'Lookup Table (LUT)', 155 | role: 'state', 156 | write: false, 157 | }, 158 | invert: { 159 | type: 'number', 160 | name: 'Invert', 161 | role: 'state', 162 | write: false, 163 | }, 164 | ch: { 165 | type: 'number', 166 | name: 'Channel', 167 | role: 'state', 168 | write: false, 169 | }, 170 | ver: { 171 | type: 'number', 172 | name: 'Version', 173 | role: 'state', 174 | write: false, 175 | }, 176 | }; 177 | 178 | const BasicStates: MyObjects = { 179 | Configuration: { 180 | type: 'channel', 181 | common: { 182 | name: 'Configuration', 183 | }, 184 | native: {}, 185 | }, 186 | Features: { 187 | type: 'channel', 188 | common: { 189 | name: 'Available features', 190 | }, 191 | native: {}, 192 | }, 193 | Info: { 194 | type: 'channel', 195 | common: { 196 | name: 'Information', 197 | }, 198 | native: {}, 199 | }, 200 | Sensors: { 201 | type: 'channel', 202 | common: { 203 | name: 'Information', 204 | }, 205 | native: {}, 206 | }, 207 | 'Configuration.checkupdate': { 208 | type: 'state', 209 | common: { 210 | name: 'Check for updates', 211 | type: 'boolean', 212 | read: true, 213 | write: true, 214 | role: 'button', 215 | def: false, 216 | }, 217 | native: {}, 218 | }, 219 | 'Configuration.restart': { 220 | type: 'state', 221 | common: { 222 | name: 'Restart Device', 223 | type: 'boolean', 224 | read: true, 225 | write: true, 226 | role: 'button', 227 | def: false, 228 | }, 229 | native: {}, 230 | }, 231 | 'Configuration.update': { 232 | type: 'state', 233 | common: { 234 | name: 'Execute update', 235 | type: 'boolean', 236 | read: true, 237 | write: true, 238 | role: 'button', 239 | def: false, 240 | }, 241 | native: {}, 242 | }, 243 | 'Info.connected': { 244 | type: 'state', 245 | common: { 246 | name: 'Device connected', 247 | type: 'boolean', 248 | read: true, 249 | write: false, 250 | role: 'info.connected', 251 | def: false, 252 | }, 253 | native: {}, 254 | }, 255 | }; 256 | 257 | function buildCommon(stateName: string): MyObjectsDefinitions { 258 | const obj: MyObjectsDefinitions = { 259 | type: 'state', 260 | common: { 261 | name: stateName, 262 | type: 'mixed', 263 | read: true, 264 | write: false, 265 | role: 'state', 266 | }, 267 | native: {}, 268 | }; 269 | 270 | if (stateAttrb[stateName] != null) { 271 | if (stateAttrb[stateName].def != null) { 272 | obj.common.def = stateAttrb[stateName].def; 273 | } 274 | if (stateAttrb[stateName].name != null) { 275 | obj.common.name = stateAttrb[stateName].name; 276 | } 277 | if (stateAttrb[stateName].unit != null) { 278 | obj.common.unit = stateAttrb[stateName].unit; 279 | } 280 | 281 | obj.common.role = stateAttrb[stateName].role; 282 | obj.common.type = stateAttrb[stateName].type; 283 | 284 | if (stateAttrb[stateName].write != null) { 285 | obj.common.write = stateAttrb[stateName].write; 286 | } 287 | 288 | if (stateAttrb[stateName].states != null) { 289 | obj.common.states = stateAttrb[stateName].states; 290 | } 291 | } 292 | return obj; 293 | } 294 | 295 | export { stateAttrb, BasicStates, MyObjectsDefinitions, buildCommon, MyObjects }; 296 | -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "open-epaper-link", 4 | "version": "0.1.0", 5 | "news": { 6 | "0.1.0": { 7 | "en": "initial release\nConnect to Access Points and receive their data\nObject structure to reflect Access Points and their connected tags", 8 | "de": "Erstveröffentlichung\nVerbinden Sie mit Access Points und erhalten Sie Ihre Daten\nObjektstruktur, um Access Points und ihre verbundenen Tags zu reflektieren", 9 | "ru": "начальный выпуск\nПодключитесь к точкам доступа и получите их данные\nСтруктура объекта для отражения точек доступа и их подключенных тегов", 10 | "pt": "lançamento inicial\nConecte-se a Pontos de Acesso e receba seus dados\nEstrutura de objetos para refletir pontos de acesso e suas tags conectadas", 11 | "nl": "Eerste release\nVerbinding met Access Points en ontvang hun gegevens\nObject structuur om Access Points te reflecteren en hun verbonden tags", 12 | "fr": "initial release\nConnectez-vous aux points d'accès et recevez leurs données\nStructure d'objet pour refléter les points d'accès et leurs étiquettes connectées", 13 | "it": "rilascio iniziale\nCollegarsi ai punti di accesso e ricevere i loro dati\nStruttura oggetti per riflettere Access Points e i loro tag collegati", 14 | "es": "liberación inicial\nConectar a Puntos de Acceso y recibir sus datos\nEstructura de objetos para reflejar puntos de acceso y sus etiquetas conectadas", 15 | "pl": "pierwsze wydanie\nPołączenia do Dostępu i otrzymaniu danych\nStruktura obiektywna odzwierciedla punkty dostępu i ich powiązane z nimi elementy", 16 | "uk": "початковий реліз\nПідключення до точок доступу та отримання їх даних\nСтруктура об'єкта для відображення точок доступу та їх підключених тегів", 17 | "zh-cn": "初步释放\n获取点并获得数据\n反映出入点及其相关传染病的客观结构" 18 | }, 19 | "0.0.1": { 20 | "en": "initial release", 21 | "de": "Erstveröffentlichung", 22 | "ru": "Начальная версия", 23 | "pt": "lançamento inicial", 24 | "nl": "Eerste uitgave", 25 | "fr": "Première version", 26 | "it": "Versione iniziale", 27 | "es": "Versión inicial", 28 | "pl": "Pierwsze wydanie", 29 | "uk": "Початкова версія", 30 | "zh-cn": "首次出版" 31 | } 32 | }, 33 | "title": "OpenEPaperLink", 34 | "titleLang": { 35 | "en": "OpenEPaperLink", 36 | "de": "OpenEPaperLink", 37 | "ru": "OpenEPaperLink", 38 | "pt": "OpenEPaperLink", 39 | "nl": "OpenEPaperLink", 40 | "fr": "OuvrirEPaperLink", 41 | "it": "OpenEPaperLink", 42 | "es": "OpenEPaperLink", 43 | "pl": "Otwórz EPaperLink", 44 | "uk": "OpenEPaperLink", 45 | "zh-cn": "打开电子纸链接" 46 | }, 47 | "desc": { 48 | "en": "Alternative firmware and protocol for the ZBS243-based Electronic Shelf Labels - ESL / price tags by Solum / Samsung. It can be used to setup E-Paper tags and supply them with content.", 49 | "de": "Alternative Firmware und Protokoll für die ZBS243-basierten elektronischen Regaletiketten – ESL/Preisschilder von Solum/Samsung. Damit lassen sich E-Paper-Tags einrichten und mit Inhalten versorgen.", 50 | "ru": "Альтернативная прошивка и протокол для электронных ценников на базе ZBS243 - ESL/ценники от Solum/Samsung. Его можно использовать для настройки тегов электронной бумаги и снабжения их контентом.", 51 | "pt": "Firmware e protocolo alternativo para etiquetas de prateleira eletrônicas baseadas em ZBS243 - ESL / etiquetas de preços da Solum / Samsung. Ele pode ser usado para configurar tags de E-Paper e fornecer conteúdo a elas.", 52 | "nl": "Alternatieve firmware en protocol voor de op ZBS243 gebaseerde elektronische planklabels - ESL / prijskaartjes van Solum / Samsung. Het kan worden gebruikt om E-Paper-tags in te stellen en deze van inhoud te voorzien.", 53 | "fr": "Micrologiciel et protocole alternatifs pour les étiquettes électroniques pour étagères basées sur ZBS243 - ESL / étiquettes de prix de Solum / Samsung. Il peut être utilisé pour configurer les balises E-Paper et leur fournir du contenu.", 54 | "it": "Firmware e protocollo alternativi per le etichette elettroniche per scaffali basate su ZBS243 - ESL / cartellini dei prezzi di Solum / Samsung. Può essere utilizzato per impostare tag E-Paper e fornire loro contenuti.", 55 | "es": "Firmware y protocolo alternativos para etiquetas electrónicas para estantes basadas en ZBS243 - ESL / etiquetas de precios de Solum / Samsung. Se puede utilizar para configurar etiquetas de papel electrónico y proporcionarles contenido.", 56 | "pl": "Alternatywne oprogramowanie i protokół dla elektronicznych etykiet półkowych opartych na ZBS243 - ESL / metki firmy Solum / Samsung. Można go używać do konfigurowania znaczników e-papieru i dostarczania im treści.", 57 | "uk": "Альтернативне мікропрограмне забезпечення та протокол для електронних етикеток на полиці на основі ZBS243 - ESL / цінники від Solum / Samsung. Його можна використовувати для налаштування тегів E-Paper і надання їм вмісту.", 58 | "zh-cn": "基于 ZBS243 的电子货架标签的替代固件和协议 - Solum / Samsung 的 ESL/价格标签。它可用于设置电子纸标签​​并为其提供内容。" 59 | }, 60 | "authors": [ 61 | "DutchmanNL " 62 | ], 63 | "keywords": [ 64 | "epaper", 65 | "openepaper", 66 | "openepaperlink" 67 | ], 68 | "license": "MIT", 69 | "platform": "Javascript/Node.js", 70 | "main": "build/main.js", 71 | "icon": "open-epaper-link.png", 72 | "enabled": true, 73 | "extIcon": "https://raw.githubusercontent.com/DrozmotiX/ioBroker.open-epaper-link/main/admin/open-epaper-link.png", 74 | "readme": "https://github.com/DrozmotiX/ioBroker.open-epaper-link/blob/main/README.md", 75 | "loglevel": "info", 76 | "mode": "daemon", 77 | "type": "hardware", 78 | "compact": true, 79 | "connectionType": "local", 80 | "dataSource": "push", 81 | "adminUI": { 82 | "config": "json" 83 | }, 84 | "messagebox": true, 85 | "subscribe": "messagebox", 86 | "dependencies": [ 87 | { 88 | "js-controller": ">=3.3.22" 89 | } 90 | ], 91 | "globalDependencies": [ 92 | { 93 | "admin": ">=5.1.13" 94 | } 95 | ] 96 | }, 97 | "native": { 98 | "option1": true, 99 | "option2": "42" 100 | }, 101 | "objects": [], 102 | "instanceObjects": [ 103 | { 104 | "_id": "info", 105 | "type": "channel", 106 | "common": { 107 | "name": "Information" 108 | }, 109 | "native": {} 110 | }, 111 | { 112 | "_id": "info.connection", 113 | "type": "state", 114 | "common": { 115 | "role": "indicator.connected", 116 | "name": "Device or service connected", 117 | "type": "boolean", 118 | "read": true, 119 | "write": false, 120 | "def": false 121 | }, 122 | "native": {} 123 | } 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with @iobroker/create-adapter v2.5.0 3 | */ 4 | 5 | type ApConnection = { 6 | [key: string]: { 7 | ip: string; 8 | connection: WebSocket; 9 | connectionStatus: string; 10 | deviceName: string; 11 | }; 12 | }; 13 | 14 | // The adapter-core module gives you access to the core ioBroker functions 15 | // you need to create an adapter 16 | import * as utils from '@iobroker/adapter-core'; 17 | import WebSocket from 'ws'; 18 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 19 | // @ts-expect-error 20 | const apConnection: ApConnection = []; 21 | const messageResponse: any = {}; 22 | 23 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 24 | // @ts-expect-error 25 | import jsonExplorer from 'iobroker-jsonexplorer'; // Use jsonExplorer library 26 | import { stateAttrb } from './lib/objectDefinitions'; 27 | 28 | // Load your modules here, e.g.: 29 | // import * as fs from "fs"; 30 | 31 | class OpenEpaperLink extends utils.Adapter { 32 | public constructor(options: Partial = {}) { 33 | super({ 34 | ...options, 35 | name: 'open-epaper-link', 36 | }); 37 | this.on('ready', this.onReady.bind(this)); 38 | this.on('stateChange', this.onStateChange.bind(this)); 39 | this.on('message', this.onMessage.bind(this)); 40 | this.on('unload', this.onUnload.bind(this)); 41 | jsonExplorer.init(this, stateAttrb); // Initiate library to handle JSOn data & state creation 42 | } 43 | 44 | /** 45 | * Is called when databases are connected and adapter received configuration. 46 | */ 47 | private async onReady(): Promise { 48 | // Initialize your adapter here 49 | 50 | // Reset the connection indicator during startup 51 | this.setState('info.connection', false, true); 52 | 53 | // Try to connect to known devices 54 | await this.tryKnownDevices(); 55 | this.setState('info.connection', true, true); 56 | } 57 | 58 | // Try to contact and read data of already known devices 59 | private async tryKnownDevices(): Promise { 60 | try { 61 | // Get all current devices from adapter tree 62 | this.log.info(`Try to connect to know devices`); 63 | const knownDevices = await this.getDevicesAsync(); 64 | 65 | // Cancel operation if no devices are found 66 | if (!knownDevices) return; 67 | 68 | // Get connection data of known devices and to connect 69 | for (const i in knownDevices) { 70 | const deviceDetails = knownDevices[i]; 71 | // Cancell operation if object does not contain IP address 72 | if (!deviceDetails.native.ip) continue; 73 | // Start connection to this device 74 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 75 | // @ts-expect-error 76 | this.wsConnectionHandler(deviceDetails.native.ip, deviceDetails.common.name); 77 | } 78 | } catch (error) { 79 | // this.errorHandler(`[tryKnownDevices]`, error); 80 | } 81 | } 82 | 83 | private wsConnectionHandler(deviceIP: string, deviceName: string): void { 84 | this.log.info(`Starting connection to ${deviceName} on IP ${deviceIP}`); 85 | apConnection[deviceIP] = { 86 | connection: new WebSocket(`ws://${deviceIP}/ws`), 87 | connectionStatus: 'Connecting', 88 | deviceName: deviceName, 89 | ip: deviceIP, 90 | }; 91 | 92 | apConnection[deviceIP].connection.on('open', () => { 93 | this.log.info( 94 | `Connected to AccessPoint ${apConnection[deviceIP].deviceName} on ${apConnection[deviceIP].ip}`, 95 | ); 96 | apConnection[deviceIP].connectionStatus = 'Connected'; 97 | 98 | // Check if device connection is caused by adding device from admin, if yes send OK message 99 | if (messageResponse[deviceIP]) { 100 | this.sendTo( 101 | messageResponse[deviceIP].from, 102 | messageResponse[deviceIP].command, 103 | { 104 | result: 'OK - Access Point successfully connected, initializing configuration. Refresh table to show all known devices', 105 | }, 106 | messageResponse[deviceIP].callback, 107 | ); 108 | delete messageResponse[deviceIP]; 109 | } 110 | 111 | //ToDo: Create Device on connection state and store decide details 112 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 113 | this.extendObject(apConnection[deviceIP].deviceName, { 114 | type: 'device', 115 | common: { 116 | name: apConnection[deviceIP].deviceName, 117 | // ToDo: @ticaki please assit with TS, value is correct but error shown 118 | // statusStates: { 119 | // onlineId: `${this.namespace}.${apConnection[deviceIP].deviceName}._info._online`, 120 | // }, 121 | }, 122 | native: { 123 | ip: apConnection[deviceIP].ip, 124 | }, 125 | }); 126 | this.extendObject(`${apConnection[deviceIP].deviceName}._info`, { 127 | type: 'channel', 128 | common: { 129 | name: 'Connection detail', 130 | }, 131 | }); 132 | 133 | jsonExplorer.stateSetCreate(`${apConnection[deviceIP].deviceName}._info.connected`, 'connected', true); 134 | jsonExplorer.stateSetCreate( 135 | `${apConnection[deviceIP].deviceName}._info.ip`, 136 | 'Access Point IP-Address', 137 | apConnection[deviceIP].ip, 138 | ); 139 | }); 140 | 141 | apConnection[deviceIP].connection.on('message', (message: string) => { 142 | //ToDo: Design messageHandler to write values to states 143 | this.log.debug(`Received message from server: ${message}`); 144 | try { 145 | message = JSON.parse(message); 146 | } catch (e) { 147 | this.log.error(`Cannot parse JSON ${message} | ${e}`); 148 | } 149 | let modifiedMessage; 150 | 151 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 152 | // @ts-expect-error 153 | if (message && message['sys']) { 154 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 155 | // @ts-expect-error 156 | modifiedMessage = message['sys']; 157 | jsonExplorer.traverseJson(modifiedMessage, `${apConnection[deviceIP].deviceName}._info`); 158 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 159 | // @ts-expect-error 160 | } else if (message && message['tags']) { 161 | //ToDO: Improvement required, channel creation for Tag ID should only be executed once 162 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 163 | // @ts-expect-error 164 | modifiedMessage = message['tags']; 165 | this.extendObject(`${apConnection[deviceIP].deviceName}.tags`, { 166 | type: 'channel', 167 | common: { 168 | name: 'Tags', 169 | }, 170 | }); 171 | //ToDO: Improvement required, channel creation for Tag ID should only be executed once 172 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 173 | // @ts-expect-error 174 | this.extendObject(`${apConnection[deviceIP].deviceName}.tags.${message && message['tags'][0].mac}`, { 175 | type: 'channel', 176 | common: { 177 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 178 | // @ts-expect-error 179 | name: message['tags'][0].alias, 180 | }, 181 | }); 182 | jsonExplorer.traverseJson( 183 | modifiedMessage[0], 184 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 185 | // @ts-expect-error 186 | `${apConnection[deviceIP].deviceName}.tags.${message && message['tags'][0].mac}`, 187 | ); 188 | } else { 189 | modifiedMessage = message; 190 | jsonExplorer.traverseJson(modifiedMessage, apConnection[deviceIP].deviceName); 191 | } 192 | apConnection[deviceIP].connectionStatus = 'Connected'; 193 | jsonExplorer.stateSetCreate(`${apConnection[deviceIP].deviceName}._info.connected`, 'connected', true); 194 | }); 195 | 196 | apConnection[deviceIP].connection.on('close', () => { 197 | this.log.info('Disconnected from server'); 198 | if (apConnection[deviceIP]) { 199 | apConnection[deviceIP].connectionStatus = 'Disconnected'; 200 | jsonExplorer.stateSetCreate(`${apConnection[deviceIP].deviceName}._info.connected`, 'connected', false); 201 | } 202 | }); 203 | } 204 | 205 | /** 206 | * Is called when adapter shuts down - callback has to be called under any circumstances! 207 | */ 208 | private onUnload(callback: () => void): void { 209 | try { 210 | // Here you must clear all timeouts or intervals that may still be active 211 | // clearTimeout(timeout1); 212 | // clearTimeout(timeout2); 213 | // ... 214 | // clearInterval(interval1); 215 | 216 | // loop truth all connection and close if present 217 | for (const ap in apConnection) { 218 | //ToDo: needs to be optimized, just quick & dirty for testing now 219 | try { 220 | apConnection[ap].connection.close(); 221 | } catch (e) { 222 | // no connection present 223 | } 224 | } 225 | 226 | callback(); 227 | } catch (e) { 228 | callback(); 229 | } 230 | } 231 | 232 | /** 233 | * Is called if a subscribed state changes 234 | */ 235 | private onStateChange(id: string, state: ioBroker.State | null | undefined): void { 236 | if (state) { 237 | // The state was changed 238 | this.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`); 239 | } else { 240 | // The state was deleted 241 | this.log.debug(`state ${id} deleted`); 242 | } 243 | } 244 | 245 | // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor. 246 | /** 247 | * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... 248 | * Using this method requires "common.messagebox" property to be set to true in io-package.json 249 | */ 250 | private onMessage(obj: ioBroker.Message): void { 251 | this.log.debug('Data from configuration received : ' + JSON.stringify(obj)); 252 | if (typeof obj === 'object' && obj.message) { 253 | this.log.debug('Data from configuration received : ' + JSON.stringify(obj)); 254 | // if (obj.command === 'send') { 255 | // e.g. send email or pushover or whatever 256 | 257 | try { 258 | switch (obj.command) { 259 | //ToDo previous add function to be removed 260 | case '_addUpdateAP': 261 | // eslint-disable-next-line no-case-declarations 262 | const ipValid = this.validateIPAddress(obj.message['apIP']); 263 | if (!ipValid) { 264 | this.log.warn(`You entered an incorrect IP-Address, cannot add device !`); 265 | 266 | this.sendTo( 267 | obj.from, 268 | obj.command, 269 | { 270 | type: 'error', 271 | message: 'connection failed', 272 | }, 273 | obj.callback, 274 | ); 275 | } else { 276 | this.log.info(`Valid IP address received`); 277 | messageResponse[obj.message['apIP']] = obj; 278 | this.wsConnectionHandler(obj.message['apIP'], obj.message['apName']); 279 | } 280 | break; 281 | // 282 | case 'loadAccessPoints': 283 | { 284 | let data = {}; 285 | 286 | const tableEntry = []; 287 | 288 | for (const device in apConnection) { 289 | tableEntry.push({ 290 | apName: apConnection[device].deviceName, 291 | ip: apConnection[device].ip, 292 | connectState: apConnection[device].connectionStatus, 293 | }); 294 | } 295 | 296 | data = { 297 | native: { 298 | accessPointTable: tableEntry, 299 | }, 300 | }; 301 | this.sendTo(obj.from, obj.command, data, obj.callback); 302 | } 303 | break; 304 | // 305 | // Front End message handler to load IP-Address dropDown with all current known devices 306 | case 'getApName': 307 | { 308 | const dropDownEntry = []; 309 | for (const device in apConnection) { 310 | dropDownEntry.push({ 311 | label: apConnection[device].deviceName, 312 | value: apConnection[device].deviceName, 313 | }); 314 | } 315 | this.sendTo(obj.from, obj.command, dropDownEntry, obj.callback); 316 | } 317 | break; 318 | 319 | case 'getApIP': 320 | { 321 | const dropDownEntry = []; 322 | for (const device in apConnection) { 323 | dropDownEntry.push({ 324 | label: apConnection[device].ip, 325 | value: apConnection[device].ip, 326 | }); 327 | } 328 | this.sendTo(obj.from, obj.command, dropDownEntry, obj.callback); 329 | } 330 | break; 331 | 332 | // Handle front-end messages to delete devices 333 | case 'deleteAP': 334 | messageResponse[obj.message['apIP']] = obj; 335 | if (apConnection[obj.message['apIP']]) { 336 | // Ensure all existing connections are closed, will trigger disconnect event to clean-up memory attributes 337 | try { 338 | if (apConnection[obj.message['apIP']].connection) 339 | apConnection[obj.message['apIP']].connection.close(); 340 | } catch (e) { 341 | // Add error handler 342 | } 343 | // Try to delete Device Object including all underlying states 344 | try { 345 | this.delObject(apConnection[obj.message['apIP']].deviceName, { recursive: true }); 346 | } catch (e) { 347 | // Deleting device channel failed 348 | } 349 | 350 | // Clean memory data 351 | delete apConnection[obj.message['apIP']]; 352 | 353 | // Send confirmation to frontend 354 | this.sendTo( 355 | messageResponse[obj.message['apIP']].from, 356 | messageResponse[obj.message['apIP']].command, 357 | { result: 'OK - Device successfully removed' }, 358 | messageResponse[obj.message['apIP']].callback, 359 | ); 360 | delete messageResponse[obj.message['apIP']]; 361 | } else { 362 | this.sendTo( 363 | obj.from, 364 | obj.command, 365 | { 366 | error: `Provided IP-Address ${JSON.stringify( 367 | obj.message, 368 | )} unknown, please refresh table and enter an valid IP-Address`, 369 | }, 370 | obj.callback, 371 | ); 372 | return; 373 | } 374 | 375 | // this.sendTo(obj.from, obj.command, 1, obj.callback); 376 | break; 377 | } 378 | } catch (error) { 379 | // this.errorHandler(`[onMessage]`, error); 380 | } 381 | 382 | // Send response in callback if required 383 | // if (obj.callback) this.sendTo(obj.from, obj.command, 'Message received', obj.callback); 384 | // } 385 | } 386 | } 387 | 388 | private validateIPAddress(ipAddress: string): boolean { 389 | return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( 390 | ipAddress, 391 | ); 392 | } 393 | } 394 | 395 | if (require.main !== module) { 396 | // Export the constructor in compact mode 397 | module.exports = (options: Partial | undefined) => new OpenEpaperLink(options); 398 | } else { 399 | // otherwise start the instance directly 400 | (() => new OpenEpaperLink())(); 401 | } 402 | --------------------------------------------------------------------------------