diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100755 index 0000000..ef39dc6 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 4, + { + "SwitchCase": 1 + } + ], + "no-console": "off", + "no-var": "error", + "prefer-const": "error", + "quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "always" + ] + }, + "parserOptions": { + "ecmaVersion": 2018 + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100755 index 0000000..8956139 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Something is not working as it should +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '...' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots & Logfiles** +If applicable, add screenshots and logfiles to help explain your problem. + +**Versions:** + - Adapter version: + - JS-Controller version: + - Node version: + - Operating system: + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..000d0ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.git +.idea +*.code-workspace +node_modules +nbproject + +# npm package files +iobroker.*.tgz + +Thumbs.db + +# i18n intermediate files +admin/i18n/flat.txt +admin/i18n/*/flat.txt \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100755 index 0000000..ed16ee5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,28 @@ +.git +.idea +node_modules/ +nbproject/ +.vs*/ +*.code-workspace +Thumbs.db +gulpfile.js + +test/ +travis/ +.travis.yml +appveyor.yml +.travis.yaml +appveyor.yaml + +.eslintrc.json +.eslintrc.js + +# npm package files +iobroker.*.tgz +package-lock.json + +# i18n intermediate files +admin/i18n + +# maintenance scripts +maintenance/** \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..d473faf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +os: + - linux + - osx + - windows + +language: node_js +node_js: + - '8' + - '10' + +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.9 + +before_install: + - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-4.9; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-4.9; fi' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm; fi' + - npm -v +script: + - npm run test:package + - npm run test:unit + - export DEBUG=testing:* + - npm run test:integration diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 index e813cb1..bb6c5d1 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 bowao +Copyright (c) 2019 bowao Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md old mode 100644 new mode 100755 index a071576..cc11bc9 --- a/README.md +++ b/README.md @@ -1,2 +1,128 @@ +![Logo](admin/epson_xp860.png) # ioBroker.epson_xp860 -Reads the ink level from the epson xp860 printer website +(German version see below) + +## Description +This is more an adapter-like helper script than a real adapter. Because the printer does not support the snmp-standard-printer-MIB, the ink status of the Epson XP860 is read from the printer's Web site. (Only if the printer is set to German) + +It is an adaptation of the iobroker.epson_stylus_px830 adapter developed by Pix--. +-> https://github.com/Pix---/ioBroker.epson_stylus_px830 + +Now it reads the ink levels of an **Epson Expression Photo XP-860** and in addition, it was converted into a **scheduled adapter** + +![alt text](img/printer_website_1.png "Website of EPSON Expression Photo XP-860") + +![alt text](img/printer_website_2.png "Website of EPSON Expression Photo XP-860") + +## Configuration +### IP / Port +IP adress of Epson Printer within local network. The adapter will stop if no IP is entered. The port number is optional and will only be considered if an ip adress is entered. + +![Screenshot Settings](img/epson_xp860SettingScreenshot.jpg "Screenshot Settings") + +## Schedule +The reading interval of the ink level can be set via the scheduling on the 'Instances' page. Default is every full hour. + +![Screenshot Schedule](img/epson_xp860ScheduleScreenshot.jpg "Screenshot Schedule") + +## Datapoints + +ink_black +ink_cyan +ink_cyanlight +ink_magenta +ink_magentalight +ink_yellow +printerInfo.connection (type of connection, WLAN,LAN) +printerInfo.ip +printerInfo.mac +printerInfo.model +printerInfo.name +printerInfo.requestResponded (True if printer responded on last request) +printerInfo.responseTime (Response time of last request) + +## VIS +### Widget +Example set of widgets for VIS (developed by Pix--) +https://raw.githubusercontent.com/bowao/ioBroker.epson_xp860/master/widgets/ink_widget + +![alt text](img/VISScreenshot.png "Example VIS Widgets") + +## Beschreibung +Dies ist eher ein Adapter-ähnliches Hilfsskript als ein echter Adapter. Da der Drucker die snmp-standard-printer-MIB nicht unterstützt, wird der Tintenstatus des Epson XP860 von der Website des Druckers gelesen. (Nur wenn der Drucker auf Deutsch eingestellt ist) + +Dies ist eine Adaption des Adapters iobroker.epson_stylus_px830 entwickelt von Pix-- +-> https://github.com/Pix---/ioBroker.epson_stylus_px830 + +Nun liest der Adapter den Füllstand der Tintenpatronen eines **Epson Expression Photo XP-860** und zusätzlich wurde er zu einem **Zeitgesteuerten-Adapter** umgebaut. + +![alt text](img/printer_website_1.png "Website of EPSON Expression Photo XP-860") + +![alt text](img/printer_website_2.png "Website of EPSON Expression Photo XP-860") + +## Einstellungen +### IP / Port +Die IP Adresse des Druckers im lokalen Netzwerk. Ohne Eingabe gibt es keine Abfrage. Die Portnummer ist optional und wird nur bei Eingabe einer IP-Adresse berücksichtigt. + +### Schedule +Der Abfrageintervall kann auf der Seite 'Instanzen' über die Spalte 'Zeitplanung' angepasst werden. Standardeinstellung: zu jeder vollen Stunde. + +## Konfiguration +![alt text](img/epson_xp860SettingScreenshot.jpg "Screenshot Einstellungen") + +## Datenpunkte + +ink_black +ink_cyan +ink_cyanlight +ink_magenta +ink_magentalight +ink_yellow +printerInfo.connection (Verbindungsart WLAN,LAN) +printerInfo.ip +printerInfo.mac +printerInfo.model +printerInfo.name +printerInfo.requestResponded (Wahr, wenn der Drucker auf die letzte Anfrage geantwortet hat) +printerInfo.responseTime (Antwortzeit der letzten Anfrage) + + +## VIS +### Widget +Beispiel widgets für VIS (entwickelt von Pix--) +https://raw.githubusercontent.com/bowao/ioBroker.epson_xp860/master/widgets/ink_widget + +![alt text](img/VISScreenshot.png "Example VIS Widgets") + + +## Changelog + +### 1.0.0 +* stable release + +## License + +The MIT License (MIT) + +Copyright (c) 2019 bowao + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + diff --git a/admin/admin.d.ts b/admin/admin.d.ts new file mode 100755 index 0000000..13444d9 --- /dev/null +++ b/admin/admin.d.ts @@ -0,0 +1 @@ +declare let systemDictionary: Record>; diff --git a/admin/custom_m.html b/admin/custom_m.html new file mode 100755 index 0000000..0320021 --- /dev/null +++ b/admin/custom_m.html @@ -0,0 +1,44 @@ + + + \ No newline at end of file diff --git a/admin/epson_xp860.png b/admin/epson_xp860.png new file mode 100755 index 0000000..e23a18e Binary files /dev/null and b/admin/epson_xp860.png differ diff --git a/admin/index_m.html b/admin/index_m.html new file mode 100755 index 0000000..872266e --- /dev/null +++ b/admin/index_m.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+ + + normally no port setting is required +
+
+ +
+
+
+ + diff --git a/admin/style.css b/admin/style.css new file mode 100755 index 0000000..703d654 --- /dev/null +++ b/admin/style.css @@ -0,0 +1,10 @@ +/* You can delete those if you want. I just found them very helpful */ +* { + box-sizing: border-box +} +.m { + /* Don't cut off dropdowns! */ + overflow: initial; +} + +/* Add your styles here */ diff --git a/admin/tab_m.html b/admin/tab_m.html new file mode 100755 index 0000000..ee1cf42 --- /dev/null +++ b/admin/tab_m.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + + Descriptions of the input field +
+
+ + + + test2 +
+
+ + + + Verification input +
+
+
+
+ + + +
+
+ mode_edit + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100755 index 0000000..b99d93e --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "./**/*.d.ts", + "./**/*.js" + ] +} diff --git a/admin/words.js b/admin/words.js new file mode 100755 index 0000000..2e9dd1f --- /dev/null +++ b/admin/words.js @@ -0,0 +1,11 @@ +/*global systemDictionary:true */ +'use strict'; + +systemDictionary = { + "Enter IP adress": { "en": "Enter IP adress", "de": "Drucker IP Addresse", "ru": "Введите IP-адрес", "pt": "Digite o endereço IP", "nl": "Voer het IP-adres in", "fr": "Entrez l'adresse IP", "it": "Inserisci l'indirizzo IP", "es": "Ingrese la dirección IP", "pl": "Wprowadź adres IP"}, + "Epson Adapter settings": { "en": "Epson Stylus PX830 Adapter settings", "de": "Epson Stylus PX830 Adapter Einstellungen", "ru": "Настройки адаптера Epson Stylus PX 830", "pt": "Configurações do adaptador Epson Stylus PX 830", "nl": "Epson Stylus PX 830 Adapterinstellingen", "fr": "Paramètres de l'adaptateur Epson Stylus PX 830", "it": "Impostazioni della scheda Epson Stylus PX 830", "es": "Configuración del adaptador Epson Stylus PX 830", "pl": "Ustawienia adaptera Epson Stylus PX 830"}, + "IP": { "en": "ip adress of printer", "de": "IP Adresse des Druckers", "ru": "ip-адрес принтера", "pt": "endereço ip da impressora", "nl": "ip-adres van de printer", "fr": "adresse IP de l'imprimante", "it": "indirizzo IP della stampante", "es": "dirección IP de la impresora", "pl": "adres ip drukarki"}, + "Port": { "en": "Port number", "de": "Port Nummer", "ru": "Номер порта", "pt": "Número da porta", "nl": "Poortnummer", "fr": "Numéro de port", "it": "Numero di porta", "es": "Número de puerto", "pl": "Numer portu"}, + "Printer settings": { "en": "Printer settings", "de": "Drucker Einstellungen", "ru": "Настройки принтера", "pt": "Configurações da impressora", "nl": "Printer instellingen", "fr": "Paramètres de l'imprimante", "it": "Impostazioni della stampante", "es": "Configuración de la impresora", "pl": "Ustawienia drukarki"}, + "normally no port setting is required": { "en": "normally no port setting is required", "de": "Die Eingabe der Portnummer ist nicht zwingend", "ru": "обычно настройка порта не требуется", "pt": "normalmente não é necessária nenhuma configuração de porta", "nl": "normaal is er geen poortinstelling vereist", "fr": "Normalement, aucun paramètre de port n'est requis", "it": "normalmente non è richiesta alcuna impostazione della porta", "es": "normalmente no se requiere configuración de puerto", "pl": "zwykle nie jest wymagane ustawienie portu"}, +}; diff --git a/gulpfile.js b/gulpfile.js new file mode 100755 index 0000000..af7215b --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,475 @@ +/*! + * ioBroker gulpfile + * Date: 2019-01-28 + */ +'use strict'; + +const gulp = require('gulp'); +const fs = require('fs'); +const pkg = require('./package.json'); +const iopackage = require('./io-package.json'); +const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; +const fileName = 'words.js'; +const EMPTY = ''; +const translate = require('./lib/tools').translateText; +const languages = { + en: {}, + de: {}, + ru: {}, + pt: {}, + nl: {}, + fr: {}, + it: {}, + es: {}, + pl: {}, + 'zh-cn': {} +}; + +function lang2data(lang, isFlat) { + let str = isFlat ? '' : '{\n'; + let count = 0; + for (const w in lang) { + if (lang.hasOwnProperty(w)) { + count++; + if (isFlat) { + str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n'; + } else { + const key = ' "' + w.replace(/"/g, '\\"') + '": '; + str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; + } + } + } + if (!count) + return isFlat ? '' : '{\n}'; + if (isFlat) { + return str; + } else { + return str.substring(0, str.length - 2) + '\n}'; + } +} + +function readWordJs(src) { + try { + let words; + if (fs.existsSync(src + 'js/' + fileName)) { + words = fs.readFileSync(src + 'js/' + fileName).toString(); + } else { + words = fs.readFileSync(src + fileName).toString(); + } + words = words.substring(words.indexOf('{'), words.length); + words = words.substring(0, words.lastIndexOf(';')); + + const resultFunc = new Function('return ' + words + ';'); + + return resultFunc(); + } catch (e) { + return null; + } +} + +function padRight(text, totalLength) { + return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); +} + +function writeWordJs(data, src) { + let text = ''; + text += '/*global systemDictionary:true */\n'; + text += "'use strict';\n\n"; + text += 'systemDictionary = {\n'; + for (const word in data) { + if (data.hasOwnProperty(word)) { + text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); + let line = ''; + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; + } + } + if (line) { + line = line.trim(); + line = line.substring(0, line.length - 1); + } + text += line + '},\n'; + } + } + text += '};'; + if (fs.existsSync(src + 'js/' + fileName)) { + fs.writeFileSync(src + 'js/' + fileName, text); + } else { + fs.writeFileSync(src + '' + fileName, text); + } +} + +function words2languages(src) { + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (const l in langs) { + if (!langs.hasOwnProperty(l)) + continue; + const keys = Object.keys(langs[l]); + keys.sort(); + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + if (!fs.existsSync(src + 'i18n/' + l)) { + fs.mkdirSync(src + 'i18n/' + l); + } + + fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); + } + } else { + console.error('Cannot read or parse ' + fileName); + } +} + +function words2languagesFlat(src) { + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + const keys = Object.keys(langs.en); + keys.sort(); + for (const l in langs) { + if (!langs.hasOwnProperty(l)) + continue; + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + langs[l] = obj; + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (const ll in langs) { + if (!langs.hasOwnProperty(ll)) + continue; + if (!fs.existsSync(src + 'i18n/' + ll)) { + fs.mkdirSync(src + 'i18n/' + ll); + } + + fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); + } + fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); + } else { + console.error('Cannot read or parse ' + fileName); + } +} + +function languagesFlat2words(src) { + const dirs = fs.readdirSync(src + 'i18n/'); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) + return 1; + if (a < b) + return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) + return 1; + if (posA < posB) + return -1; + return 0; + } + }); + const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); + + for (const lang of dirs) { + if (lang === 'flat.txt') + continue; + const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); + langs[lang] = {}; + keys.forEach(function (word, i) { + langs[lang][word] = values[i]; + }); + + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + + const temporaryIgnore = ['flat.txt']; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w]; + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) + return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} + +function languages2words(src) { + const dirs = fs.readdirSync(src + 'i18n/'); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) + return 1; + if (a < b) + return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) + return 1; + if (posA < posB) + return -1; + return 0; + } + }); + for (const lang of dirs) { + if (lang === 'flat.txt') + continue; + langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); + langs[lang] = JSON.parse(langs[lang]); + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + + const temporaryIgnore = ['flat.txt']; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w]; + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) + return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} + +async function translateNotExisting(obj, baseText, yandex) { + let t = obj['en']; + if (!t) { + t = baseText; + } + + if (t) { + for (let l in languages) { + if (!obj[l]) { + const time = new Date().getTime(); + obj[l] = await translate(t, l, yandex); + console.log('en -> ' + l + ' ' + (new Date().getTime() - time) + ' ms'); + } + } + } +} + +//TASKS + +gulp.task('adminWords2languages', function (done) { + words2languages('./admin/'); + done(); +}); + +gulp.task('adminWords2languagesFlat', function (done) { + words2languagesFlat('./admin/'); + done(); +}); + +gulp.task('adminLanguagesFlat2words', function (done) { + languagesFlat2words('./admin/'); + done(); +}); + +gulp.task('adminLanguages2words', function (done) { + languages2words('./admin/'); + done(); +}); + +gulp.task('updatePackages', function (done) { + iopackage.common.version = pkg.version; + iopackage.common.news = iopackage.common.news || {}; + if (!iopackage.common.news[pkg.version]) { + const news = iopackage.common.news; + const newNews = {}; + + newNews[pkg.version] = { + en: 'news', + de: 'neues', + ru: 'новое', + pt: 'novidades', + nl: 'nieuws', + fr: 'nouvelles', + it: 'notizie', + es: 'noticias', + pl: 'nowości', + 'zh-cn': '新' + }; + iopackage.common.news = Object.assign(newNews, news); + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); + done(); +}); + +gulp.task('updateReadme', function (done) { + const readme = fs.readFileSync('README.md').toString(); + const pos = readme.indexOf('## Changelog\n'); + if (pos !== -1) { + const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + const readmeEnd = readme.substring(pos + '## Changelog\n'.length); + + if (readme.indexOf(version) === -1) { + const timestamp = new Date(); + const date = timestamp.getFullYear() + '-' + + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + + ('0' + (timestamp.getDate()).toString(10)).slice(-2); + + let news = ''; + if (iopackage.common.news && iopackage.common.news[pkg.version]) { + news += '* ' + iopackage.common.news[pkg.version].en; + } + + fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); + } + } + done(); +}); + +gulp.task('translate', async function (done) { + + let yandex; + const i = process.argv.indexOf('--yandex'); + if (i > -1) { + yandex = process.argv[i + 1]; + } + + if (iopackage && iopackage.common) { + if (iopackage.common.news) { + console.log('Translate News'); + for (let k in iopackage.common.news) { + console.log('News: ' + k); + let nw = iopackage.common.news[k]; + await translateNotExisting(nw, null, yandex); + } + } + if (iopackage.common.titleLang) { + console.log('Translate Title'); + await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); + } + if (iopackage.common.desc) { + console.log('Translate Description'); + await translateNotExisting(iopackage.common.desc, null, yandex); + } + + if (fs.existsSync('./admin/i18n/en/translations.json')) { + let enTranslations = require('./admin/i18n/en/translations.json'); + for (let l in languages) { + console.log('Translate Text: ' + l); + let existing = {}; + if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { + existing = require('./admin/i18n/' + l + '/translations.json'); + } + for (let t in enTranslations) { + if (!existing[t]) { + existing[t] = await translate(enTranslations[t], l, yandex); + } + } + if (!fs.existsSync('./admin/i18n/' + l + '/')) { + fs.mkdirSync('./admin/i18n/' + l + '/'); + } + fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); + } + } + + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); +}); + +gulp.task('translateAndUpdateWordsJS', gulp.series('translate', 'adminLanguages2words', 'adminWords2languages')); + +gulp.task('default', gulp.series('updatePackages', 'updateReadme')); \ No newline at end of file diff --git a/img/VISScreenshot.png b/img/VISScreenshot.png new file mode 100755 index 0000000..ccec252 Binary files /dev/null and b/img/VISScreenshot.png differ diff --git a/img/epson_xp860ScheduleScreenshot.jpg b/img/epson_xp860ScheduleScreenshot.jpg new file mode 100755 index 0000000..705e590 Binary files /dev/null and b/img/epson_xp860ScheduleScreenshot.jpg differ diff --git a/img/epson_xp860SettingScreenshot.jpg b/img/epson_xp860SettingScreenshot.jpg new file mode 100755 index 0000000..fb09889 Binary files /dev/null and b/img/epson_xp860SettingScreenshot.jpg differ diff --git a/img/printer_website_1.png b/img/printer_website_1.png new file mode 100755 index 0000000..fab0d0f Binary files /dev/null and b/img/printer_website_1.png differ diff --git a/img/printer_website_2.png b/img/printer_website_2.png new file mode 100755 index 0000000..b47494b Binary files /dev/null and b/img/printer_website_2.png differ diff --git a/io-package.json b/io-package.json new file mode 100755 index 0000000..3acdc54 --- /dev/null +++ b/io-package.json @@ -0,0 +1,176 @@ +{ + "common": { + "name": "epson_xp860", + "version": "1.0.0", + "news": { + "1.0.0": { + "en": "stable release", + "de": "stabile Version", + "ru": "стабильный выпуск", + "pt": "versão estável", + "nl": "stabiele vrijlating", + "fr": "version stable", + "it": "rilascio stabile", + "es": "lanzamiento estable", + "pl": "wersja stabilna" + } + }, + "title": "Epson XP-860", + "titleLang": { + "en": "Epson XP-860", + "de": "Epson XP-860", + "ru": "Epson XP-860", + "pt": "Epson XP-860", + "nl": "Epson XP-860", + "fr": "Epson XP-860", + "it": "Epson XP-860", + "es": "Epson XP-860" + }, + "desc": { + "en": "Epson XP-860 Ink Status", + "de": "Epson XP-860 Tintenstatus", + "ru": "Epson XP-860 Состояние чернил", + "pt": "Status da tinta Epson XP-860", + "nl": "Epson XP-860 inktstatus", + "fr": "État de l'encre Epson XP-860", + "it": "Stato inchiostro Epson XP-860", + "es": "Estado de la tinta Epson XP-860", + "pl": "Epson XP-860 Status atramentu" + }, + "authors": [ + "bowao" + ], + "keywords": [ + "printer", + "ink", + "Epson", + "Tinte", + "Drucker", + "office" + ], + "license": "MIT", + "platform": "javascript/Node.js", + "main": "main.js", + "icon": "epson_xp860.png", + "enabled": true, + "extIcon": "https://raw.githubusercontent.com/bowao/ioBroker.epson_xp860/master/admin/epson_xp860.png", + "readme": "https://github.com/bowao/ioBroker.epson_xp860/blob/master/README.md", + "loglevel": "info", + "mode": "schedule", + "schedule": "0 * * * *", + "type": "infrastructure", + "materialize": true, + "dependencies": [ + { + "js-controller": ">=1.4.2" + } + ] + }, + "native": { + "printerip": "", + "printerport": "" + }, + "objects": [], + "instanceObjects": [ + { + "_id": "printerInfo", + "type": "channel", + "common": { + "name": "Printer Information" + }, + "native": {} + }, + { + "_id": "printerInfo.ip", + "type": "state", + "common": { + "name": "Printer IP", + "desc": "Printer IP adress", + "type": "string", + "read": true, + "write": false, + "role": "info.ip" + }, + "native": {} + }, + { + "_id": "printerInfo.mac", + "type": "state", + "common": { + "name": "Printer MAC", + "desc": "Printer MAC adress", + "type": "string", + "read": true, + "write": false, + "role": "info.mac" + }, + "native": {} + }, + { + "_id": "printerInfo.model", + "type": "state", + "common": { + "name": "Printer Model", + "desc": "Printer model type", + "type": "string", + "read": true, + "write": false, + "role": "info.model" + }, + "native": {} + }, + { + "_id": "printerInfo.name", + "type": "state", + "common": { + "name": "Printer Name", + "desc": "Printer Name", + "type": "string", + "read": true, + "write": false, + "role": "info.name" + }, + "native": {} + }, + { + "_id": "printerInfo.connection", + "type": "state", + "common": { + "name": "Printer connection status", + "desc": "Printer connection status", + "type": "string", + "read": true, + "write": false, + "role": "text" + }, + "native": {} + }, + { + "_id": "printerInfo.requestResponded", + "type": "state", + "common": { + "name": "Printer responded to last request", + "desc": "Printer responded to last request", + "type": "bool", + "read": true, + "write": false, + "role": "state" + }, + "native": {} + }, + { + "_id": "printerInfo.responseTime", + "type": "state", + "common": { + "name": "Response time of last request", + "desc": "Response time of last request", + "type": "number", + "read": true, + "write": false, + "role": "value", + "unit": "ms" + }, + "native": {} + } + ] +} diff --git a/lib/adapter-config.d.ts b/lib/adapter-config.d.ts new file mode 100755 index 0000000..600e7ca --- /dev/null +++ b/lib/adapter-config.d.ts @@ -0,0 +1,16 @@ +// This file extends the AdapterConfig type from "@types/iobroker" +// using the actual properties present in io-package.json +// in order to provide typings for adapter.config properties + +import { native } from '../io-package.json'; + +type _AdapterConfig = typeof native; + +// Augment the globally declared type ioBroker.AdapterConfig +declare global { + namespace ioBroker { + interface AdapterConfig extends _AdapterConfig { + // Do not enter anything here! + } + } +} \ No newline at end of file diff --git a/lib/tools.js b/lib/tools.js new file mode 100755 index 0000000..f8b4017 --- /dev/null +++ b/lib/tools.js @@ -0,0 +1,91 @@ +const axios = require('axios'); + +/** + * Tests whether the given variable is a real object and not an Array + * @param {any} it The variable to test + * @returns {it is Record} + */ +function isObject(it) { + // This is necessary because: + // typeof null === 'object' + // typeof [] === 'object' + // [] instanceof Object === true + return Object.prototype.toString.call(it) === '[object Object]'; +} + +/** + * Tests whether the given variable is really an Array + * @param {any} it The variable to test + * @returns {it is any[]} + */ +function isArray(it) { + if (typeof Array.isArray === 'function') return Array.isArray(it); + return Object.prototype.toString.call(it) === '[object Array]'; +} + +/** + * Translates text to the target language. Automatically chooses the right translation API. + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers + * @returns {Promise} + */ +async function translateText(text, targetLang, yandexApiKey) { + if (targetLang === 'en') { + return text; + } + if (yandexApiKey) { + return await translateYandex(text, targetLang, yandexApiKey); + } else { + return await translateGoogle(text, targetLang); + } +} + +/** + * Translates text with Yandex API + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @param {string} [apiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers + * @returns {Promise} + */ +async function translateYandex(text, targetLang, apiKey) { + if (targetLang === 'zh-cn') { + targetLang = 'zh'; + } + try { + const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; + const response = await axios({url, timeout: 15000}); + if (response.data && response.data['text']) { + return response.data['text'][0]; + } + throw new Error('Invalid response for translate request'); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } +} + +/** + * Translates text with Google API + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @returns {Promise} + */ +async function translateGoogle(text, targetLang) { + try { + const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; + const response = await axios({url, timeout: 15000}); + if (isArray(response.data)) { + // we got a valid response + return response.data[0][0][0]; + } + throw new Error('Invalid response for translate request'); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } +} + +module.exports = { + isArray, + isObject, + translateText +}; diff --git a/main.js b/main.js new file mode 100755 index 0000000..d4b9734 --- /dev/null +++ b/main.js @@ -0,0 +1,240 @@ +'use strict'; + +/* + * Created with @iobroker/create-adapter v1.17.0 + */ + +// The adapter-core module gives you access to the core ioBroker functions +// you need to create an adapter +const utils = require('@iobroker/adapter-core'); + +// Load your modules here, e.g.: +const request = require('request'); + +var lang = 'de'; +var ip = ''; +var baselevel = 50; // bedeutet: in der Webseite wird ein Balken von 100% Höhe 50px hoch gezeichnet. + // Also entspricht ein gezeigtes Tintenlevel von 25 (px) dann 50% und eines von 10 (px) dann 20% +var link = ''; +var ink = { + 'cyan' : { + 'state': 'cyan', + 'name': 'Cyan', + 'cut': 'Ink_C.PNG', + 'cartridge': 'T2422', + 'slot': '4' + }, + 'cyanlight' : { + 'state': 'cyanlight', + 'name': 'Cyan Light', + 'cut': 'Ink_LC.PNG', + 'cartridge': 'T2425', + 'slot': '2' + }, + 'yellow' : { + 'state': 'yellow', + 'name': 'Yellow', + 'cut': 'Ink_Y.PNG', + 'cartridge': 'T2424', + 'slot': '5' + }, + 'black' : { + 'state': 'black', + 'name': 'Black', + 'cut': 'Ink_K.PNG', + 'cartridge': 'T2421', + 'slot': '1' + }, + 'magenta' : { + 'state': 'magenta', + 'name': 'Magenta', + 'cut': 'Ink_M.PNG', + 'cartridge': 'T2423', + 'slot': '3' + }, + 'magentalight' : { + 'state': 'magentalight', + 'name': 'Magenta Light', + 'cut': 'Ink_LM.PNG', + 'cartridge': 'T2426', + 'slot': '6' + } +}; + +class epson_xp860 extends utils.Adapter { + + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: 'epson_xp860', + }); + this.on('ready', this.onReady.bind(this)); + this.on('unload', this.onUnload.bind(this)); + } + + /** + * Is called when databases are connected and adapter received configuration. + */ + async onReady() { + // Initialize your adapter here + this.main(); + + } + + /** + * Is called when adapter shuts down - callback has to be called under any circumstances! + * @param {() => void} callback + */ + onUnload(callback) { + try { + this.log.info('cleaned everything up...'); + callback(); + } catch (e) { + callback(); + } + } + + readSettings() { + //check if IP is entered in settings + if (!this.config.printerip) { + this.log.warn('No IP adress of printer set up. Adapter will be stopped.'); + //stopReadPrinter(); + } + else { + ip = (this.config.printerport.length > 0) ? this.config.printerip + ':' + this.config.printerport : this.config.printerip; // if port is set then ip+port else ip only + this.log.debug('IP: ' + ip); + link = 'http://' + ip + '/PRESENTATION/HTML/TOP/PRTINFO.HTML'; + this.setState('printerInfo.ip', { + val: ip, + ack: false + }); + } + } + + readPrinter() { + + var name_cut = 'Druckername', + name_cut2 = 'Verbindungsstatus', + connect_cut = 'Verbindungsstatus', + connect_cut2 = 'IP-Adresse beziehen', + model_cut = '', + model_cut2 = '', + mac_cut = 'MAC-Adresse', + mac_cut2 = 'info-wfd', + requestResponded, + responseTime + + request( + { + url: link, + time: true, + timeout: 4500 + }, + (error, response, body) => { + if (!error && response.statusCode == 200) { + requestResponded = true; + this.setState('printerInfo.ip', { + val: ip, + ack: true + }); + // NAME EINLESEN + var name_cut_position = body.indexOf(name_cut) + name_cut.length + 41, + name_cut2_position = body.indexOf(name_cut2) -64; + var name_string = body.substring(name_cut_position, name_cut2_position); + this.setState('printerInfo.name', {val: name_string, ack: true}); + + // MODELL EINLESEN + var model_cut_position = body.indexOf(model_cut) + model_cut.length, + model_cut2_position = body.indexOf(model_cut2); + var model_string = body.substring(model_cut_position, model_cut2_position); + this.setState('printerInfo.model', {val: model_string, ack: true}); + + // MAC ADRESSE EINLESEN + var mac_cut_position = body.indexOf(mac_cut) + mac_cut.length + 41, + mac_cut2_position = body.indexOf(mac_cut2) - 39; + var mac_string = body.substring(mac_cut_position, mac_cut2_position); + this.setState('printerInfo.mac', {val: mac_string, ack: true}); + + // CONNECTION EINLESEN + var connect_cut_position = body.indexOf(connect_cut) + connect_cut.length + 41, + connect_cut2_position = body.indexOf(connect_cut2) - 64; + var connect_string = body.substring(connect_cut_position, connect_cut2_position); + this.setState('printerInfo.connection', {val: connect_string, ack: true}); + + for (var i in ink) { + this.setObjectNotExists('ink_' + ink[i].state, { + type: 'state', + common: { + name: 'Level of Cartridge ' + ink[i].cartridge + ' ' + ink[i].name, + desc: 'Level of Cartridge ' + ink[i].cartridge + ' ' + ink[i].name, + type: 'number', + unit: '%', + min: 0, + max: 100, + read: true, + write: false, + role: 'value.level' + }, + native: {} + }); + + // READ LEVELS + var cut_position = body.indexOf(ink[i].cut) + ink[i].cut.length + 10; + var level_string = body.substring(cut_position, cut_position + 12); + var level = parseInt(level_string,10) * 100 / parseInt(baselevel,10); + this.setState('ink_' + ink[i].state, {val: level, ack: true}); + this.log.debug(ink[i].name + ' Level: ' + level + '%'); + } + + this.log.debug('Channels and states created/write'); + responseTime = parseInt(response.timingPhases.total); + + } else { + this.log.warn('Cannot connect to Printer: ' + error); + requestResponded = false; + responseTime = -1; + } + + // Write connection status + this.setState('printerInfo.requestResponded', { + val: requestResponded, + ack: true + }); + this.setState('printerInfo.responseTime', { + val: responseTime, + ack: true + }); + } + ); // End request + this.log.debug('finished reading printer Data'); + } + + main() { + const that = this; + this.readSettings(); + this.log.debug('Epson XP860 adapter started...'); + this.readPrinter(); + + setTimeout(function() { + that.log.info('Epson XP-860 adapter stopped'); + that.stop(); + + }, 10000); + } + +} + +// @ts-ignore parent is a valid property on module +if (module.parent) { + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = (options) => new epson_xp860(options); +} else { + // otherwise start the instance directly + new epson_xp860(); +} diff --git a/main.test.js b/main.test.js new file mode 100755 index 0000000..5dc6ee4 --- /dev/null +++ b/main.test.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * This is a dummy TypeScript test file using chai and mocha + * + * It's automatically excluded from npm and its build output is excluded from both git and npm. + * It is advised to test all your modules with accompanying *.test.js-files + */ + +// tslint:disable:no-unused-expression + +const { expect } = require('chai'); +// import { functionToTest } from "./moduleToTest"; + +describe('module to test => function to test', () => { + // initializing logic + const expected = 5; + + it(`should return ${expected}`, () => { + const result = 5; + // assign result a value from functionToTest + expect(result).to.equal(expected); + // or using the should() syntax + result.should.equal(expected); + }); + // ... more tests => it + +}); + +// ... more test suites => describe diff --git a/package.json b/package.json new file mode 100755 index 0000000..4c443d7 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "iobroker.epson_xp860", + "version": "1.0.0", + "description": "Ink status of Epson Expression Photo XP-860", + "author": { + "name": "bowao", + "email": "cryolab@web.de" + }, + "homepage": "https://github.com/bowao/ioBroker.epson_xp860", + "license": "MIT", + "keywords": [ + "ioBroker", + "hardware", + "office", + "printer", + "ink", + "epson", + "drucker", + "tinte" + ], + "repository": { + "type": "git", + "url": "https://github.com/bowao/ioBroker.epson_xp860" + }, + "dependencies": { + "request": "^2.72.0", + "@iobroker/adapter-core": "^1.0.3" + }, + "devDependencies": { + "@iobroker/testing": "^1.2.6", + "@types/chai": "^4.2.3", + "@types/chai-as-promised": "^7.1.2", + "@types/gulp": "^4.0.6", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.21", + "@types/proxyquire": "^1.3.28", + "@types/sinon": "^7.5.0", + "@types/sinon-chai": "^3.2.3", + "axios": "^0.19.0", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^6.5.1", + "gulp": "^4.0.2", + "mocha": "^6.2.1", + "proxyquire": "^2.1.3", + "sinon": "^7.5.0", + "sinon-chai": "^3.3.0" + }, + "main": "main.js", + "scripts": { + "test:js": "mocha --opts test/mocha.custom.opts", + "test:package": "mocha test/package --exit", + "test:unit": "mocha test/unit --exit", + "test:integration": "mocha test/integration --exit", + "test": "npm run test:js && npm run test:package", + "lint": "eslint" + }, + "bugs": { + "url": "https://github.com/bowao/ioBroker.epson_xp860/issues" + }, + "readmeFilename": "README.md" +} diff --git a/test/integration.js b/test/integration.js new file mode 100755 index 0000000..1b3453e --- /dev/null +++ b/test/integration.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options +tests.integration(path.join(__dirname, '..')); diff --git a/test/mocha.custom.opts b/test/mocha.custom.opts new file mode 100755 index 0000000..703f749 --- /dev/null +++ b/test/mocha.custom.opts @@ -0,0 +1,2 @@ +--require test/mocha.setup.js +{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js} \ No newline at end of file diff --git a/test/mocha.setup.js b/test/mocha.setup.js new file mode 100755 index 0000000..15b9051 --- /dev/null +++ b/test/mocha.setup.js @@ -0,0 +1,14 @@ +// Don't silently swallow unhandled rejections +process.on('unhandledRejection', (e) => { + throw e; +}); + +// enable the should interface with sinon +// and load chai-as-promised and sinon-chai by default +const sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const { should, use } = require('chai'); + +should(); +use(sinonChai); +use(chaiAsPromised); \ No newline at end of file diff --git a/test/package.js b/test/package.js new file mode 100755 index 0000000..38eacc8 --- /dev/null +++ b/test/package.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Validate the package files +tests.packageFiles(path.join(__dirname, '..')); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100755 index 0000000..a2308c1 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noImplicitAny": false + }, + "include": [ + "./**/*.js" + ] +} diff --git a/test/unit.js b/test/unit.js new file mode 100755 index 0000000..8e27b3a --- /dev/null +++ b/test/unit.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Run unit tests - See https://github.com/ioBroker/testing for a detailed explanation and further options +tests.unit(path.join(__dirname, '..')); diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 0000000..7bff724 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compileOnSave": true, + "compilerOptions": { + // do not compile anything, this file is just to configure type checking + "noEmit": true, + + // check JS files + "allowJs": true, + "checkJs": true, + + "module": "commonjs", + "moduleResolution": "node", + // this is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, + + // Set this to false if you want to disable the very strict rules (not recommended) + "strict": true, + // Or enable some of those features for more fine-grained control + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + "noImplicitAny": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + + // Consider targetting es2017 or higher if you require the new NodeJS 8+ features + "target": "es2015", + + }, + "include": [ + "**/*.js", + "**/*.d.ts" + ], + "exclude": [ + "node_modules/**", + "admin/**" + ] +} \ No newline at end of file diff --git a/widgets/ink_widget b/widgets/ink_widget new file mode 100755 index 0000000..1e83f1a --- /dev/null +++ b/widgets/ink_widget @@ -0,0 +1 @@ +[{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_magentalight","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2426

Magenta Light","html_append_singular":"%

T2426

Magenta Light","html_prepend":"","name":"Magenta Light Info","html":"Druckertinte","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_extended":false,"is_comma":"true","factor":"1","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"421px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"55px","height":"113px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_magenta","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2423

Magenta","html_append_singular":"%

T2423

Magenta","html_prepend":"","name":"Magenta Info","html":"Druckertinte","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_extended":false,"is_comma":"true","factor":"1","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"354px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"55px","height":"96px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_black","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2421

Black","html_append_singular":"%

T2421

Black","html_prepend":"","name":"Black Info","html":"Druckertinte","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_extended":false,"is_comma":"true","factor":"1","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"287px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"53px","height":"96px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_yellow","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2424

Yellow","html_append_singular":"%

T2424

Yellow","html_prepend":"","name":"Yellow Info","html":"Druckertinte","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_extended":false,"is_comma":"true","factor":"1","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"221px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"53px","height":"96px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_cyanlight","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2425

Cyan Light","html_append_singular":"%

T2425

Cyan Light","html_prepend":"","name":"Cyan Light Info","html":"Druckertinte","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_extended":false,"is_comma":"true","factor":"1","g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"154px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"53px","height":"103px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplValueFloat","data":{"oid":"epson_xp860.0.ink_cyan","visibility-cond":"==","visibility-val":1,"html_append_plural":"%

T2422

Cyan","html_append_singular":"%

T2422

Cyan","html_prepend":"","name":"Cyan Info","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_extended":false,"is_comma":"true","factor":"1","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"88px","top":"380px","z-index":"25","color":"rgba(250,250,250,1)","width":"53px","height":"97px","font-family":"","font-size":"14px","text-align":"center"},"widgetSet":"basic"},{"tpl":"tplHtml","data":{"visibility-cond":"==","visibility-val":1,"refreshInterval":"0","html":"Model: \n{epson_xp860.0.printerModel}\n
\nBezeichnung: \n{epson_xp860.0.printerName}\n
\nMAC-Adresse: \n{epson_xp860.0.printerMac}\n
","name":"Model/Bezeichnung/MAC","visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_signals":false,"signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"86px","top":"79px","z-index":"15","color":"rgba(250,250,250,1)","font-family":"","font-size":"14px","width":"390px","height":"80px"},"widgetSet":"basic"},{"tpl":"tplHtml","data":{"visibility-cond":"==","visibility-val":1,"refreshInterval":"0","name":"Hinweis Tintenpackung","html":"","signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0},"style":{"left":"89px","top":"165px","z-index":"25","color":"rgba(250,250,250,1)","font-family":"","font-size":"14px","width":"182px","height":"26px","font-style":"oblique"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_magentalight","visibility-cond":"==","visibility-val":1,"factor":"1","color":"#ecb1eb","name":"Magenta Light","reverse":true,"orientation":"vertical","html":"Druckertinte","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_magentalight"},"style":{"left":"420px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_magenta","visibility-cond":"==","visibility-val":1,"factor":"1","color":"Magenta","name":"Magenta","reverse":true,"orientation":"vertical","html":"Druckertinte","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_magenta"},"style":{"left":"353px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_black","visibility-cond":"==","visibility-val":1,"factor":"1","color":"black","name":"Black","reverse":true,"orientation":"vertical","html":"Druckertinte","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_black"},"style":{"left":"286px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_cyanlight","visibility-cond":"==","visibility-val":1,"factor":"1","color":"#d5f1f1","name":"Light Cyan","reverse":true,"orientation":"vertical","html":"Druckertinte","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_cyanlight"},"style":{"left":"153px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_yellow","visibility-cond":"==","visibility-val":1,"factor":"1","color":"yellow","name":"Yellow","reverse":true,"orientation":"vertical","html":"Druckertinte","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_yellow"},"style":{"left":"220px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueFloatBar","data":{"oid":"epson_xp860.0.ink_cyan","visibility-cond":"==","visibility-val":1,"factor":"1","color":"cyan","name":"Cyan","reverse":true,"orientation":"vertical","min":"0","max":"100","signals-cond-0":"<","signals-val-0":"10","signals-icon-0":"/vis/signals/waterColor.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"20","signals-vert-0":"5","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"visibility-groups-action":"hide","g_gestures":false,"g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.ink_cyan","signals-icon-style-0":""},"style":{"left":"87px","top":"193px","z-index":"22","color":"rgba(250,250,250,1);","width":"53px","height":"180px"},"widgetSet":"basic"},{"tpl":"tplValueTimestamp","data":{"oid":"epson_xp860.0.ink_cyan","visibility-cond":"==","visibility-val":1,"format_date":"DD.MM.YYYY hh:mm:ss","html_prepend":"Letzte Aktualisierung:  ","name":"Letzte Aktualisierung (Cyan)","signals-cond-0":"==","signals-val-0":"false","signals-icon-0":"/vis/signals/online.png","signals-icon-size-0":0,"signals-blink-0":true,"signals-horz-0":"11","signals-vert-0":"0","signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false,"is_milliseconds":true,"visibility-groups-action":"hide","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"signals-oid-0":"epson_xp860.0.printerInfo.requestResponded","signals-text-0":"","signals-text-style-0":""},"style":{"left":"88px","top":"501px","z-index":"18","color":"rgba(250,250,250,1)","font-family":"","text-align":"right","width":"390px","height":"32px","font-size":"14px","line-height":"2"},"widgetSet":"basic"},{"tpl":"tplHtml","data":{"refreshInterval":"0","html":"Druckertinte","name":"Überschrift","g_css_background":false,"g_css_border":false,"g_css_shadow_padding":false,"g_gestures":false,"g_last_change":false,"lc-type":"last-change","lc-is-interval":true,"lc-is-moment":false,"lc-format":"","lc-position-vert":"top","lc-position-horz":"right","lc-offset-vert":0,"lc-offset-horz":0,"lc-font-size":"12px","lc-font-family":"","lc-font-style":"","lc-bkg-color":"","lc-color":"","lc-border-width":"0","lc-border-style":"","lc-border-color":"","lc-border-radius":10,"lc-zindex":0,"g_visibility":true,"visibility-cond":"==","visibility-val":1,"visibility-groups-action":"hide","g_signals":false,"signals-cond-0":"==","signals-val-0":true,"signals-icon-0":"/vis/signals/lowbattery.png","signals-icon-size-0":0,"signals-blink-0":false,"signals-horz-0":0,"signals-vert-0":0,"signals-hide-edit-0":false,"signals-cond-1":"==","signals-val-1":true,"signals-icon-1":"/vis/signals/lowbattery.png","signals-icon-size-1":0,"signals-blink-1":false,"signals-horz-1":0,"signals-vert-1":0,"signals-hide-edit-1":false,"signals-cond-2":"==","signals-val-2":true,"signals-icon-2":"/vis/signals/lowbattery.png","signals-icon-size-2":0,"signals-blink-2":false,"signals-horz-2":0,"signals-vert-2":0,"signals-hide-edit-2":false},"style":{"left":"83px","top":"22px","width":"200px","height":"29px","color":"rgba(250,250,250,1)","text-align":"left","text-shadow":"2px 3px rgba(250,250,250,0.2)","font-family":"","font-style":"italic","font-variant":"","font-weight":"","font-size":"25px","z-index":"40"},"widgetSet":"basic"}] \ No newline at end of file