Initial commit
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.idea
|
||||
tmp
|
||||
admin/i18n/*/flat.txt
|
||||
admin/i18n/flat.txt
|
||||
iob_npm.done
|
||||
package-lock.json
|
||||
12
.npmignore
Normal file
12
.npmignore
Normal file
@@ -0,0 +1,12 @@
|
||||
gulpfile.js
|
||||
tasks
|
||||
test
|
||||
tmp
|
||||
docs
|
||||
.idea
|
||||
.travis.yml
|
||||
.git
|
||||
appveyor.yml
|
||||
admin/i18n
|
||||
iob_npm.done
|
||||
package-lock.json
|
||||
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
language: node_js
|
||||
node_js:
|
||||
- '4'
|
||||
- '6'
|
||||
- '8'
|
||||
- '10'
|
||||
before_script:
|
||||
- export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
|
||||
- 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi'
|
||||
- npm -v
|
||||
- npm install winston@2.3.1
|
||||
- npm install mqtt@2.13.0
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2019 bluefox <dogafox@gmail.com>
|
||||
|
||||
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.
|
||||
|
||||
194
README.md
Executable file
194
README.md
Executable file
@@ -0,0 +1,194 @@
|
||||

|
||||
# ioBroker Sonoff
|
||||
==============
|
||||
|
||||
[](https://www.npmjs.com/package/iobroker.sonoff)
|
||||
[](https://www.npmjs.com/package/iobroker.sonoff)
|
||||
[](https://travis-ci.org/ioBroker/ioBroker.sonoff)
|
||||
|
||||
[](https://nodei.co/npm/iobroker.sonoff/)
|
||||
|
||||
Requires node.js 4.0 or higher.
|
||||
|
||||
## Usage
|
||||
|
||||
This adapter communicates with Sonoff devices with Tasmota firmware or ESP devices via MQTT.
|
||||
|
||||
Following topics are expected:
|
||||
- tele/DeviceNAME/STATE
|
||||
- tele/DeviceNAME/SENSOR
|
||||
- tele/DeviceNAME/INFOx
|
||||
- tele/DeviceNAME/ENERGY
|
||||
- cmnd/DeviceNAME/POWERx
|
||||
- stat/DeviceNAME/POWERx
|
||||
- /DeviceNAME/BM280/Temperature
|
||||
- /DeviceNAME/BM280/Humidity
|
||||
- /DeviceNAME/BM280/Temperatur
|
||||
- /DeviceNAME/BM280/Feuchtigkeit
|
||||
- /DeviceNAME/BM280/Vcc
|
||||
- /DeviceNAME/BM280/VCC
|
||||
- /DeviceNAME/BM280/Laufzeit
|
||||
- /DeviceNAME/BM280/RSSI
|
||||
- /DeviceNAME/BM280/POWER
|
||||
- /DeviceNAME/BM280/POWER1
|
||||
- /DeviceNAME/BM280/POWER2
|
||||
- /DeviceNAME/BM280/POWER3
|
||||
- /DeviceNAME/BM280/POWER4
|
||||
- /DeviceNAME/BM280/Switch1
|
||||
- /DeviceNAME/BM280/Switch2
|
||||
- /DeviceNAME/BM280/Total
|
||||
- /DeviceNAME/BM280/Today
|
||||
- /DeviceNAME/BM280/heute
|
||||
- /DeviceNAME/BM280/Yesterday
|
||||
- /DeviceNAME/BM280/gestern
|
||||
- /DeviceNAME/BM280/Faktor
|
||||
- /DeviceNAME/BM280/Factor
|
||||
- /DeviceNAME/BM280/Power
|
||||
- /DeviceNAME/BM280/Leistung
|
||||
- /DeviceNAME/BM280/Voltage
|
||||
- /DeviceNAME/BM280/Spannung
|
||||
- /DeviceNAME/BM280/Current
|
||||
- /DeviceNAME/BM280/Strom
|
||||
- /DeviceNAME/BM280/Punkt
|
||||
- /DeviceNAME/BM280/Counter1
|
||||
- /DeviceNAME/BM280/Counter2
|
||||
- /DeviceNAME/BM280/Counter3
|
||||
- /DeviceNAME/BM280/Counter4
|
||||
- /DeviceNAME/BM280/Pressure
|
||||
- /DeviceNAME/BM280/SeaPressure
|
||||
- /DeviceNAME/BM280/Druck
|
||||
- /DeviceNAME/BM280/Approx. Altitude
|
||||
- /DeviceNAME/BM280/Module
|
||||
- /DeviceNAME/BM280/Version
|
||||
- /DeviceNAME/BM280/Hostname
|
||||
- /DeviceNAME/BM280/IPAddress
|
||||
- /DeviceNAME/BM280/IPaddress
|
||||
- /DeviceNAME/BM280/RestartReason
|
||||
- /DeviceNAME/BM280/CarbonDioxide
|
||||
- /DeviceNAME/DHT11/Illuminance
|
||||
- /DeviceNAME/SonoffSC/Light
|
||||
- /DeviceNAME/SonoffSC/Noise
|
||||
- /DeviceNAME/SonoffSC/AirQuality
|
||||
- /DeviceNAME/SDS0X1/PM2.5
|
||||
- /DeviceNAME/SDS0X1/PM10
|
||||
- /DeviceNAME/SDS0X1/UvLevel
|
||||
- /DeviceNAME/SDS0X1/Latitude
|
||||
- /DeviceNAME/SDS0X1/Longitude
|
||||
- /DeviceNAME/SR04/Distance
|
||||
|
||||
**Note**: The list could be easily extended. Please send *Pull Requests* or *debug data* for unknown states to developer (via issue).
|
||||
|
||||
## Auto-creation of objects
|
||||
In the web config you can determine which MQTT telegrams create the new objects not in default datapoints
|
||||
|
||||
* TELE_SENSOR creates objects from tele/xxx/SENSOR telegrams
|
||||
* TELE_STATE creates objects from tele/xxx/STATE telegrams
|
||||
* STAT_RESULT creates objects from stat/xxx/RESULT telegrams
|
||||
|
||||
Usually TELE_SENSOR should be sufficient for most users.
|
||||
|
||||
## Flags for LED controllers
|
||||
The mode states will be created only if device has one of the states:
|
||||
|
||||
- 'Red', 'Green', 'Blue', 'WW', 'CW', 'Color', 'RGB_POWER', 'WW_POWER', 'CW_POWER', 'Hue', 'Saturation'
|
||||
|
||||
States:
|
||||
|
||||
* modeLedExor - exor for white LEDs and color leds => if the white LEDs are switched on, color LEDs are switched off and vice versa (default true)
|
||||
* modeReadColors - allow for color read from MQTT (default false)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2.2.3 (2019-01-10)
|
||||
* (simatec) Support for comapct mode
|
||||
|
||||
### 2.2.2 (2018-06-22)
|
||||
* (bluefox) Configuration was fixed
|
||||
|
||||
### 2.2.1 (2018-06-20)
|
||||
* (bluefox) '-' in names was allowed again
|
||||
|
||||
### 2.2.0 (2018-05-22)
|
||||
* (gemu2015) auto generate objects, support for arrays (channel), ledcontrollers improved
|
||||
|
||||
### 2.1.3 (2018-05-08)
|
||||
* (bluefox) Added HC-SR04 Ultrasonic Sensor
|
||||
|
||||
### 2.1.2 (2018-04-23)
|
||||
* (bluefox) Added support of UvLight, Longitude and Latitude
|
||||
|
||||
### 2.1.1 (2018-04-13)
|
||||
* (bluefox) Support of the particle concentration sensor
|
||||
|
||||
### 2.1.0 (2018-03-30)
|
||||
* (gemu2015) Support of the devices control (many thanks :)
|
||||
* (gemu2015) Support of many new values
|
||||
* (modmax) Update alive status of the clients
|
||||
* (modmax) Added POWER5-8 and Switch3-4
|
||||
|
||||
### 2.0.2 (2018-03-19)
|
||||
* (modmax) Fixing reconnection of clients
|
||||
* (bluefox) Add SeaPressure
|
||||
|
||||
### 2.0.1 (2018-03-17)
|
||||
* (bluefox) Replace stream handler
|
||||
* (bluefox) Add timeout for clients
|
||||
* (bluefox) Add Light/Noise/AirQuality
|
||||
* (bluefox) Do not send pingresp for invalid clients
|
||||
|
||||
### 1.0.3 (2018-03-03)
|
||||
* (bluefox) Add Analog0/1/2/3 sensor
|
||||
|
||||
### 1.0.2 (2018-02-17)
|
||||
* (Apollon77) Add Illuminance sensor
|
||||
|
||||
### 1.0.1 (2018-02-05)
|
||||
* (bluefox) Ready for admin3
|
||||
* (bluefox) Added CO2 sensor
|
||||
|
||||
### 1.0.0 (2017-11-27)
|
||||
* (AlZiBa) typo @ alive
|
||||
* (AlZiBa) add Todays power consumption for Sonoff POW
|
||||
* (AlZiBa) unit of power consumption is kWh
|
||||
|
||||
### 0.3.3 (2017-11-03)
|
||||
* (bluefox) Add counters
|
||||
|
||||
### 0.3.2 (2017-10-22)
|
||||
* (Tan-DE) Small change for Switch1. Switch2 and additional IPaddress added.
|
||||
|
||||
### 0.3.1 (2017-10-12)
|
||||
* (bluefox) Fix tests and LWT
|
||||
|
||||
### 0.3.0 (2017-10-06)
|
||||
* (bluefox) Add INFO and ESP
|
||||
|
||||
### 0.2.0 (2017-10-05)
|
||||
* (bluefox) Add ENERGY and DS18x20
|
||||
|
||||
### 0.1.0 (2017-10-01)
|
||||
* (bluefox) initial commit
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2019, bluefox <dogafox@gmail.com>
|
||||
|
||||
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.
|
||||
49
admin/i18n/de/translations.json
Normal file
49
admin/i18n/de/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Sonoff Adapter-Einstellungen",
|
||||
"Type:": "Typ",
|
||||
"Port:": "Port",
|
||||
"User:": "Username",
|
||||
"Password:": "Kennwort",
|
||||
"Password confirmation:": "Kennwort-Wiederholung",
|
||||
"URL:": "URL",
|
||||
"Secure:": "SSL",
|
||||
"Public certificate:": "Publikzertifikat",
|
||||
"Private certificate:": "Privatzertifikat",
|
||||
"Chained certificate:": "Kettenzertifikat",
|
||||
"Patterns:": "Subscribe patterns",
|
||||
"Use WebSockets:": "Benutze auch WebSockets",
|
||||
"Connection": "Verbindung",
|
||||
"Sonoff Settings": "Sonoff Einstellungen",
|
||||
"Client ID:": "Client ID",
|
||||
"chars": "Symbolen",
|
||||
"ms": "ms",
|
||||
"Interval before send topics by connection:": "Interval vom Senden von allen Topics bei der verbindung",
|
||||
"Send interval:": "Sendeintervall",
|
||||
"Use chunk patch:": "Benutze Patch für Chunking",
|
||||
"Divided by comma": "Mit Komma getrennt, z.B 'mqtt/0/#,javascript/#'",
|
||||
"Mask to publish": "z.B 'mqtt.0.*,javascript.*'",
|
||||
"Store only on change:": "Publish nur bei Änderung",
|
||||
"Trace output for every message:": "Trace Ausgabe für jede Meldung",
|
||||
"Test connection": "Teste Verbindung zum Server",
|
||||
"Result: ": "Ergebnis ",
|
||||
"connected": "verbunden",
|
||||
"Main settings": "Allgemeine Einstellungen",
|
||||
"Connection settings": "Verbindungseinstellungen",
|
||||
"Authentication settings": "Authentication Einstellungen",
|
||||
"Adapter settings": "Sonoff Einstellungen",
|
||||
"Mask to publish own states:": "Maske für Bekanntgeben von eigenen States",
|
||||
"Send states (ack=true) too:": "Sende auch Zustände (ack=true)",
|
||||
"Publish all states at start:": "Bekanntgeben eigene States beim Verbinden",
|
||||
"Use different topic names for set and get:": "Unterschiedliche Namen für setzten und lesen",
|
||||
"Publish states on subscribe:": "Bekanntgeben von States bei Subscribe",
|
||||
"Set certificates or load it first in the system settings (right top).": "Setze Zertificate oder lade die erst unter System/Einstellungen (oben rechts).",
|
||||
"Prefix for topics:": "Prefix für alle Topics",
|
||||
"Max topic length:": "Maximale Topicnamelänge",
|
||||
"Server": "Server/broker",
|
||||
"Client": "Client/subscriber",
|
||||
"For tele/SENSOR": "Für tele/SENSOR",
|
||||
"For tele/STATE": "Für tele/STATE",
|
||||
"For stat/RESULT": "Für stat/RESULT",
|
||||
"Client timeout (sec):": "Client-Zeitüberschreitung (Sek.)",
|
||||
"Auto creation of states": "Automatische Erstellung von Zuständen"
|
||||
}
|
||||
49
admin/i18n/en/translations.json
Normal file
49
admin/i18n/en/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Sonoff adapter settings",
|
||||
"Type:": "Type",
|
||||
"Port:": "Port",
|
||||
"User:": "User",
|
||||
"Password:": "Password",
|
||||
"Password confirmation:": "Password confirmation",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Secure",
|
||||
"Public certificate:": "Public certificate",
|
||||
"Private certificate:": "Private certificate",
|
||||
"Chained certificate:": "Chained certificate",
|
||||
"Patterns:": "Subscribe patterns",
|
||||
"Use WebSockets:": "Use WebSockets too",
|
||||
"Connection": "Connection",
|
||||
"Sonoff Settings": "Sonoff Settings",
|
||||
"Client ID:": "Client ID",
|
||||
"chars": "chars",
|
||||
"ms": "ms",
|
||||
"Interval before send topics by connection:": "Interval before send topics by connection",
|
||||
"Send interval:": "Send interval",
|
||||
"Use chunk patch:": "Use chunk patch",
|
||||
"Divided by comma": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'",
|
||||
"Mask to publish": "e.g. 'mqtt.0.*,javascript.*'",
|
||||
"Store only on change:": "Publish only on change",
|
||||
"Trace output for every message:": "Trace output for every message",
|
||||
"Test connection": "Test connection with Server",
|
||||
"Result: ": "Result ",
|
||||
"connected": "connected",
|
||||
"Main settings": "Main settings",
|
||||
"Connection settings": "Connection settings",
|
||||
"Authentication settings": "Authentication settings",
|
||||
"Adapter settings": "Sonoff settings",
|
||||
"Mask to publish own states:": "Mask to publish own states",
|
||||
"Send states (ack=true) too:": "Send states (ack=true) too",
|
||||
"Publish all states at start:": "Publish own states on connect",
|
||||
"Use different topic names for set and get:": "Use different topic names for set and get",
|
||||
"Publish states on subscribe:": "Publish states on subscribe",
|
||||
"Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).",
|
||||
"Prefix for topics:": "Prefix for all topics",
|
||||
"Max topic length:": "Max topic name length",
|
||||
"Server": "Server/broker",
|
||||
"Client": "Client/subscriber",
|
||||
"For tele/SENSOR": "For tele/SENSOR",
|
||||
"For tele/STATE": "For tele/STATE",
|
||||
"For stat/RESULT": "For stat/RESULT",
|
||||
"Client timeout (sec):": "Client timeout (sec)",
|
||||
"Auto creation of states": "Auto creation of states"
|
||||
}
|
||||
49
admin/i18n/es/translations.json
Normal file
49
admin/i18n/es/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Configuraciones del adaptador Sonoff",
|
||||
"Type:": "Tipo",
|
||||
"Port:": "Puerto",
|
||||
"User:": "Usuario",
|
||||
"Password:": "Contraseña",
|
||||
"Password confirmation:": "confirmación de contraseña",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Seguro",
|
||||
"Public certificate:": "Certificado público",
|
||||
"Private certificate:": "Certificado privado",
|
||||
"Chained certificate:": "Certificado encadenado",
|
||||
"Patterns:": "Suscribir patrones",
|
||||
"Use WebSockets:": "Use WebSockets también",
|
||||
"Connection": "Conexión",
|
||||
"Sonoff Settings": "Configuraciones Sonoff",
|
||||
"Client ID:": "Identificación del cliente",
|
||||
"chars": "caracteres",
|
||||
"ms": "Sra",
|
||||
"Interval before send topics by connection:": "Intervalo antes de enviar temas por conexión",
|
||||
"Send interval:": "Intervalo de envío",
|
||||
"Use chunk patch:": "Use el fragmento de fragmento",
|
||||
"Divided by comma": "Dividido por coma, p. 'mqtt/0/#, javascript/#'",
|
||||
"Mask to publish": "p.ej. 'mqtt.0.*, javascript.*'",
|
||||
"Store only on change:": "Publicar solo en el cambio",
|
||||
"Trace output for every message:": "Salida de seguimiento para cada mensaje",
|
||||
"Test connection": "Prueba de conexión con el servidor",
|
||||
"Result: ": "Resultado",
|
||||
"connected": "conectado",
|
||||
"Main settings": "Ajustes principales",
|
||||
"Connection settings": "Configuración de conexión",
|
||||
"Authentication settings": "Configuraciones de autenticación",
|
||||
"Adapter settings": "Configuraciones Sonoff",
|
||||
"Mask to publish own states:": "Máscara para publicar estados propios",
|
||||
"Send states (ack=true) too:": "Enviar estados (ack = true) también",
|
||||
"Publish all states at start:": "Publicar estados propios al conectar",
|
||||
"Use different topic names for set and get:": "Use diferentes nombres de tema para establecer y obtener",
|
||||
"Publish states on subscribe:": "Publicar estados al suscribirse",
|
||||
"Set certificates or load it first in the system settings (right top).": "Establezca certificados o cárguelos primero en la configuración del sistema (arriba a la derecha).",
|
||||
"Prefix for topics:": "Prefijo para todos los temas",
|
||||
"Max topic length:": "Longitud máxima del nombre del tema",
|
||||
"Server": "Servidor/intermediario",
|
||||
"Client": "Cliente/suscriptor",
|
||||
"For tele/SENSOR": "Por tele/SENSOR",
|
||||
"For tele/STATE": "Por tele/STATE",
|
||||
"For stat/RESULT": "Por stat/RESULT",
|
||||
"Client timeout (sec):": "Tiempo de espera del cliente (seg)",
|
||||
"Auto creation of states": "Creación automática de estados"
|
||||
}
|
||||
49
admin/i18n/fr/translations.json
Normal file
49
admin/i18n/fr/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Paramètres de l'adaptateur Sonoff",
|
||||
"Type:": "Type",
|
||||
"Port:": "Port",
|
||||
"User:": "Utilisateur",
|
||||
"Password:": "Mot de passe",
|
||||
"Password confirmation:": "Confirmation mot de passe",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Garantir",
|
||||
"Public certificate:": "Certificat public",
|
||||
"Private certificate:": "Certificat privé",
|
||||
"Chained certificate:": "Certificat chaîné",
|
||||
"Patterns:": "Modèles de souscription",
|
||||
"Use WebSockets:": "Utilisez aussi WebSockets",
|
||||
"Connection": "Connexion",
|
||||
"Sonoff Settings": "Paramètres Sonoff",
|
||||
"Client ID:": "identité du client",
|
||||
"chars": "chars",
|
||||
"ms": "Mme",
|
||||
"Interval before send topics by connection:": "Intervalle avant d'envoyer des sujets par connexion",
|
||||
"Send interval:": "Envoyer un intervalle",
|
||||
"Use chunk patch:": "Utiliser un patch de morceau",
|
||||
"Divided by comma": "Divisé par une virgule, par ex. 'mqtt/0/#, javascript/#'",
|
||||
"Mask to publish": "par exemple. 'mqtt.0.*, javascript.*'",
|
||||
"Store only on change:": "Publier uniquement en cas de modification",
|
||||
"Trace output for every message:": "Trace de sortie pour chaque message",
|
||||
"Test connection": "Tester la connexion avec le serveur",
|
||||
"Result: ": "Résultat",
|
||||
"connected": "connecté",
|
||||
"Main settings": "Réglages principaux",
|
||||
"Connection settings": "Paramètres de connexion",
|
||||
"Authentication settings": "Paramètres d'authentification",
|
||||
"Adapter settings": "Paramètres Sonoff",
|
||||
"Mask to publish own states:": "Masque pour publier ses propres états",
|
||||
"Send states (ack=true) too:": "Envoyer des états (ack = true) aussi",
|
||||
"Publish all states at start:": "Publier ses propres états sur se connecter",
|
||||
"Use different topic names for set and get:": "Utilisez différents noms de sujet pour définir et obtenir",
|
||||
"Publish states on subscribe:": "Publier les états sur l'abonnement",
|
||||
"Set certificates or load it first in the system settings (right top).": "Définissez des certificats ou chargez-les d'abord dans les paramètres du système (en haut à droite).",
|
||||
"Prefix for topics:": "Préfixe pour tous les sujets",
|
||||
"Max topic length:": "Longueur maximale du nom de la rubrique",
|
||||
"Server": "Serveur/courtier",
|
||||
"Client": "Client/abonné",
|
||||
"For tele/SENSOR": "Pour tele/SENSOR",
|
||||
"For tele/STATE": "Pour tele/STATE",
|
||||
"For stat/RESULT": "Pour stat/RESULT",
|
||||
"Client timeout (sec):": "Délai d'expiration du client (sec)",
|
||||
"Auto creation of states": "Création automatique d'états"
|
||||
}
|
||||
49
admin/i18n/it/translations.json
Normal file
49
admin/i18n/it/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Impostazioni dell'adattatore Sonoff",
|
||||
"Type:": "genere",
|
||||
"Port:": "Porta",
|
||||
"User:": "Utente",
|
||||
"Password:": "Parola d'ordine",
|
||||
"Password confirmation:": "conferma password",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Sicuro",
|
||||
"Public certificate:": "Certificato pubblico",
|
||||
"Private certificate:": "Certificato privato",
|
||||
"Chained certificate:": "Certificato incatenato",
|
||||
"Patterns:": "Iscriviti modelli",
|
||||
"Use WebSockets:": "Utilizza anche WebSockets",
|
||||
"Connection": "Connessione",
|
||||
"Sonoff Settings": "Impostazioni Sonoff",
|
||||
"Client ID:": "Identificativo cliente",
|
||||
"chars": "caratteri",
|
||||
"ms": "Signorina",
|
||||
"Interval before send topics by connection:": "Intervallo prima di inviare argomenti per connessione",
|
||||
"Send interval:": "Invia intervallo",
|
||||
"Use chunk patch:": "Usa patch pezzo",
|
||||
"Divided by comma": "Diviso in virgola, ad es. 'MQTT/0/#, JavaScript/#'",
|
||||
"Mask to publish": "per esempio. 'Mqtt.0. *, Javascript. *'",
|
||||
"Store only on change:": "Pubblica solo sul cambiamento",
|
||||
"Trace output for every message:": "Traccia l'output per ogni messaggio",
|
||||
"Test connection": "Verifica la connessione con il server",
|
||||
"Result: ": "Risultato",
|
||||
"connected": "collegato",
|
||||
"Main settings": "Impostazioni principali",
|
||||
"Connection settings": "Impostazioni di connessione",
|
||||
"Authentication settings": "Impostazioni di autenticazione",
|
||||
"Adapter settings": "Impostazioni Sonoff",
|
||||
"Mask to publish own states:": "Maschera per pubblicare i propri stati",
|
||||
"Send states (ack=true) too:": "Invia stati (ack = true) anche",
|
||||
"Publish all states at start:": "Pubblica i tuoi stati sulla connessione",
|
||||
"Use different topic names for set and get:": "Usa diversi nomi di argomenti per impostare e ottenere",
|
||||
"Publish states on subscribe:": "Pubblica stati su iscriviti",
|
||||
"Set certificates or load it first in the system settings (right top).": "Imposta i certificati o caricali prima nelle impostazioni di sistema (in alto a destra).",
|
||||
"Prefix for topics:": "Prefisso per tutti gli argomenti",
|
||||
"Max topic length:": "Lunghezza massima del nome dell'argomento",
|
||||
"Server": "Server/mediatore",
|
||||
"Client": "Client/abbonato",
|
||||
"For tele/SENSOR": "Per tele/SENSOR",
|
||||
"For tele/STATE": "Per tele/STATE",
|
||||
"For stat/RESULT": "Per stat/RESULT",
|
||||
"Client timeout (sec):": "Timeout del client (sec)",
|
||||
"Auto creation of states": "Creazione automatica di stati"
|
||||
}
|
||||
49
admin/i18n/nl/translations.json
Normal file
49
admin/i18n/nl/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Configurações do adaptador Sonoff",
|
||||
"Type:": "Tipo",
|
||||
"Port:": "Porta",
|
||||
"User:": "Do utilizador",
|
||||
"Password:": "Senha",
|
||||
"Password confirmation:": "ConfirmaÇão Da Senha",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Seguro",
|
||||
"Public certificate:": "Certificado público",
|
||||
"Private certificate:": "Certificado privado",
|
||||
"Chained certificate:": "Certificado acorrentado",
|
||||
"Patterns:": "Assinar padrões",
|
||||
"Use WebSockets:": "Use também WebSockets",
|
||||
"Connection": "Conexão",
|
||||
"Sonoff Settings": "Configurações de Sonoff",
|
||||
"Client ID:": "ID do Cliente",
|
||||
"chars": "caracteres",
|
||||
"ms": "Senhora",
|
||||
"Interval before send topics by connection:": "Intervalo antes de enviar tópicos por conexão",
|
||||
"Send interval:": "Intervalo de envio",
|
||||
"Use chunk patch:": "Use patch de pedaços",
|
||||
"Divided by comma": "Dividido por vírgula, e. 'mqtt/0/#, javascript/#'",
|
||||
"Mask to publish": "por exemplo. 'mqtt.0.*, javascript.*'",
|
||||
"Store only on change:": "Publique apenas na mudança",
|
||||
"Trace output for every message:": "Saída de rastreamento para cada mensagem",
|
||||
"Test connection": "Teste a conexão com o Servidor",
|
||||
"Result: ": "Resultado",
|
||||
"connected": "conectado",
|
||||
"Main settings": "Configurações principais",
|
||||
"Connection settings": "Configurações de conexão",
|
||||
"Authentication settings": "Configurações de autenticação",
|
||||
"Adapter settings": "Configurações de Sonoff",
|
||||
"Mask to publish own states:": "Máscara para publicar estados próprios",
|
||||
"Send states (ack=true) too:": "Enviar estados (ack = true) também",
|
||||
"Publish all states at start:": "Publique seus próprios estados na conexão",
|
||||
"Use different topic names for set and get:": "Use nomes de tópicos diferentes para definir e obter",
|
||||
"Publish states on subscribe:": "Publique estados na assinatura",
|
||||
"Set certificates or load it first in the system settings (right top).": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).",
|
||||
"Prefix for topics:": "Prefix para todos os tópicos",
|
||||
"Max topic length:": "Nome máximo do nome do tópico",
|
||||
"Server": "Servidor/corretor",
|
||||
"Client": "Cliente/assinante",
|
||||
"For tele/SENSOR": "Voor tele/SENSOR",
|
||||
"For tele/STATE": "Voor tele/STATE",
|
||||
"For stat/RESULT": "Voor stat/RESULT",
|
||||
"Client timeout (sec):": "Clienttimeout (sec)",
|
||||
"Auto creation of states": "Automatisch creëren van staten"
|
||||
}
|
||||
49
admin/i18n/pl/translations.json
Normal file
49
admin/i18n/pl/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Ustawienia adaptera Sonoff",
|
||||
"Type:": "Rodzaj",
|
||||
"Port:": "Port",
|
||||
"User:": "Użytkownik",
|
||||
"Password:": "Hasło",
|
||||
"Password confirmation:": "Potwierdzenie hasła",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Bezpieczne",
|
||||
"Public certificate:": "Certyfikat publiczny",
|
||||
"Private certificate:": "Prywatny certyfikat",
|
||||
"Chained certificate:": "Przykuty certyfikat",
|
||||
"Patterns:": "Wzorce subskrypcji",
|
||||
"Use WebSockets:": "Użyj także WebSockets",
|
||||
"Connection": "Połączenie",
|
||||
"Sonoff Settings": "Ustawienia Sonoff",
|
||||
"Client ID:": "Identyfikator klienta",
|
||||
"chars": "znaki",
|
||||
"ms": "ms",
|
||||
"Interval before send topics by connection:": "Interwał przed wysłaniem tematów według połączenia",
|
||||
"Send interval:": "Interwał wysyłania",
|
||||
"Use chunk patch:": "Użyj poprawki kawałka",
|
||||
"Divided by comma": "Podzielone przecinkiem, np. \"mqtt / 0 / #, javascript / # '",
|
||||
"Mask to publish": "na przykład \"mqtt.0. *, javascript. *\"",
|
||||
"Store only on change:": "Opublikuj tylko w przypadku zmiany",
|
||||
"Trace output for every message:": "Dane wyjściowe śledzenia dla każdej wiadomości",
|
||||
"Test connection": "Sprawdź połączenie z serwerem",
|
||||
"Result: ": "Wynik",
|
||||
"connected": "połączony",
|
||||
"Main settings": "Ustawienia główne",
|
||||
"Connection settings": "Ustawienia połączenia",
|
||||
"Authentication settings": "Ustawienia uwierzytelniania",
|
||||
"Adapter settings": "Ustawienia Sonoff",
|
||||
"Mask to publish own states:": "Maskuj, aby publikować własne stany",
|
||||
"Send states (ack=true) too:": "Wyślij także stany (ack = true)",
|
||||
"Publish all states at start:": "Opublikuj własne stany przy połączeniu",
|
||||
"Use different topic names for set and get:": "Używaj różnych nazw tematów do ustawiania i pobierania",
|
||||
"Publish states on subscribe:": "Opublikuj stany w subskrypcji",
|
||||
"Set certificates or load it first in the system settings (right top).": "Ustaw certyfikaty lub załaduj najpierw w ustawieniach systemu (prawy górny).",
|
||||
"Prefix for topics:": "Prefiks dla wszystkich tematów",
|
||||
"Max topic length:": "Maksymalna długość nazwy tematu",
|
||||
"Server": "Serwer / broker",
|
||||
"Client": "Klient / subskrybent",
|
||||
"For tele/SENSOR": "Dla tele/SENSOR",
|
||||
"For tele/STATE": "Dla tele/STATE",
|
||||
"For stat/RESULT": "Dla stat/RESULT",
|
||||
"Client timeout (sec):": "Limit czasu klienta (s)",
|
||||
"Auto creation of states": "Automatyczne tworzenie stanów"
|
||||
}
|
||||
49
admin/i18n/pt/translations.json
Normal file
49
admin/i18n/pt/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Configurações do adaptador Sonoff",
|
||||
"Type:": "Tipo",
|
||||
"Port:": "Porta",
|
||||
"User:": "Do utilizador",
|
||||
"Password:": "Senha",
|
||||
"Password confirmation:": "ConfirmaÇão Da Senha",
|
||||
"URL:": "URL",
|
||||
"Secure:": "Seguro",
|
||||
"Public certificate:": "Certificado público",
|
||||
"Private certificate:": "Certificado privado",
|
||||
"Chained certificate:": "Certificado acorrentado",
|
||||
"Patterns:": "Assinar padrões",
|
||||
"Use WebSockets:": "Use também WebSockets",
|
||||
"Connection": "Conexão",
|
||||
"Sonoff Settings": "Configurações de Sonoff",
|
||||
"Client ID:": "ID do Cliente",
|
||||
"chars": "caracteres",
|
||||
"ms": "Senhora",
|
||||
"Interval before send topics by connection:": "Intervalo antes de enviar tópicos por conexão",
|
||||
"Send interval:": "Intervalo de envio",
|
||||
"Use chunk patch:": "Use patch de pedaços",
|
||||
"Divided by comma": "Dividido por vírgula, e. 'mqtt / 0 / #, javascript / #'",
|
||||
"Mask to publish": "por exemplo. 'mqtt.0. *, javascript. *'",
|
||||
"Store only on change:": "Publique apenas na mudança",
|
||||
"Trace output for every message:": "Saída de rastreamento para cada mensagem",
|
||||
"Test connection": "Teste a conexão com o Servidor",
|
||||
"Result: ": "Resultado",
|
||||
"connected": "conectado",
|
||||
"Main settings": "Configurações principais",
|
||||
"Connection settings": "Configurações de conexão",
|
||||
"Authentication settings": "Configurações de autenticação",
|
||||
"Adapter settings": "Configurações de Sonoff",
|
||||
"Mask to publish own states:": "Máscara para publicar estados próprios",
|
||||
"Send states (ack=true) too:": "Enviar estados (ack = true) também",
|
||||
"Publish all states at start:": "Publique seus próprios estados na conexão",
|
||||
"Use different topic names for set and get:": "Use nomes de tópicos diferentes para definir e obter",
|
||||
"Publish states on subscribe:": "Publique estados na assinatura",
|
||||
"Set certificates or load it first in the system settings (right top).": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).",
|
||||
"Prefix for topics:": "Prefix para todos os tópicos",
|
||||
"Max topic length:": "Nome máximo do nome do tópico",
|
||||
"Server": "Servidor / corretor",
|
||||
"Client": "Cliente / assinante",
|
||||
"For tele/SENSOR": "Para tele/SENSOR",
|
||||
"For tele/STATE": "Para tele/STATE",
|
||||
"For stat/RESULT": "Para stat/RESULT",
|
||||
"Client timeout (sec):": "Tempo limite do cliente (seg)",
|
||||
"Auto creation of states": "Criação automática de estados"
|
||||
}
|
||||
49
admin/i18n/ru/translations.json
Normal file
49
admin/i18n/ru/translations.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"Sonoff adapter settings": "Настройки драйвера Sonoff",
|
||||
"Type:": "Тип",
|
||||
"Port:": "Порт",
|
||||
"User:": "Имя пользователя",
|
||||
"Password:": "Пароль",
|
||||
"Password confirmation:": "Подтверждение пароля",
|
||||
"URL:": "URL",
|
||||
"Secure:": "SSL",
|
||||
"Public certificate:": "'Public' сертификат",
|
||||
"Private certificate:": "'Private' сертификат",
|
||||
"Chained certificate:": "'Chained' сертификат",
|
||||
"Patterns:": "Patterns",
|
||||
"Use WebSockets:": "Сервер WebSockets тоже",
|
||||
"Connection": "Соединение",
|
||||
"Sonoff Settings": "Настройки Sonoff",
|
||||
"Client ID:": "ID Клиента",
|
||||
"chars": "символов",
|
||||
"ms": "мс",
|
||||
"Interval before send topics by connection:": "Интервал перед отсылкой всей топиков после соединения",
|
||||
"Send interval:": "Интервал между пакетами",
|
||||
"Use chunk patch:": "Использовать заплатку для Chunking",
|
||||
"Divided by comma": "Использовать запятую, как разделитеть. Например 'mqtt/0/#,javascript/#'",
|
||||
"Mask to publish": "Использовать запятую, как разделитеть. Например 'mqtt.0.*,javascript.*'",
|
||||
"Store only on change:": "Отсылать только изменения",
|
||||
"Trace output for every message:": "Вывод лога для каждого изменения",
|
||||
"Test connection": "Проверить настройки",
|
||||
"Result: ": "Результат ",
|
||||
"connected": "успешно",
|
||||
"Main settings": "Основные настройки",
|
||||
"Connection settings": "Настройки соединения",
|
||||
"Authentication settings": "Настройки аутентификации",
|
||||
"Adapter settings": "Настройки Sonoff",
|
||||
"Mask to publish own states:": "Маска для собственных значений",
|
||||
"Send states (ack=true) too:": "Посылать не только команды, но и состояния (ack=true)",
|
||||
"Publish all states at start:": "Выдавать собственные значения при старте",
|
||||
"Use different topic names for set and get:": "Использовать разные имена для чтения и записи",
|
||||
"Publish states on subscribe:": "Публиковать состояния при подписке",
|
||||
"Set certificates or load it first in the system settings (right top).": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).",
|
||||
"Prefix for topics:": "Префикс для всех значений",
|
||||
"Max topic length:": "Максимальная длина имени топика",
|
||||
"Server": "Сервер/брокер",
|
||||
"Client": "Клиент/подписчик",
|
||||
"For tele/SENSOR": "Для tele/SENSOR",
|
||||
"For tele/STATE": "Для tele/STATE",
|
||||
"For stat/RESULT": "Для stat/RESULT",
|
||||
"Client timeout (sec):": "Client timeout (sec):",
|
||||
"Auto creation of states": "Auto creation of states"
|
||||
}
|
||||
177
admin/index.html
Normal file
177
admin/index.html
Normal file
@@ -0,0 +1,177 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- these 4 files always have to be included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
|
||||
|
||||
<!-- these three files have to be always included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||
<script type="text/javascript" src="words.js"></script>
|
||||
<!-- you have to define 2 functions in the global scope: -->
|
||||
<script type="text/javascript">
|
||||
var onChange;
|
||||
var secret;
|
||||
|
||||
function encrypt(key, value) {
|
||||
var result = '';
|
||||
for(var i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function decrypt(key, value) {
|
||||
var result = '';
|
||||
for(var i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setValue(id, value) {
|
||||
var $value = $('#' + id + '.value');
|
||||
if ($value.attr('type') === 'checkbox') {
|
||||
$value.prop('checked', value).change(function() {
|
||||
onChange();
|
||||
});
|
||||
} else {
|
||||
$value.val(value).change(function() {
|
||||
onChange();
|
||||
}).keyup(function() {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadHelper(settings, param, subparam) {
|
||||
if (!settings) return;
|
||||
|
||||
if (settings.useChunkPatch === undefined) settings.useChunkPatch = false;
|
||||
if (settings.timeout === undefined) settings.timeout = 300;
|
||||
|
||||
if (subparam && param) {
|
||||
for (var key in settings[param][subparam]) {
|
||||
if (typeof settings[param][subparam][key] !== 'object') {
|
||||
setValue(param + '_' + subparam + '_' + key, settings[param][subparam][key]);
|
||||
} else {
|
||||
alert('4th level is not supported');
|
||||
}
|
||||
}
|
||||
} else if (param) {
|
||||
for (var key in settings[param]) {
|
||||
if (typeof settings[param][key] !== 'object') {
|
||||
setValue(param + '_' + key, settings[param][key]);
|
||||
} else {
|
||||
loadHelper(settings, param, key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in settings) {
|
||||
if (typeof settings[key] !== 'object') {
|
||||
if (key === 'pass') {
|
||||
settings[key] = decrypt(secret, settings[key]);
|
||||
}
|
||||
setValue(key, settings[key]);
|
||||
} else {
|
||||
loadHelper(settings, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the function loadSettings has to exist ...
|
||||
function load(settings, _onChange) {
|
||||
$('#tabs').tabs();
|
||||
onChange = _onChange;
|
||||
socket.emit('getObject', 'system.config', function (err, obj) {
|
||||
secret = (obj.native ? obj.native.secret : '') || 'Zgfr56gFe87jJOM';
|
||||
loadHelper(settings);
|
||||
});
|
||||
|
||||
onChange(false);
|
||||
}
|
||||
|
||||
function saveHelper(obj, id, value) {
|
||||
var ids = id.split('_');
|
||||
|
||||
if (ids.length === 1) {
|
||||
if (ids[0] === 'pass') value = encrypt(secret, value);
|
||||
obj[id] = value;
|
||||
} else if (ids.length === 2) {
|
||||
if (!obj[ids[0]]) obj[ids[0]] = {};
|
||||
obj[ids[0]][ids[1]] = value;
|
||||
} else if (ids.length === 3) {
|
||||
if (!obj[ids[0]]) obj[ids[0]] = {};
|
||||
if (!obj[ids[0]][ids[1]]) obj[ids[0]][ids[1]] = {};
|
||||
obj[ids[0]][ids[1]][ids[2]] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function getSettings() {
|
||||
var obj = {};
|
||||
$('.value').each(function () {
|
||||
var $this = $(this);
|
||||
var id = $this.attr('id');
|
||||
|
||||
if ($this.attr('type') === 'checkbox') {
|
||||
obj = saveHelper(obj, id, $this.prop('checked'))
|
||||
} else {
|
||||
obj = saveHelper(obj, id, $this.val())
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
function save(callback) {
|
||||
if ($('#pass').val() !== $('#pass_confirm').val()) {
|
||||
showMessage(_('Password confirmation is not equal with password'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(getSettings());
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- you have to put your config page in a div with id adapter-container -->
|
||||
<div id="adapter-container">
|
||||
<table><tr>
|
||||
<td><img src="sonoff.png"></td>
|
||||
<td style="padding-top: 20px;padding-left: 10px"><h3 class="translate">Sonoff adapter settings</h3></td>
|
||||
</tr></table>
|
||||
<table>
|
||||
<tr>
|
||||
<td colspan='2'><h4 class="translate">Connection settings</h4></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="port">Port:</label></td>
|
||||
<td><input id="port" type="number" min="1" max="65565" class="value"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="timeout">Client timeout (sec):</label></td>
|
||||
<td><input id="timeout" type="number" min="1" max="65565" class="value"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan='2'><h4 class="translate">Authentication settings</h4></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="user">User:</label></td>
|
||||
<td><input id="user" size="17" class="value"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="pass">Password:</label></td>
|
||||
<td><input id="pass" type="password" size="17" class="value"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="pass_confirm">Password confirmation:</label></td>
|
||||
<td><input id="pass_confirm" type="password" size="17"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
183
admin/index_m.html
Normal file
183
admin/index_m.html
Normal file
@@ -0,0 +1,183 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Materialze style -->
|
||||
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
|
||||
|
||||
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
|
||||
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
|
||||
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||
<script type="text/javascript" src="words.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var onChange;
|
||||
var secret;
|
||||
|
||||
function encrypt(key, value) {
|
||||
var result = '';
|
||||
for(var i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function decrypt(key, value) {
|
||||
var result = '';
|
||||
for(var i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setValue(id, value) {
|
||||
var $value = $('#' + id + '.value');
|
||||
if ($value.attr('type') === 'checkbox') {
|
||||
$value.prop('checked', value).on('change', function () {
|
||||
onChange();
|
||||
});
|
||||
} else {
|
||||
$value.val(value).on('change', function () {
|
||||
onChange();
|
||||
}).keyup(function () {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadHelper(settings, param, subparam) {
|
||||
if (!settings) return;
|
||||
|
||||
if (settings.useChunkPatch === undefined) settings.useChunkPatch = false;
|
||||
if (settings.timeout === undefined) settings.timeout = 300;
|
||||
|
||||
for (var key in settings) {
|
||||
if (!settings.hasOwnProperty(key)) continue;
|
||||
if (key === 'pass') {
|
||||
settings[key] = decrypt(secret, settings[key]);
|
||||
}
|
||||
setValue(key, settings[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// the function loadSettings has to exist ...
|
||||
function load(settings, _onChange) {
|
||||
onChange = _onChange;
|
||||
socket.emit('getObject', 'system.config', function (err, obj) {
|
||||
secret = (obj.native ? obj.native.secret : '') || 'Zgfr56gFe87jJOM';
|
||||
loadHelper(settings);
|
||||
M.updateTextFields();
|
||||
});
|
||||
|
||||
onChange(false);
|
||||
}
|
||||
|
||||
function getSettings() {
|
||||
var obj = {};
|
||||
$('.value').each(function () {
|
||||
var $this = $(this);
|
||||
var id = $this.attr('id');
|
||||
|
||||
if ($this.attr('type') === 'checkbox') {
|
||||
obj[id] = $this.prop('checked');
|
||||
} else {
|
||||
var value = $this.val();
|
||||
if (id === 'pass') {
|
||||
value = encrypt(secret, value);
|
||||
}
|
||||
obj[id] = value;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
function save(callback) {
|
||||
if ($('#pass').val() !== $('#pass_confirm').val()) {
|
||||
showMessage(_('Password confirmation is not equal with password'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback(getSettings());
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.adapter-container>div {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.page {
|
||||
height: calc(100% - 50px) !important;
|
||||
}
|
||||
.marg{
|
||||
margin-top: 5em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="m adapter-container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="row">
|
||||
<div class="col s6">
|
||||
<img src="sonoff.png" class="logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h6 class="title translate">Connection settings</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 m4 l2">
|
||||
<input id="port" type="number" min="1" max="65565" class="value"/>
|
||||
<label class="translate" for="port">Port:</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m4 l2">
|
||||
<input id="timeout" type="number" min="1" max="99999" class="value"/>
|
||||
<label class="translate" for="timeout">Client timeout (sec):</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h6 class="title translate">Authentication settings</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="user" type="text" class="value"/>
|
||||
<label class="translate" for="user">User:</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="pass" type="password" class="value"/>
|
||||
<label class="translate" for="pass">Password:</label>
|
||||
</div>
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="pass_confirm" type="password" />
|
||||
<label class="translate" for="pass_confirm">Password confirmation:</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h6 class="title translate">Auto creation of states</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="TELE_SENSOR" type="checkbox" class="value"/>
|
||||
<span class="translate" for="user">For tele/SENSOR</span>
|
||||
</div>
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="TELE_STATE" type="checkbox" class="value"/>
|
||||
<span class="translate" for="TELE_STATE">For tele/STATE</span>
|
||||
</div>
|
||||
<div class="input-field col s6 m4">
|
||||
<input id="STAT_RESULT" type="checkbox" class="value"/>
|
||||
<span class="translate" for="STAT_RESULT">For stat/RESULT</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
admin/sonoff.png
Normal file
BIN
admin/sonoff.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
53
admin/words.js
Normal file
53
admin/words.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
|
||||
/*global systemDictionary:true */
|
||||
'use strict';
|
||||
|
||||
systemDictionary = {
|
||||
"Sonoff adapter settings": { "en": "Sonoff adapter settings", "de": "Sonoff Adapter-Einstellungen", "ru": "Настройки драйвера Sonoff", "pt": "Configurações do adaptador Sonoff", "nl": "Configurações do adaptador Sonoff", "fr": "Paramètres de l'adaptateur Sonoff", "it": "Impostazioni dell'adattatore Sonoff", "es": "Configuraciones del adaptador Sonoff", "pl": "Ustawienia adaptera Sonoff"},
|
||||
"Type:": { "en": "Type", "de": "Typ", "ru": "Тип", "pt": "Tipo", "nl": "Tipo", "fr": "Type", "it": "genere", "es": "Tipo", "pl": "Rodzaj"},
|
||||
"Port:": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Porta", "nl": "Porta", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port"},
|
||||
"User:": { "en": "User", "de": "Username", "ru": "Имя пользователя", "pt": "Do utilizador", "nl": "Do utilizador", "fr": "Utilisateur", "it": "Utente", "es": "Usuario", "pl": "Użytkownik"},
|
||||
"Password:": { "en": "Password", "de": "Kennwort", "ru": "Пароль", "pt": "Senha", "nl": "Senha", "fr": "Mot de passe", "it": "Parola d'ordine", "es": "Contraseña", "pl": "Hasło"},
|
||||
"Password confirmation:": { "en": "Password confirmation", "de": "Kennwort-Wiederholung", "ru": "Подтверждение пароля", "pt": "ConfirmaÇão Da Senha", "nl": "ConfirmaÇão Da Senha", "fr": "Confirmation mot de passe", "it": "conferma password", "es": "confirmación de contraseña", "pl": "Potwierdzenie hasła"},
|
||||
"URL:": { "en": "URL", "de": "URL", "ru": "URL", "pt": "URL", "nl": "URL", "fr": "URL", "it": "URL", "es": "URL", "pl": "URL"},
|
||||
"Secure:": { "en": "Secure", "de": "SSL", "ru": "SSL", "pt": "Seguro", "nl": "Seguro", "fr": "Garantir", "it": "Sicuro", "es": "Seguro", "pl": "Bezpieczne"},
|
||||
"Public certificate:": { "en": "Public certificate", "de": "Publikzertifikat", "ru": "'Public' сертификат", "pt": "Certificado público", "nl": "Certificado público", "fr": "Certificat public", "it": "Certificato pubblico", "es": "Certificado público", "pl": "Certyfikat publiczny"},
|
||||
"Private certificate:": { "en": "Private certificate", "de": "Privatzertifikat", "ru": "'Private' сертификат", "pt": "Certificado privado", "nl": "Certificado privado", "fr": "Certificat privé", "it": "Certificato privato", "es": "Certificado privado", "pl": "Prywatny certyfikat"},
|
||||
"Chained certificate:": { "en": "Chained certificate", "de": "Kettenzertifikat", "ru": "'Chained' сертификат", "pt": "Certificado acorrentado", "nl": "Certificado acorrentado", "fr": "Certificat chaîné", "it": "Certificato incatenato", "es": "Certificado encadenado", "pl": "Przykuty certyfikat"},
|
||||
"Patterns:": { "en": "Subscribe patterns", "de": "Subscribe patterns", "ru": "Patterns", "pt": "Assinar padrões", "nl": "Assinar padrões", "fr": "Modèles de souscription", "it": "Iscriviti modelli", "es": "Suscribir patrones", "pl": "Wzorce subskrypcji"},
|
||||
"Use WebSockets:": { "en": "Use WebSockets too", "de": "Benutze auch WebSockets", "ru": "Сервер WebSockets тоже", "pt": "Use também WebSockets", "nl": "Use também WebSockets", "fr": "Utilisez aussi WebSockets", "it": "Utilizza anche WebSockets", "es": "Use WebSockets también", "pl": "Użyj także WebSockets"},
|
||||
"Connection": { "en": "Connection", "de": "Verbindung", "ru": "Соединение", "pt": "Conexão", "nl": "Conexão", "fr": "Connexion", "it": "Connessione", "es": "Conexión", "pl": "Połączenie"},
|
||||
"Sonoff Settings": { "en": "Sonoff Settings", "de": "Sonoff Einstellungen", "ru": "Настройки Sonoff", "pt": "Configurações de Sonoff", "nl": "Configurações de Sonoff", "fr": "Paramètres Sonoff", "it": "Impostazioni Sonoff", "es": "Configuraciones Sonoff", "pl": "Ustawienia Sonoff"},
|
||||
"Client ID:": { "en": "Client ID", "de": "Client ID", "ru": "ID Клиента", "pt": "ID do Cliente", "nl": "ID do Cliente", "fr": "identité du client", "it": "Identificativo cliente", "es": "Identificación del cliente", "pl": "Identyfikator klienta"},
|
||||
"chars": { "en": "chars", "de": "Symbolen", "ru": "символов", "pt": "caracteres", "nl": "caracteres", "fr": "chars", "it": "caratteri", "es": "caracteres", "pl": "znaki"},
|
||||
"ms": { "en": "ms", "de": "ms", "ru": "мс", "pt": "Senhora", "nl": "Senhora", "fr": "Mme", "it": "Signorina", "es": "Sra", "pl": "ms"},
|
||||
"Interval before send topics by connection:": { "en": "Interval before send topics by connection", "de": "Interval vom Senden von allen Topics bei der verbindung", "ru": "Интервал перед отсылкой всей топиков после соединения", "pt": "Intervalo antes de enviar tópicos por conexão", "nl": "Intervalo antes de enviar tópicos por conexão", "fr": "Intervalle avant d'envoyer des sujets par connexion", "it": "Intervallo prima di inviare argomenti per connessione", "es": "Intervalo antes de enviar temas por conexión", "pl": "Interwał przed wysłaniem tematów według połączenia"},
|
||||
"Send interval:": { "en": "Send interval", "de": "Sendeintervall", "ru": "Интервал между пакетами", "pt": "Intervalo de envio", "nl": "Intervalo de envio", "fr": "Envoyer un intervalle", "it": "Invia intervallo", "es": "Intervalo de envío", "pl": "Interwał wysyłania"},
|
||||
"Use chunk patch:": { "en": "Use chunk patch", "de": "Benutze Patch für Chunking", "ru": "Использовать заплатку для Chunking", "pt": "Use patch de pedaços", "nl": "Use patch de pedaços", "fr": "Utiliser un patch de morceau", "it": "Usa patch pezzo", "es": "Use el fragmento de fragmento", "pl": "Użyj poprawki kawałka"},
|
||||
"Divided by comma": { "en": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "de": "Mit Komma getrennt, z.B 'mqtt/0/#,javascript/#'", "ru": "Использовать запятую, как разделитеть. Например 'mqtt/0/#,javascript/#'", "pt": "Dividido por vírgula, e. 'mqtt / 0 / #, javascript / #'", "nl": "Dividido por vírgula, e. 'mqtt/0/#, javascript/#'", "fr": "Divisé par une virgule, par ex. 'mqtt/0/#, javascript/#'", "it": "Diviso in virgola, ad es. 'MQTT/0/#, JavaScript/#'", "es": "Dividido por coma, p. 'mqtt/0/#, javascript/#'", "pl": "Podzielone przecinkiem, np. \"mqtt / 0 / #, javascript / # '"},
|
||||
"Mask to publish": { "en": "e.g. 'mqtt.0.*,javascript.*'", "de": "z.B 'mqtt.0.*,javascript.*'", "ru": "Использовать запятую, как разделитеть. Например 'mqtt.0.*,javascript.*'", "pt": "por exemplo. 'mqtt.0. *, javascript. *'", "nl": "por exemplo. 'mqtt.0.*, javascript.*'", "fr": "par exemple. 'mqtt.0.*, javascript.*'", "it": "per esempio. 'Mqtt.0. *, Javascript. *'", "es": "p.ej. 'mqtt.0.*, javascript.*'", "pl": "na przykład \"mqtt.0. *, javascript. *\""},
|
||||
"Store only on change:": { "en": "Publish only on change", "de": "Publish nur bei Änderung", "ru": "Отсылать только изменения", "pt": "Publique apenas na mudança", "nl": "Publique apenas na mudança", "fr": "Publier uniquement en cas de modification", "it": "Pubblica solo sul cambiamento", "es": "Publicar solo en el cambio", "pl": "Opublikuj tylko w przypadku zmiany"},
|
||||
"Trace output for every message:": { "en": "Trace output for every message", "de": "Trace Ausgabe für jede Meldung", "ru": "Вывод лога для каждого изменения", "pt": "Saída de rastreamento para cada mensagem", "nl": "Saída de rastreamento para cada mensagem", "fr": "Trace de sortie pour chaque message", "it": "Traccia l'output per ogni messaggio", "es": "Salida de seguimiento para cada mensaje", "pl": "Dane wyjściowe śledzenia dla każdej wiadomości"},
|
||||
"Test connection": { "en": "Test connection with Server", "de": "Teste Verbindung zum Server", "ru": "Проверить настройки", "pt": "Teste a conexão com o Servidor", "nl": "Teste a conexão com o Servidor", "fr": "Tester la connexion avec le serveur", "it": "Verifica la connessione con il server", "es": "Prueba de conexión con el servidor", "pl": "Sprawdź połączenie z serwerem"},
|
||||
"Result: ": { "en": "Result ", "de": "Ergebnis ", "ru": "Результат ", "pt": "Resultado", "nl": "Resultado", "fr": "Résultat", "it": "Risultato", "es": "Resultado", "pl": "Wynik"},
|
||||
"connected": { "en": "connected", "de": "verbunden", "ru": "успешно", "pt": "conectado", "nl": "conectado", "fr": "connecté", "it": "collegato", "es": "conectado", "pl": "połączony"},
|
||||
"Main settings": { "en": "Main settings", "de": "Allgemeine Einstellungen", "ru": "Основные настройки", "pt": "Configurações principais", "nl": "Configurações principais", "fr": "Réglages principaux", "it": "Impostazioni principali", "es": "Ajustes principales", "pl": "Ustawienia główne"},
|
||||
"Connection settings": { "en": "Connection settings", "de": "Verbindungseinstellungen", "ru": "Настройки соединения", "pt": "Configurações de conexão", "nl": "Configurações de conexão", "fr": "Paramètres de connexion", "it": "Impostazioni di connessione", "es": "Configuración de conexión", "pl": "Ustawienia połączenia"},
|
||||
"Authentication settings": { "en": "Authentication settings", "de": "Authentication Einstellungen", "ru": "Настройки аутентификации", "pt": "Configurações de autenticação", "nl": "Configurações de autenticação", "fr": "Paramètres d'authentification", "it": "Impostazioni di autenticazione", "es": "Configuraciones de autenticación", "pl": "Ustawienia uwierzytelniania"},
|
||||
"Adapter settings": { "en": "Sonoff settings", "de": "Sonoff Einstellungen", "ru": "Настройки Sonoff", "pt": "Configurações de Sonoff", "nl": "Configurações de Sonoff", "fr": "Paramètres Sonoff", "it": "Impostazioni Sonoff", "es": "Configuraciones Sonoff", "pl": "Ustawienia Sonoff"},
|
||||
"Mask to publish own states:": { "en": "Mask to publish own states", "de": "Maske für Bekanntgeben von eigenen States", "ru": "Маска для собственных значений", "pt": "Máscara para publicar estados próprios", "nl": "Máscara para publicar estados próprios", "fr": "Masque pour publier ses propres états", "it": "Maschera per pubblicare i propri stati", "es": "Máscara para publicar estados propios", "pl": "Maskuj, aby publikować własne stany"},
|
||||
"Send states (ack=true) too:": { "en": "Send states (ack=true) too", "de": "Sende auch Zustände (ack=true)", "ru": "Посылать не только команды, но и состояния (ack=true)", "pt": "Enviar estados (ack = true) também", "nl": "Enviar estados (ack = true) também", "fr": "Envoyer des états (ack = true) aussi", "it": "Invia stati (ack = true) anche", "es": "Enviar estados (ack = true) también", "pl": "Wyślij także stany (ack = true)"},
|
||||
"Publish all states at start:": { "en": "Publish own states on connect", "de": "Bekanntgeben eigene States beim Verbinden", "ru": "Выдавать собственные значения при старте", "pt": "Publique seus próprios estados na conexão", "nl": "Publique seus próprios estados na conexão", "fr": "Publier ses propres états sur se connecter", "it": "Pubblica i tuoi stati sulla connessione", "es": "Publicar estados propios al conectar", "pl": "Opublikuj własne stany przy połączeniu"},
|
||||
"Use different topic names for set and get:": { "en": "Use different topic names for set and get", "de": "Unterschiedliche Namen für setzten und lesen", "ru": "Использовать разные имена для чтения и записи", "pt": "Use nomes de tópicos diferentes para definir e obter", "nl": "Use nomes de tópicos diferentes para definir e obter", "fr": "Utilisez différents noms de sujet pour définir et obtenir", "it": "Usa diversi nomi di argomenti per impostare e ottenere", "es": "Use diferentes nombres de tema para establecer y obtener", "pl": "Używaj różnych nazw tematów do ustawiania i pobierania"},
|
||||
"Publish states on subscribe:": { "en": "Publish states on subscribe", "de": "Bekanntgeben von States bei Subscribe", "ru": "Публиковать состояния при подписке", "pt": "Publique estados na assinatura", "nl": "Publique estados na assinatura", "fr": "Publier les états sur l'abonnement", "it": "Pubblica stati su iscriviti", "es": "Publicar estados al suscribirse", "pl": "Opublikuj stany w subskrypcji"},
|
||||
"Set certificates or load it first in the system settings (right top).": {"en": "Set certificates or load it first in the system settings (right top).", "de": "Setze Zertificate oder lade die erst unter System/Einstellungen (oben rechts).", "ru": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).", "pt": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).", "nl": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).", "fr": "Définissez des certificats ou chargez-les d'abord dans les paramètres du système (en haut à droite).", "it": "Imposta i certificati o caricali prima nelle impostazioni di sistema (in alto a destra).", "es": "Establezca certificados o cárguelos primero en la configuración del sistema (arriba a la derecha).", "pl": "Ustaw certyfikaty lub załaduj najpierw w ustawieniach systemu (prawy górny)."},
|
||||
"Prefix for topics:": { "en": "Prefix for all topics", "de": "Prefix für alle Topics", "ru": "Префикс для всех значений", "pt": "Prefix para todos os tópicos", "nl": "Prefix para todos os tópicos", "fr": "Préfixe pour tous les sujets", "it": "Prefisso per tutti gli argomenti", "es": "Prefijo para todos los temas", "pl": "Prefiks dla wszystkich tematów"},
|
||||
"Max topic length:": { "en": "Max topic name length", "de": "Maximale Topicnamelänge", "ru": "Максимальная длина имени топика", "pt": "Nome máximo do nome do tópico", "nl": "Nome máximo do nome do tópico", "fr": "Longueur maximale du nom de la rubrique", "it": "Lunghezza massima del nome dell'argomento", "es": "Longitud máxima del nombre del tema", "pl": "Maksymalna długość nazwy tematu"},
|
||||
"Server": { "en": "Server/broker", "de": "Server/broker", "ru": "Сервер/брокер", "pt": "Servidor / corretor", "nl": "Servidor/corretor", "fr": "Serveur/courtier", "it": "Server/mediatore", "es": "Servidor/intermediario", "pl": "Serwer / broker"},
|
||||
"Client": { "en": "Client/subscriber", "de": "Client/subscriber", "ru": "Клиент/подписчик", "pt": "Cliente / assinante", "nl": "Cliente/assinante", "fr": "Client/abonné", "it": "Client/abbonato", "es": "Cliente/suscriptor", "pl": "Klient / subskrybent"},
|
||||
"For tele/SENSOR": { "en": "For tele/SENSOR", "de": "Für tele/SENSOR", "ru": "Для tele/SENSOR", "pt": "Para tele/SENSOR", "nl": "Voor tele/SENSOR", "fr": "Pour tele/SENSOR", "it": "Per tele/SENSOR", "es": "Por tele/SENSOR", "pl": "Dla tele/SENSOR"},
|
||||
"For tele/STATE": { "en": "For tele/STATE", "de": "Für tele/STATE", "ru": "Для tele/STATE", "pt": "Para tele/STATE", "nl": "Voor tele/STATE", "fr": "Pour tele/STATE", "it": "Per tele/STATE", "es": "Por tele/STATE", "pl": "Dla tele/STATE"},
|
||||
"For stat/RESULT": { "en": "For stat/RESULT", "de": "Für stat/RESULT", "ru": "Для stat/RESULT", "pt": "Para stat/RESULT", "nl": "Voor stat/RESULT", "fr": "Pour stat/RESULT", "it": "Per stat/RESULT", "es": "Por stat/RESULT", "pl": "Dla stat/RESULT"},
|
||||
"Client timeout (sec):": { "en": "Client timeout (sec)", "de": "Client-Zeitüberschreitung (Sek.)", "ru": "Client timeout (sec):", "pt": "Tempo limite do cliente (seg)", "nl": "Clienttimeout (sec)", "fr": "Délai d'expiration du client (sec)", "it": "Timeout del client (sec)", "es": "Tiempo de espera del cliente (seg)", "pl": "Limit czasu klienta (s)"},
|
||||
"Auto creation of states": { "en": "Auto creation of states", "de": "Automatische Erstellung von Zuständen", "ru": "Auto creation of states", "pt": "Criação automática de estados", "nl": "Automatisch creëren van staten", "fr": "Création automatique d'états", "it": "Creazione automatica di stati", "es": "Creación automática de estados", "pl": "Automatyczne tworzenie stanów"}
|
||||
};
|
||||
26
appveyor.yml
Normal file
26
appveyor.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: 'test-{build}'
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: '4'
|
||||
- nodejs_version: '6'
|
||||
- nodejs_version: '8'
|
||||
- nodejs_version: '10'
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%'
|
||||
install:
|
||||
- ps: 'Install-Product node $env:nodejs_version $env:platform'
|
||||
- ps: '$NpmVersion = (npm -v).Substring(0,1)'
|
||||
- ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }'
|
||||
- ps: npm --version
|
||||
- npm install
|
||||
- npm install winston@2.3.1
|
||||
- npm install mqtt@2.13.0
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
test_script:
|
||||
- echo %cd%
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm test
|
||||
build: 'off'
|
||||
401
gulpfile.js
Normal file
401
gulpfile.js
Normal file
@@ -0,0 +1,401 @@
|
||||
'use strict';
|
||||
|
||||
var gulp = require('gulp');
|
||||
var fs = require('fs');
|
||||
var pkg = require('./package.json');
|
||||
var iopackage = require('./io-package.json');
|
||||
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
|
||||
/*var appName = getAppName();
|
||||
|
||||
function getAppName() {
|
||||
var parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 1].split('.')[0].toLowerCase();
|
||||
}
|
||||
*/
|
||||
const fileName = 'words.js';
|
||||
var languages = {
|
||||
en: {},
|
||||
de: {},
|
||||
ru: {},
|
||||
pt: {},
|
||||
nl: {},
|
||||
fr: {},
|
||||
it: {},
|
||||
es: {},
|
||||
pl: {}
|
||||
};
|
||||
|
||||
function lang2data(lang, isFlat) {
|
||||
var str = isFlat ? '' : '{\n';
|
||||
var count = 0;
|
||||
for (var w in lang) {
|
||||
if (lang.hasOwnProperty(w)) {
|
||||
count++;
|
||||
if (isFlat) {
|
||||
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
|
||||
} else {
|
||||
var 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 {
|
||||
var words;
|
||||
if (fs.existsSync(src + 'js/' + fileName)) {
|
||||
words = fs.readFileSync(src + 'js/' + fileName).toString();
|
||||
} else {
|
||||
words = fs.readFileSync(src + fileName).toString();
|
||||
}
|
||||
|
||||
var lines = words.split(/\r\n|\r|\n/g);
|
||||
var i = 0;
|
||||
while (!lines[i].match(/^systemDictionary = {/)) {
|
||||
i++;
|
||||
}
|
||||
lines.splice(0, i);
|
||||
|
||||
// remove last empty lines
|
||||
i = lines.length - 1;
|
||||
while (!lines[i]) {
|
||||
i--;
|
||||
}
|
||||
if (i < lines.length - 1) {
|
||||
lines.splice(i + 1);
|
||||
}
|
||||
|
||||
lines[0] = lines[0].replace('systemDictionary = ', '');
|
||||
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
|
||||
words = lines.join('\n');
|
||||
var 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) {
|
||||
var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
|
||||
text += '/*global systemDictionary:true */\n';
|
||||
text += '\'use strict\';\n\n';
|
||||
|
||||
text += 'systemDictionary = {\n';
|
||||
for (var word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
|
||||
var line = '';
|
||||
for (var 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 = text.replace(/},\n$/, '}\n');
|
||||
text += '};';
|
||||
|
||||
if (fs.existsSync(src + 'js/' + fileName)) {
|
||||
fs.writeFileSync(src + 'js/' + fileName, text);
|
||||
} else {
|
||||
fs.writeFileSync(src + '' + fileName, text);
|
||||
}
|
||||
}
|
||||
|
||||
const EMPTY = '';
|
||||
|
||||
function words2languages(src) {
|
||||
var langs = Object.assign({}, languages);
|
||||
var data = readWordJs(src);
|
||||
if (data) {
|
||||
for (var word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
for (var lang in data[word]) {
|
||||
if (data[word].hasOwnProperty(lang)) {
|
||||
langs[lang][word] = data[word][lang];
|
||||
// pre-fill all other languages
|
||||
for (var j in langs) {
|
||||
if (langs.hasOwnProperty(j)) {
|
||||
langs[j][word] = langs[j][word] || EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(src + 'i18n/')) {
|
||||
fs.mkdirSync(src + 'i18n/');
|
||||
}
|
||||
for (var l in langs) {
|
||||
if (!langs.hasOwnProperty(l)) continue;
|
||||
var keys = Object.keys(langs[l]);
|
||||
//keys.sort();
|
||||
var obj = {};
|
||||
for (var 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) {
|
||||
var langs = Object.assign({}, languages);
|
||||
var data = readWordJs(src);
|
||||
if (data) {
|
||||
for (var word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
for (var lang in data[word]) {
|
||||
if (data[word].hasOwnProperty(lang)) {
|
||||
langs[lang][word] = data[word][lang];
|
||||
// pre-fill all other languages
|
||||
for (var j in langs) {
|
||||
if (langs.hasOwnProperty(j)) {
|
||||
langs[j][word] = langs[j][word] || EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var keys = Object.keys(langs.en);
|
||||
//keys.sort();
|
||||
for (var l in langs) {
|
||||
if (!langs.hasOwnProperty(l)) continue;
|
||||
var obj = {};
|
||||
for (var 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 (var 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) {
|
||||
var dirs = fs.readdirSync(src + 'i18n/');
|
||||
var langs = {};
|
||||
var bigOne = {};
|
||||
var order = Object.keys(languages);
|
||||
dirs.sort(function (a, b) {
|
||||
var posA = order.indexOf(a);
|
||||
var 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;
|
||||
}
|
||||
});
|
||||
var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
|
||||
|
||||
for (var l = 0; l < dirs.length; l++) {
|
||||
if (dirs[l] === 'flat.txt') continue;
|
||||
var lang = dirs[l];
|
||||
var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
|
||||
langs[lang] = {};
|
||||
keys.forEach(function (word, i) {
|
||||
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
|
||||
});
|
||||
|
||||
var words = langs[lang];
|
||||
for (var word in words) {
|
||||
if (words.hasOwnProperty(word)) {
|
||||
bigOne[word] = bigOne[word] || {};
|
||||
if (words[word] !== EMPTY) {
|
||||
bigOne[word][lang] = words[word];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// read actual words.js
|
||||
var aWords = readWordJs();
|
||||
|
||||
var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
|
||||
if (aWords) {
|
||||
// Merge words together
|
||||
for (var 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) {
|
||||
var dirs = fs.readdirSync(src + 'i18n/');
|
||||
var langs = {};
|
||||
var bigOne = {};
|
||||
var order = Object.keys(languages);
|
||||
dirs.sort(function (a, b) {
|
||||
var posA = order.indexOf(a);
|
||||
var 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 (var l = 0; l < dirs.length; l++) {
|
||||
if (dirs[l] === 'flat.txt') continue;
|
||||
var lang = dirs[l];
|
||||
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
|
||||
langs[lang] = JSON.parse(langs[lang]);
|
||||
var words = langs[lang];
|
||||
for (var word in words) {
|
||||
if (words.hasOwnProperty(word)) {
|
||||
bigOne[word] = bigOne[word] || {};
|
||||
if (words[word] !== EMPTY) {
|
||||
bigOne[word][lang] = words[word];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// read actual words.js
|
||||
var aWords = readWordJs();
|
||||
|
||||
var temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
|
||||
if (aWords) {
|
||||
// Merge words together
|
||||
for (var 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);
|
||||
}
|
||||
|
||||
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]) {
|
||||
var news = iopackage.common.news;
|
||||
var newNews = {};
|
||||
|
||||
newNews[pkg.version] = {
|
||||
en: 'news',
|
||||
de: 'neues',
|
||||
ru: 'новое'
|
||||
};
|
||||
iopackage.common.news = Object.assign(newNews, news);
|
||||
}
|
||||
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('updateReadme', function (done) {
|
||||
var readme = fs.readFileSync('README.md').toString();
|
||||
var pos = readme.indexOf('## Changelog\n');
|
||||
if (pos !== -1) {
|
||||
var readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
|
||||
var readmeEnd = readme.substring(pos + '## Changelog\n'.length);
|
||||
|
||||
if (readme.indexOf(version) === -1) {
|
||||
var timestamp = new Date();
|
||||
var date = timestamp.getFullYear() + '-' +
|
||||
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
|
||||
('0' + (timestamp.getDate()).toString(10)).slice(-2);
|
||||
|
||||
var 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('default', ['updatePackages', 'updateReadme']);
|
||||
184
io-package.json
Executable file
184
io-package.json
Executable file
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"common": {
|
||||
"name": "sonoff",
|
||||
"version": "2.2.3",
|
||||
"news": {
|
||||
"2.2.3": {
|
||||
"en": "Support for compact mode",
|
||||
"de": "Unterstützung für den Kompaktmodus",
|
||||
"ru": "Поддержка компактного режима",
|
||||
"pt": "Suporte para o modo compacto",
|
||||
"nl": "Ondersteuning voor de compacte modus",
|
||||
"fr": "Prise en charge du mode compact",
|
||||
"it": "Supporto per la modalità compatta",
|
||||
"es": "Soporte para modo compacto",
|
||||
"pl": "Obsługa trybu kompaktowego"
|
||||
},
|
||||
"2.2.2": {
|
||||
"en": "Configuration was fixed",
|
||||
"de": "Die Konfiguration wurde repariert",
|
||||
"ru": "Конфигурация была исправлена",
|
||||
"pt": "Configuração foi corrigida",
|
||||
"nl": "Configuratie was opgelost",
|
||||
"fr": "La configuration a été corrigée",
|
||||
"it": "La configurazione è stata riparata",
|
||||
"es": "La configuración fue arreglada",
|
||||
"pl": "Konfiguracja została naprawiona"
|
||||
},
|
||||
"2.2.1": {
|
||||
"en": "- in names was allowed again",
|
||||
"de": "- in Namen wurde wieder erlaubt",
|
||||
"ru": "- в именах снова было разрешено",
|
||||
"pt": "- nos nomes foi permitido novamente",
|
||||
"nl": "- in namen was weer toegestaan",
|
||||
"fr": "- dans les noms a été autorisé à nouveau",
|
||||
"it": "- nei nomi è stato permesso di nuovo",
|
||||
"es": "- en los nombres se permitió de nuevo",
|
||||
"pl": "- w nazwach było ponownie dozwolone"
|
||||
},
|
||||
"2.2.0": {
|
||||
"en": "Auto generate of objects was implemented",
|
||||
"de": "Auto-Generierung von Objekten wurde implementiert",
|
||||
"ru": "Была реализована автоматическая генерация объектов",
|
||||
"pt": "Geração automática de objetos foi implementada",
|
||||
"nl": "Automatisch genereren van objecten is geïmplementeerd",
|
||||
"fr": "La génération automatique d'objets a été implémentée",
|
||||
"it": "È stata implementata la generazione automatica di oggetti",
|
||||
"es": "Generación automática de objetos fue implementado",
|
||||
"pl": "Automatycznie generowano obiekty"
|
||||
},
|
||||
"2.1.3": {
|
||||
"en": "Added HC-SR04 Ultrasonic Sensor",
|
||||
"de": "HC-SR04 Ultraschallsensor hinzugefügt",
|
||||
"ru": "Добавлен ультразвуковой датчик HC-SR04",
|
||||
"pt": "Adicionado Sensor Ultrassônico HC-SR04",
|
||||
"nl": "HC-SR04 ultrasone sensor toegevoegd",
|
||||
"fr": "Capteur à ultrasons HC-SR04 ajouté",
|
||||
"it": "Aggiunto il sensore a ultrasuoni HC-SR04",
|
||||
"es": "Sensor ultrasónico HC-SR04 agregado",
|
||||
"pl": "Dodano czujnik ultradźwiękowy HC-SR04"
|
||||
},
|
||||
"2.1.2": {
|
||||
"en": "Added support of UvLight, Longitude and Latitude",
|
||||
"de": "Zusätzliche Unterstützung von UvLight, Longitude und Latitude",
|
||||
"ru": "Добавлена поддержка UvLight, долготы и широты",
|
||||
"pt": "Adicionado suporte de UvLight, Longitude e Latitude",
|
||||
"nl": "Ondersteuning toegevoegd van UvLight, Longitude en Latitude",
|
||||
"fr": "Ajout du support de UvLight, Longitude et Latitude",
|
||||
"it": "Aggiunto il supporto di UvLight, Longitude e Latitude",
|
||||
"es": "Se agregó soporte de UvLight, Longitude y Latitude",
|
||||
"pl": "Dodano obsługę UvLight, długości i szerokości geograficznej"
|
||||
},
|
||||
"2.1.1": {
|
||||
"en": "Support of the particle concentration sensor",
|
||||
"de": "Unterstützung des Partikelkonzentrationssensors",
|
||||
"ru": "Поддержка датчика концентрации частиц",
|
||||
"pt": "Suporte do sensor de concentração de partículas",
|
||||
"nl": "Ondersteuning van de deeltjesconcentratiesensor",
|
||||
"fr": "Support du capteur de concentration de particules",
|
||||
"it": "Supporto del sensore di concentrazione di particelle",
|
||||
"es": "Soporte del sensor de concentración de partículas",
|
||||
"pl": "Wsparcie czujnika stężenia cząstek"
|
||||
},
|
||||
"2.1.0": {
|
||||
"en": "Support of the devices control",
|
||||
"de": "Unterstützung der Gerätekontrolle",
|
||||
"ru": "Поддержка управления устройствами",
|
||||
"pt": "Suporte do controle de dispositivos",
|
||||
"nl": "Ondersteuning van de apparatencontrole",
|
||||
"fr": "Prise en charge du contrôle des appareils",
|
||||
"it": "Supporto del controllo dei dispositivi",
|
||||
"es": "Soporte del control de dispositivos",
|
||||
"pl": "Obsługa sterowania urządzeniami"
|
||||
},
|
||||
"2.0.2": {
|
||||
"en": "Fixing re-connection of clients\nSeaPressure was added",
|
||||
"de": "Reparieren der Neuverbindung von Clients\nSeaPressure wurde hinzugefügt",
|
||||
"ru": "Фиксация повторного подключения клиентов\nДобавлено SeaPressure",
|
||||
"pt": "Reparando conexão de clientes\nA SeaPressure foi adicionada",
|
||||
"nl": "Bevestiging van opnieuw verbinden van clients\nSeaPressure is toegevoegd",
|
||||
"fr": "Correction de la reconnexion des clients\nSeaPressure a été ajouté",
|
||||
"it": "Risolvere la riconnessione dei client\nSeaPressure è stato aggiunto",
|
||||
"es": "Reparar la reconexión de los clientes\nSeaPressure fue agregado",
|
||||
"pl": "Naprawianie ponownego połączenia klientów\nSeaPressure zostało dodane"
|
||||
},
|
||||
"2.0.1": {
|
||||
"en": "Add Light/Noise/AirQuality\nDo not send pingresp for invalid clients",
|
||||
"de": "Fügen Sie den Light/Noise/AirQuality hinzu\nSendet keine Pingresp für ungültige Clients",
|
||||
"ru": "Добавлены Light/Noise/AirQuality\nНе отправляen pingresp для недействительных клиентов",
|
||||
"pt": "Adicionar Light/Noise/AirQuality",
|
||||
"nl": "Light/Noise/AirQuality toevoegen",
|
||||
"fr": "Ajouter un capteur Light/Noise/AirQuality",
|
||||
"it": "Aggiungi il Light/Noise/AirQuality",
|
||||
"es": "Agregar Light/Noise/AirQuality",
|
||||
"pl": "Dodaj Light/Noise/AirQuality"
|
||||
}
|
||||
},
|
||||
"title": "Sonoff",
|
||||
"desc": {
|
||||
"en": "This adapter allows to connect sonoff components via MQTT",
|
||||
"de": "Dieser Adapter ermöglicht den Anschluss von Sonoff-Komponenten über MQTT",
|
||||
"ru": "Этот адаптер позволяет подключать компоненты Sonoff через MQTT",
|
||||
"pt": "Este adaptador permite conectar os componentes do sonoff via MQTT",
|
||||
"nl": "Met deze adapter kunnen sonoff-componenten via MQTT worden aangesloten",
|
||||
"fr": "Cet adaptateur permet de connecter des composants sonoff via MQTT",
|
||||
"it": "Questo adattatore consente di collegare i componenti Sonoff via MQTT",
|
||||
"es": "Este adaptador permite conectar componentes de sonof a través de MQTT",
|
||||
"pl": "Ten adapter umożliwia łączenie komponentów sonoff za pośrednictwem MQTT"
|
||||
},
|
||||
"authors": [
|
||||
"bluefox <dogafox@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"platform": "Javascript/Node.js",
|
||||
"mode": "daemon",
|
||||
"compact": true,
|
||||
"enabled": true,
|
||||
"readme": "https://github.com/ioBroker/ioBroker.sonoff/blob/master/README.md",
|
||||
"loglevel": "info",
|
||||
"icon": "sonoff.png",
|
||||
"materialize": true,
|
||||
"keywords": ["ITEAD", "MQTT", "Sonoff", "Tasmota"],
|
||||
"extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.sonoff/master/admin/sonoff.png",
|
||||
"type": "lighting"
|
||||
},
|
||||
"native": {
|
||||
"port": 1883,
|
||||
"bind": "0.0.0.0",
|
||||
"user": "",
|
||||
"pass": "",
|
||||
"timeout": 300,
|
||||
"TELE_SENSOR": false,
|
||||
"TELE_STATE": false,
|
||||
"STAT_RESULT": false,
|
||||
"storeClientsTime": 1440,
|
||||
"defaultQoS": 0,
|
||||
"retransmitInterval": 2000,
|
||||
"retransmitCount": 10
|
||||
},
|
||||
"objects": [
|
||||
|
||||
],
|
||||
"instanceObjects": [
|
||||
{
|
||||
"_id": "info",
|
||||
"type": "channel",
|
||||
"common": {
|
||||
"name": "Information"
|
||||
},
|
||||
"native": {}
|
||||
},
|
||||
{
|
||||
"_id": "info.connection",
|
||||
"type": "state",
|
||||
"common": {
|
||||
"role": "info.clients",
|
||||
"name": "List of connected clients",
|
||||
"type": "string",
|
||||
"read": true,
|
||||
"write": false
|
||||
},
|
||||
"native": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
139
lib/datapoints.js
Normal file
139
lib/datapoints.js
Normal file
@@ -0,0 +1,139 @@
|
||||
module.exports = {
|
||||
Action: {type: 'number', role: 'value', read: true, write: true},
|
||||
AirQuality: {type: 'number', role: 'value', read: true, write: false, unit: '%'},
|
||||
A0: {type: 'number', role: 'value', read: true, write: false},
|
||||
A1: {type: 'number', role: 'value', read: true, write: false},
|
||||
A2: {type: 'number', role: 'value', read: true, write: false},
|
||||
A3: {type: 'number', role: 'value', read: true, write: false},
|
||||
ANALOG: {type: 'object', role: 'state', read: true, write: false},
|
||||
Analog0: {type: 'number', role: 'value', read: true, write: false},
|
||||
Analog1: {type: 'number', role: 'value', read: true, write: false},
|
||||
Analog2: {type: 'number', role: 'value', read: true, write: false},
|
||||
Analog3: {type: 'number', role: 'value', read: true, write: false},
|
||||
Arm: {type: 'number', role: 'value', read: true, write: true},
|
||||
'Approx. Altitude': {type: 'number', role: 'value.altitude', read: true, write: false, unit: 'm'},
|
||||
Bits: {type: 'number', role: 'value', read: true, write: false},
|
||||
Blue: {type: 'number', role: 'level.color.blue', read: true, write: true, min: 0, max: 100},
|
||||
Boiler: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Burner: {type: 'number', role: 'value', read: true, write: false},
|
||||
CT: {type: 'number', role: 'level.color.temp', read: true, write: true},
|
||||
CW: {type: 'number', role: 'level.color.coldwhite', read: true, write: true},
|
||||
CW_POWER: {type: 'boolean', role: 'switch.light', read: true, write: true, storeMap: true},
|
||||
CarbonDioxide: {type: 'number', role: 'value.CO2', read: true, write: false, unit: 'ppm'},
|
||||
Channel: {type: 'array', role: 'level', read: true, write: true},
|
||||
Collector: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Color: {type: 'string', role: 'level.color.rgb', read: true, write: true},
|
||||
Counter1: {type: 'number', role: 'value', read: true, write: false},
|
||||
Counter2: {type: 'number', role: 'value', read: true, write: false},
|
||||
Counter3: {type: 'number', role: 'value', read: true, write: false},
|
||||
Counter4: {type: 'number', role: 'value', read: true, write: false},
|
||||
Current: {type: 'number', role: 'value.current', read: true, write: false, unit: 'A'},
|
||||
Data: {type: 'string', role: 'state', read: true, write: true},
|
||||
Days: {type: 'string', role: 'state', read: true, write: true},
|
||||
Dimmer: {type: 'number', role: 'level.dimmer', read: true, write: true},
|
||||
Distance: {type: 'number', role: 'value', read: true, write: false, unit: 'cm'},
|
||||
Druck: {type: 'number', role: 'value.pressure', read: true, write: false, unit: 'P'},
|
||||
Factor: {type: 'number', role: 'value', read: true, write: false},
|
||||
Fade: {type: 'boolean', role: 'switch', read: true, write: true},
|
||||
Faktor: {type: 'number', role: 'value', read: true, write: false},
|
||||
Feuchtigkeit: {type: 'number', role: 'value.humidity', read: true, write: false, unit: '%'},
|
||||
Green: {type: 'number', role: 'level.color.green', read: true, write: true, min: 0, max: 100},
|
||||
Hostname: {type: 'string', role: 'state', read: true, write: false},
|
||||
Hue: {type: 'number', role: 'level.color.hue', read: true, write: true, min: 0}, // max ??
|
||||
Humidity: {type: 'number', role: 'value.humidity', read: true, write: false, unit: '%'},
|
||||
HSBColor: {type: 'string', role: 'value.hsb', read: true, write: true},
|
||||
IPAddress: {type: 'string', role: 'state', read: true, write: false},
|
||||
IPaddress: {type: 'string', role: 'state', read: true, write: false},
|
||||
Illuminance: {type: 'number', role: 'value.illuminance', read: true, write: false, unit: 'lx'},
|
||||
IrReceived: {type: 'object', role: 'state', read: true, write: false},
|
||||
IRSend: {type: 'string', role: 'state', read: true, write: true},
|
||||
Latitude: {type: 'number', role: 'value.gps.latitude', read: true, write: false, unit: '°'},
|
||||
Laufzeit: {type: 'number', role: 'value.duration', read: true, write: false, unit: 'hours'}, /// ?
|
||||
LedBar: {type: 'number', role: 'value', read: true, write: true},
|
||||
LED_Mode: {type: 'number', role: 'value', read: true, write: true},
|
||||
Leistung: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'W'},
|
||||
Light: {type: 'number', role: 'value', read: true, write: false, unit: 'lx'},
|
||||
Longitude: {type: 'number', role: 'value.gps.longitude', read: true, write: false, unit: '°'},
|
||||
modeLedExor: {type: 'boolean', role: 'switch', read: true, write: true, def: false, desc: 'exor for white LEDs and color leds => if the white LEDs are switched on, color LEDs are switched off and vice versa'},
|
||||
modeReadColors:{type: 'boolean', role: 'switch', read: true, write: true, def: true, desc: 'allow for color read from MQTT'},
|
||||
Mode: {type: 'number', role: 'value', read: true, write: true},
|
||||
Module: {type: 'string', role: 'state', read: true, write: false},
|
||||
Noise: {type: 'number', role: 'value', read: true, write: false, unit: 'dB'},
|
||||
Output: {type: 'number', role: 'value', read: true, write: true},
|
||||
Outsidetemp: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
'PM2.5': {type: 'number', role: 'value', read: true, write: false, unit: 'ug/m3'},
|
||||
PM10: {type: 'number', role: 'value', read: true, write: false, unit: 'ug/m3'},
|
||||
POWER1: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER2: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER3: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER4: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER5: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER6: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER7: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER8: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
POWER: {type: 'boolean', role: 'switch', read: true, write: true, storeMap: true},
|
||||
Protocol: {type: 'string', role: 'state', read: true, write: true},
|
||||
Power: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'W'},
|
||||
Power_curr: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'W'},
|
||||
Pressure: {type: 'number', role: 'value.pressure', read: true, write: false, unit: 'P'},
|
||||
Punkt: {type: 'number', role: 'value', read: true, write: false, unit: '?'}, /// ?
|
||||
RGB_POWER: {type: 'boolean', role: 'switch.light', read: true, write: true, storeMap: true},
|
||||
RSSI: {type: 'number', role: 'value.rssi', read: true, write: false},
|
||||
Red: {type: 'number', role: 'level.color.red', read: true, write: true, min: 0, max: 100},
|
||||
Repeat: {type: 'number', role: 'value', read: true, write: true},
|
||||
RestartReason: {type: 'string', role: 'state', read: true, write: false},
|
||||
Returns: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Roomtemp: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Saturation: {type: 'number', role: 'level.color.saturation', read: true, write: true, min: 0}, // max: 100 ??
|
||||
Scheme: {type: 'number', role: 'value', read: true, write: false},
|
||||
SeaPressure: {type: 'number', role: 'value.pressure', read: true, write: false, unit: 'P'},
|
||||
Solarpump: {type: 'number', role: 'value', read: true, write: false},
|
||||
Solarstorage: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Spannung: {type: 'number', role: 'value.voltage', read: true, write: false, unit: 'V'},
|
||||
Speed: {type: 'number', role: 'value', read: true, write: false},
|
||||
Status: {type: 'number', role: 'value', read: true, write: false},
|
||||
Strom: {type: 'number', role: 'value.current', read: true, write: false, unit: 'A'},
|
||||
Switch1: {type: 'boolean', role: 'switch', read: true, write: false},
|
||||
Switch2: {type: 'boolean', role: 'switch', read: true, write: false},
|
||||
Switch3: {type: 'boolean', role: 'switch', read: true, write: false},
|
||||
Switch4: {type: 'boolean', role: 'switch', read: true, write: false},
|
||||
TVOC: {type: 'number', role: 'value.tvoc', read: true, write: false, unit: 'ppb'},
|
||||
Temperatur: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Temperature: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Time: {type: 'string', role: 'state', read: true, write: true},
|
||||
Timer1: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer2: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer3: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer4: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer5: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer6: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer7: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer8: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer9: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer10: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer11: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer12: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer13: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer14: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer15: {type: 'object', role: 'state', read: true, write: true},
|
||||
Timer16: {type: 'object', role: 'state', read: true, write: true},
|
||||
Today: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
Total: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
Total_in: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
Total_out: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
Uptime: {type: 'string', role: 'state', read: true, write: false},
|
||||
UvLevel: {type: 'number', role: 'value', read: true, write: false},
|
||||
VCC: {type: 'number', role: 'value.voltage', read: true, write: false, unit: 'V'},
|
||||
Vcc: {type: 'number', role: 'value.voltage', read: true, write: false, unit: 'V'},
|
||||
Version: {type: 'string', role: 'state', read: true, write: false},
|
||||
Voltage: {type: 'number', role: 'value.voltage', read: true, write: false, unit: 'V'},
|
||||
Window: {type: 'number', role: 'value', read: true, write: true},
|
||||
WW: {type: 'number', role: 'level.color.white', read: true, write: true},
|
||||
WW_POWER: {type: 'boolean', role: 'switch.light', read: true, write: true, storeMap: true},
|
||||
Wakeup: {type: 'string', role: 'state', read: true, write: true},
|
||||
Warmwater: {type: 'number', role: 'value.temperature', read: true, write: false, unit: '°C'},
|
||||
Yesterday: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
eCO2: {type: 'number', role: 'value.eco2', read: true, write: false, unit: 'ppm'},
|
||||
gestern: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'},
|
||||
heute: {type: 'number', role: 'value.power.consumption', read: true, write: false, unit: 'kWh'}
|
||||
};
|
||||
1643
lib/server.js
Normal file
1643
lib/server.js
Normal file
File diff suppressed because it is too large
Load Diff
83
lib/utils.js
Normal file
83
lib/utils.js
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let controllerDir;
|
||||
let appName;
|
||||
|
||||
/**
|
||||
* returns application name
|
||||
*
|
||||
* The name of the application can be different and this function finds it out.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getAppName() {
|
||||
const parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 2].split('.')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* looks for js-controller home folder
|
||||
*
|
||||
* @param {boolean} isInstall
|
||||
* @returns {string}
|
||||
*/
|
||||
function getControllerDir(isInstall) {
|
||||
// Find the js-controller location
|
||||
const possibilities = [
|
||||
'iobroker.js-controller',
|
||||
'ioBroker.js-controller',
|
||||
];
|
||||
/** @type {string} */
|
||||
let controllerPath;
|
||||
for (const pkg of possibilities) {
|
||||
try {
|
||||
const possiblePath = require.resolve(pkg);
|
||||
if (fs.existsSync(possiblePath)) {
|
||||
controllerPath = possiblePath;
|
||||
break;
|
||||
}
|
||||
} catch (e) { /* not found */ }
|
||||
}
|
||||
if (!controllerPath) {
|
||||
if (!isInstall) {
|
||||
console.log('Cannot find js-controller');
|
||||
process.exit(10);
|
||||
} else {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
// we found the controller
|
||||
return path.dirname(controllerPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* reads controller base settings
|
||||
*
|
||||
* @alias getConfig
|
||||
* @returns {object}
|
||||
*/
|
||||
function getConfig() {
|
||||
let configPath;
|
||||
if (fs.existsSync(
|
||||
configPath = path.join(controllerDir, 'conf', appName + '.json')
|
||||
)) {
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} else if (fs.existsSync(
|
||||
configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json')
|
||||
)) {
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} else {
|
||||
throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
|
||||
}
|
||||
}
|
||||
appName = getAppName();
|
||||
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);
|
||||
const adapter = require(path.join(controllerDir, 'lib/adapter.js'));
|
||||
|
||||
exports.controllerDir = controllerDir;
|
||||
exports.getConfig = getConfig;
|
||||
exports.Adapter = adapter;
|
||||
exports.appName = appName;
|
||||
99
main.js
Normal file
99
main.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/* jshint -W097 */
|
||||
/* jshint strict:true */
|
||||
/* jslint node: true */
|
||||
/* jslint esversion: 6 */
|
||||
|
||||
/**
|
||||
*
|
||||
* ioBroker sonoff Adapter
|
||||
*
|
||||
* (c) 2017-2019 bluefox
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||
let adapter;
|
||||
const adapterName = require('./package.json').name.split('.').pop();
|
||||
const Server = require(__dirname + '/lib/server');
|
||||
let server = null;
|
||||
|
||||
function decrypt(key, value) {
|
||||
let result = '';
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function startAdapter(options) {
|
||||
options = options || {};
|
||||
Object.assign(options, {name: adapterName});
|
||||
|
||||
adapter = new utils.Adapter(options);
|
||||
|
||||
adapter.on('ready', () => {
|
||||
// it must be like this
|
||||
|
||||
adapter.getForeignObject('system.config', (err, obj) => {
|
||||
if (obj && obj.native && obj.native.secret) {
|
||||
//noinspection JSUnresolvedVariable
|
||||
adapter.config.pass = decrypt(obj.native.secret, adapter.config.pass);
|
||||
} else {
|
||||
//noinspection JSUnresolvedVariable
|
||||
adapter.config.pass = decrypt('Zgfr56gFe87jJOM', adapter.config.pass);
|
||||
}
|
||||
main();
|
||||
});
|
||||
});
|
||||
|
||||
adapter.on('unload', cb => {
|
||||
if (server) {
|
||||
server.destroy(cb);
|
||||
} else if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
||||
// is called if a subscribed state changes
|
||||
adapter.on('stateChange', (id, state) => {
|
||||
adapter.log.debug('stateChange ' + id + ': ' + JSON.stringify(state));
|
||||
// you can use the ack flag to detect if state is desired or acknowledged
|
||||
if (state && !state.ack && server) {
|
||||
server.onStateChange(id, state);
|
||||
}
|
||||
});
|
||||
return adapter;
|
||||
}
|
||||
|
||||
function main() {
|
||||
adapter.config.TELE_SENSOR = adapter.config.TELE_SENSOR === true || adapter.config.TELE_SENSOR === 'true';
|
||||
adapter.config.TELE_STATE = adapter.config.TELE_STATE === true || adapter.config.TELE_STATE === 'true';
|
||||
adapter.config.STAT_RESULT = adapter.config.STAT_RESULT === true || adapter.config.STAT_RESULT === 'true';
|
||||
|
||||
// subscribe for all variables
|
||||
adapter.subscribeStates('*');
|
||||
|
||||
// read all states and set alive to false
|
||||
adapter.getStatesOf('', '', (err, states) => {
|
||||
if (states && states.length) {
|
||||
states.forEach(state => {
|
||||
if (state._id.match(/\.alive$/)) {
|
||||
adapter.setForeignState(state._id, false, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server = new Server(adapter);
|
||||
}
|
||||
|
||||
// If started as allInOne/compact mode => return function to create instance
|
||||
if (module && module.parent) {
|
||||
module.exports = startAdapter;
|
||||
} else {
|
||||
// or start the instance directly
|
||||
startAdapter();
|
||||
}
|
||||
46
package.json
Executable file
46
package.json
Executable file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "iobroker.sonoff",
|
||||
"description": "The adapter allows to send and receive MQTT messages from ioBroker and to be a broker.",
|
||||
"version": "2.2.3",
|
||||
"author": "bluefox <dogafox@gmail.com>",
|
||||
"homepage": "https://github.com/ioBroker/ioBroker.sonoff",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ioBroker/ioBroker.sonoff"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/ioBroker/ioBroker.sonoff/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"iobroker",
|
||||
"sonoff",
|
||||
"MQTT",
|
||||
"tasmota",
|
||||
"esp"
|
||||
],
|
||||
"devDependencies ": {
|
||||
"mqtt": "^2.18.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"mqtt-connection": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"mocha": "^5.2.0",
|
||||
"chai": "^4.1.2"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ioBroker/ioBroker.sonoff/issues"
|
||||
},
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "node node_modules/mocha/bin/mocha --exit"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
94
test/lib/mqttClient.js
Normal file
94
test/lib/mqttClient.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
const mqtt = require('mqtt');
|
||||
|
||||
function Client(cbConnected, cbChanged, config) {
|
||||
let that = this;
|
||||
if (typeof config === 'string') config = {name: config};
|
||||
config = config || {};
|
||||
config.url = config.url || 'localhost';
|
||||
this.client = mqtt.connect('mqtt://' + (config.user ? (config.user + ':' + config.pass + '@') : '') + config.url + (config.name ? '?clientId=' + config.name : ''), config);
|
||||
|
||||
this.client.on('connect', () => {
|
||||
console.log((new Date()) + ' test client connected to localhost');
|
||||
|
||||
/*that.client.publish('mqtt/0/test', 'Roger1');
|
||||
client.publish('test/out/testMessage1', 'Roger1');
|
||||
client.publish('test/out/testMessage2', 'Roger2');
|
||||
client.publish('test/in/testMessage3', 'Roger3');
|
||||
client.publish('test/in/testMessage4', 'Roger4');*/
|
||||
|
||||
/*client.publish('arduino/kitchen/out/temperature', '10.1');
|
||||
client.publish('arduino/kitchen/out/humidity', '56');
|
||||
// Current light state
|
||||
client.publish('arduino/kitchen/in/lightActor', 'false');
|
||||
|
||||
client.subscribe('arduino/kitchen/in/#');*/
|
||||
//client.subscribe('arduino/kitchen/in/updateInterval');
|
||||
that.client.subscribe('#');
|
||||
if (cbConnected) cbConnected(true);
|
||||
});
|
||||
|
||||
this.client.on('message', (topic, message, packet) => {
|
||||
// message is Buffer
|
||||
if (cbChanged) {
|
||||
cbChanged(topic, message, packet);
|
||||
} else {
|
||||
console.log('Test MQTT Client received "' + topic + '": ' + message.toString());
|
||||
}
|
||||
});
|
||||
this.client.on('close', err => {
|
||||
if (err) console.error('Connection closed: ' + err);
|
||||
// message is Buffer
|
||||
if (cbConnected) {
|
||||
cbConnected(false);
|
||||
} else {
|
||||
console.log('Test MQTT Client closed');
|
||||
}
|
||||
});
|
||||
|
||||
this.client.on('error', error => {
|
||||
console.error('Test MQTT Client error: ' + error);
|
||||
});
|
||||
|
||||
this.publish = (topic, message, qos, retain, cb) => {
|
||||
if (typeof qos === 'function') {
|
||||
cb = qos;
|
||||
qos = undefined;
|
||||
}
|
||||
if (typeof retain === 'function') {
|
||||
cb = retain;
|
||||
retain = undefined;
|
||||
}
|
||||
const opts = {
|
||||
retain: retain || false,
|
||||
qos: qos || 0
|
||||
};
|
||||
that.client.publish(topic, message, opts, cb);
|
||||
};
|
||||
this.subscribe = (topic, opts, cb) => {
|
||||
if (typeof opts === 'function') {
|
||||
cb = opts;
|
||||
opts = null;
|
||||
}
|
||||
that.client.subscribe(topic, opts, cb);
|
||||
};
|
||||
this.unsubscribe = (topic, cb) => {
|
||||
that.client.unsubscribe(topic, cb);
|
||||
};
|
||||
this.destroy = () => {
|
||||
if (that.client) {
|
||||
that.client.end();
|
||||
that.client = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.stop = this.destroy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.parent) {
|
||||
module.exports = Client;
|
||||
} else {
|
||||
new Client();
|
||||
}
|
||||
1411
test/lib/objects.js
Normal file
1411
test/lib/objects.js
Normal file
File diff suppressed because it is too large
Load Diff
718
test/lib/setup.js
Normal file
718
test/lib/setup.js
Normal file
@@ -0,0 +1,718 @@
|
||||
'use strict';
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
// check if tmp directory exists
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const child_process = require('child_process');
|
||||
const rootDir = path.normalize(__dirname + '/../../');
|
||||
const pkg = require(rootDir + 'package.json');
|
||||
const debug = typeof v8debug === 'object';
|
||||
pkg.main = pkg.main || 'main.js';
|
||||
|
||||
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
|
||||
adapterName = adapterName[adapterName.length - 2];
|
||||
let adapterStarted = false;
|
||||
|
||||
function getAppName() {
|
||||
const parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 3].split('.')[0];
|
||||
}
|
||||
|
||||
const appName = getAppName().toLowerCase();
|
||||
|
||||
let objects;
|
||||
let states;
|
||||
|
||||
let pid = null;
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
|
||||
let targetFile = target;
|
||||
|
||||
//if target is a directory a new file with the same name will be created
|
||||
if (fs.existsSync(target)) {
|
||||
if ( fs.lstatSync( target ).isDirectory() ) {
|
||||
targetFile = path.join(target, path.basename(source));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(targetFile, fs.readFileSync(source));
|
||||
}
|
||||
catch (err) {
|
||||
console.log(`file copy error: ${source}" -> "${targetFile}" (error ignored)`);
|
||||
}
|
||||
}
|
||||
|
||||
function copyFolderRecursiveSync(source, target, ignore) {
|
||||
let files = [];
|
||||
|
||||
let base = path.basename(source);
|
||||
if (base === adapterName) {
|
||||
base = pkg.name;
|
||||
}
|
||||
//check if folder needs to be created or integrated
|
||||
const targetFolder = path.join(target, base);
|
||||
if (!fs.existsSync(targetFolder)) {
|
||||
fs.mkdirSync(targetFolder);
|
||||
}
|
||||
|
||||
//copy
|
||||
if (fs.lstatSync(source).isDirectory()) {
|
||||
files = fs.readdirSync(source);
|
||||
files.forEach(function (file) {
|
||||
if (ignore && ignore.indexOf(file) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const curSource = path.join(source, file);
|
||||
const curTarget = path.join(targetFolder, file);
|
||||
if (fs.lstatSync(curSource).isDirectory()) {
|
||||
// ignore grunt files
|
||||
if (file.indexOf('grunt') !== -1) return;
|
||||
if (file === 'chai') return;
|
||||
if (file === 'mocha') return;
|
||||
copyFolderRecursiveSync(curSource, targetFolder, ignore);
|
||||
} else {
|
||||
copyFileSync(curSource, curTarget);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp')) {
|
||||
fs.mkdirSync(rootDir + 'tmp');
|
||||
}
|
||||
|
||||
function storeOriginalFiles() {
|
||||
console.log('Store original files...');
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
let f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
|
||||
objects['system.adapter.admin.0'].common.enabled = false;
|
||||
}
|
||||
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
|
||||
objects['system.adapter.admin.1'].common.enabled = false;
|
||||
}
|
||||
|
||||
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json');
|
||||
fs.writeFileSync(dataDir + 'states.json.original', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json found - ignore');
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOriginalFiles() {
|
||||
console.log('restoreOriginalFiles...');
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
let f = fs.readFileSync(dataDir + 'objects.json.original');
|
||||
fs.writeFileSync(dataDir + 'objects.json', f);
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json.original');
|
||||
fs.writeFileSync(dataDir + 'states.json', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json.original found - ignore');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkIsAdapterInstalled(cb, counter, customName) {
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
counter = counter || 0;
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
console.log('checkIsAdapterInstalled...');
|
||||
|
||||
try {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.' + customName + '.0']) {
|
||||
console.log('checkIsAdapterInstalled: ready!');
|
||||
setTimeout(() => cb && cb(), 100);
|
||||
return;
|
||||
} else {
|
||||
console.warn('checkIsAdapterInstalled: still not ready');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.error('checkIsAdapterInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsAdapterInstalled: wait...');
|
||||
setTimeout(() => checkIsAdapterInstalled(cb, counter + 1), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsControllerInstalled(cb, counter) {
|
||||
counter = counter || 0;
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
console.log('checkIsControllerInstalled...');
|
||||
try {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0']) {
|
||||
console.log('checkIsControllerInstalled: installed!');
|
||||
setTimeout(() => cb && cb(), 100);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.log('checkIsControllerInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsControllerInstalled: wait...');
|
||||
setTimeout(() => checkIsControllerInstalled(cb, counter + 1), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function installAdapter(customName, cb) {
|
||||
if (typeof customName === 'function') {
|
||||
cb = customName;
|
||||
customName = null;
|
||||
}
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
console.log('Install adapter...');
|
||||
const startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
|
||||
// make first install
|
||||
if (debug) {
|
||||
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
checkIsAdapterInstalled(error => {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
} else {
|
||||
// add controller
|
||||
const _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
|
||||
waitForEnd(_pid, () => {
|
||||
checkIsAdapterInstalled(error => {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function waitForEnd(_pid, cb) {
|
||||
if (!_pid) {
|
||||
cb(-1, -1);
|
||||
return;
|
||||
}
|
||||
_pid.on('exit', (code, signal) => {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
_pid.on('close', (code, signal) => {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installJsController(cb) {
|
||||
console.log('installJsController...');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
|
||||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
|
||||
// try to detect appName.js-controller in node_modules/appName.js-controller
|
||||
// travis CI installs js-controller into node_modules
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
|
||||
// copy all
|
||||
// stop controller
|
||||
console.log('Stop controller if running...');
|
||||
let _pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js stop', {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, () => {
|
||||
// copy all files into
|
||||
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
|
||||
console.log('Copy js-controller...');
|
||||
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
|
||||
}
|
||||
|
||||
console.log('Setup js-controller...');
|
||||
let __pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
waitForEnd(__pid, () => {
|
||||
checkIsControllerInstalled(() => {
|
||||
// change ports for object and state DBs
|
||||
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
console.log('Setup finished.');
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(() => {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// check if port 9000 is free, else admin adapter will be added to running instance
|
||||
const client = new require('net').Socket();
|
||||
client.connect(9000, '127.0.0.1', () => {
|
||||
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
client.destroy();
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => install from git');
|
||||
|
||||
child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
|
||||
cwd: rootDir + 'tmp/',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
console.log('Setup js-controller...');
|
||||
if (debug) {
|
||||
// start controller
|
||||
child_process.exec('node ' + appName + '.js setup first', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
child_process.fork(appName + '.js', ['setup', 'first'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// let npm install admin and run setup
|
||||
checkIsControllerInstalled(() => {
|
||||
let _pid;
|
||||
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, () => {
|
||||
// change ports for object and state DBs
|
||||
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(() => {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
setImmediate(() => {
|
||||
console.log('installJsController: js-controller installed');
|
||||
if (cb) cb(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyAdapterToController() {
|
||||
console.log('Copy adapter...');
|
||||
// Copy adapter to tmp/node_modules/appName.adapter
|
||||
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
|
||||
console.log('Adapter copied.');
|
||||
}
|
||||
|
||||
function clearControllerLog() {
|
||||
const dirPath = rootDir + 'tmp/log';
|
||||
let files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear controller log...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Controller log cleared');
|
||||
} catch (err) {
|
||||
console.error('cannot clear log: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearDB() {
|
||||
const dirPath = rootDir + 'tmp/iobroker-data/sqlite';
|
||||
let files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear sqlite DB...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Clear sqlite DB');
|
||||
} catch (err) {
|
||||
console.error('cannot clear DB: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupController(cb) {
|
||||
installJsController(isInited => {
|
||||
clearControllerLog();
|
||||
clearDB();
|
||||
|
||||
if (!isInited) {
|
||||
restoreOriginalFiles();
|
||||
copyAdapterToController();
|
||||
}
|
||||
// read system.config object
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
let objs;
|
||||
try {
|
||||
objs = fs.readFileSync(dataDir + 'objects.json');
|
||||
objs = JSON.parse(objs);
|
||||
}
|
||||
catch (e) {
|
||||
console.log('ERROR reading/parsing system configuration. Ignore');
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
if (!objs || !objs['system.config']) {
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
|
||||
if (cb) cb(objs['system.config']);
|
||||
});
|
||||
}
|
||||
|
||||
function startAdapter(objects, states, callback) {
|
||||
if (adapterStarted) {
|
||||
console.log('Adapter already started ...');
|
||||
if (callback) callback(objects, states);
|
||||
return;
|
||||
}
|
||||
adapterStarted = true;
|
||||
console.log('startAdapter...');
|
||||
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
|
||||
try {
|
||||
if (debug) {
|
||||
// start controller
|
||||
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
// start controller
|
||||
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify(error));
|
||||
}
|
||||
} else {
|
||||
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
|
||||
}
|
||||
if (callback) callback(objects, states);
|
||||
}
|
||||
|
||||
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
|
||||
if (typeof isStartAdapter === 'function') {
|
||||
callback = onStateChange;
|
||||
onStateChange = onObjectChange;
|
||||
onObjectChange = isStartAdapter;
|
||||
isStartAdapter = true;
|
||||
}
|
||||
|
||||
if (onStateChange === undefined) {
|
||||
callback = onObjectChange;
|
||||
onObjectChange = undefined;
|
||||
}
|
||||
|
||||
if (pid) {
|
||||
console.error('Controller is already started!');
|
||||
} else {
|
||||
console.log('startController...');
|
||||
adapterStarted = false;
|
||||
let isObjectConnected;
|
||||
let isStatesConnected;
|
||||
|
||||
const Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
|
||||
objects = new Objects({
|
||||
connection: {
|
||||
type : 'file',
|
||||
host : '127.0.0.1',
|
||||
port : 19001,
|
||||
user : '',
|
||||
pass : '',
|
||||
noFileCache: false,
|
||||
connectTimeout: 2000
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.warn(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.error(msg);
|
||||
}
|
||||
},
|
||||
connected: () => {
|
||||
isObjectConnected = true;
|
||||
if (isStatesConnected) {
|
||||
console.log('startController: started!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onObjectChange
|
||||
});
|
||||
|
||||
// Just open in memory DB itself
|
||||
const States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
|
||||
states = new States({
|
||||
connection: {
|
||||
type: 'file',
|
||||
host: '127.0.0.1',
|
||||
port: 19000,
|
||||
options: {
|
||||
auth_pass: null,
|
||||
retry_max_delay: 15000
|
||||
}
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
connected: () => {
|
||||
isStatesConnected = true;
|
||||
if (isObjectConnected) {
|
||||
console.log('startController: started!!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onStateChange
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stopAdapter(cb) {
|
||||
if (!pid) {
|
||||
console.error('Controller is not running!');
|
||||
if (cb) {
|
||||
setImmediate(() => cb(false));
|
||||
}
|
||||
} else {
|
||||
adapterStarted = false;
|
||||
pid.on('exit', (code, signal) => {
|
||||
if (pid) {
|
||||
console.log('child process terminated due to receipt of signal ' + signal);
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.on('close', (code, signal) => {
|
||||
if (pid) {
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
function _stopController() {
|
||||
if (objects) {
|
||||
objects.destroy();
|
||||
objects = null;
|
||||
}
|
||||
if (states) {
|
||||
states.destroy();
|
||||
states = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stopController(cb) {
|
||||
let timeout;
|
||||
if (objects) {
|
||||
console.log('Set system.adapter.' + pkg.name + '.0');
|
||||
objects.setObject('system.adapter.' + pkg.name + '.0', {
|
||||
common:{
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopAdapter(() => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(true);
|
||||
cb = null;
|
||||
}
|
||||
});
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
console.log('child process NOT terminated');
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(false);
|
||||
cb = null;
|
||||
}
|
||||
pid = null;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Setup the adapter
|
||||
function setAdapterConfig(common, native, instance) {
|
||||
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
if (common) objects[id].common = common;
|
||||
if (native) objects[id].native = native;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
|
||||
}
|
||||
|
||||
// Read config of the adapter
|
||||
function getAdapterConfig(instance) {
|
||||
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
return objects[id];
|
||||
}
|
||||
|
||||
if (typeof module !== undefined && module.parent) {
|
||||
module.exports.getAdapterConfig = getAdapterConfig;
|
||||
module.exports.setAdapterConfig = setAdapterConfig;
|
||||
module.exports.startController = startController;
|
||||
module.exports.stopController = stopController;
|
||||
module.exports.setupController = setupController;
|
||||
module.exports.stopAdapter = stopAdapter;
|
||||
module.exports.startAdapter = startAdapter;
|
||||
module.exports.installAdapter = installAdapter;
|
||||
module.exports.appName = appName;
|
||||
module.exports.adapterName = adapterName;
|
||||
module.exports.adapterStarted = adapterStarted;
|
||||
}
|
||||
708
test/lib/states.js
Normal file
708
test/lib/states.js
Normal file
@@ -0,0 +1,708 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const rootDir = path.normalize(__dirname + '/../../');
|
||||
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
|
||||
adapterName = adapterName[adapterName.length - 2];
|
||||
|
||||
const logger = {
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.warn(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
function States(cb, stateChange) {
|
||||
const that = this;
|
||||
const _States = require(rootDir + 'tmp/node_modules/iobroker.js-controller/lib/states');
|
||||
let callbackId = 0;
|
||||
|
||||
const options = {
|
||||
stateChange: (id, state) => stateChange && stateChange(id, state)
|
||||
};
|
||||
|
||||
that.namespace = 'test';
|
||||
|
||||
that.states = new _States({
|
||||
connection: {
|
||||
type : 'file',
|
||||
host : '127.0.0.1',
|
||||
port : 19000,
|
||||
options : {
|
||||
auth_pass : null,
|
||||
retry_max_delay : 15000
|
||||
}
|
||||
},
|
||||
logger: logger,
|
||||
change: (id, state) => {
|
||||
if (!id || typeof id !== 'string') {
|
||||
console.log('Something is wrong! ' + JSON.stringify(id));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear cache if accidentally got the message about change (Will work for admin and javascript)
|
||||
if (id.match(/^system\.user\./) || id.match(/^system\.group\./)) {
|
||||
that.users = [];
|
||||
}
|
||||
|
||||
// If someone want to have log messages
|
||||
if (that.logList && id.match(/\.logging$/)) {
|
||||
that.logRedirect(state ? state.val : false, id.substring(0, id.length - '.logging'.length));
|
||||
} else
|
||||
if (id === 'log.system.adapter.' + that.namespace) {
|
||||
that.processLog(state);
|
||||
} else
|
||||
// If this is messagebox
|
||||
if (id === 'messagebox.system.adapter.' + that.namespace && state) {
|
||||
// Read it from fifo list
|
||||
that.states.delMessage('system.adapter.' + that.namespace, state._id);
|
||||
const obj = state;
|
||||
if (obj) {
|
||||
// If callback stored for this request
|
||||
if (obj.callback &&
|
||||
obj.callback.ack &&
|
||||
obj.callback.id &&
|
||||
that.callbacks &&
|
||||
that.callbacks['_' + obj.callback.id]) {
|
||||
// Call callback function
|
||||
if (that.callbacks['_' + obj.callback.id].cb) {
|
||||
that.callbacks['_' + obj.callback.id].cb(obj.message);
|
||||
delete that.callbacks['_' + obj.callback.id];
|
||||
}
|
||||
// delete too old callbacks IDs, like garbage collector
|
||||
const now = Date.now();
|
||||
for (const _id in that.callbacks) {
|
||||
if (that.callbacks.hasOwnProperty(_id) && now - that.callbacks[_id].time > 3600000) delete that.callbacks[_id];
|
||||
}
|
||||
|
||||
} else {
|
||||
if (options.message) {
|
||||
// Else inform about new message the adapter
|
||||
options.message(obj);
|
||||
}
|
||||
that.emit('message', obj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (id.slice(that.namespace.length) === that.namespace) {
|
||||
if (typeof options.stateChange === 'function') options.stateChange(id.slice(that.namespace.length + 1), state);
|
||||
// emit 'stateChange' event instantly
|
||||
setImmediate(() => that.emit('stateChange', id.slice(that.namespace.length + 1), state));
|
||||
|
||||
} else {
|
||||
if (typeof options.stateChange === 'function') options.stateChange(id, state);
|
||||
if (id.substring(0, 4) === 'log.') {
|
||||
console.log("LOG");
|
||||
}
|
||||
if (that.emit) {
|
||||
// emit 'stateChange' event instantly
|
||||
setImmediate(() => that.emit('stateChange', id, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connectTimeout: (error) => {
|
||||
if (logger) logger.error(that.namespace + ' no connection to states DB');
|
||||
if (cb) cb('Timeout');
|
||||
}
|
||||
});
|
||||
|
||||
// Send message to other adapter instance or all instances of adapter
|
||||
that.sendTo = function sendTo(objName, command, message, callback) {
|
||||
if (typeof message === 'undefined') {
|
||||
message = command;
|
||||
command = 'send';
|
||||
}
|
||||
const obj = {command: command, message: message, from: 'system.adapter.' + that.namespace};
|
||||
|
||||
if (!objName.match(/^system\.adapter\./)) objName = 'system.adapter.' + objName;
|
||||
|
||||
that.log.info('sendTo "' + command + '" to ' + objName + ' from system.adapter.' + that.namespace + ': ' + JSON.stringify(message));
|
||||
|
||||
// If not specific instance
|
||||
if (!objName.match(/\.[0-9]+$/)) {
|
||||
// Send to all instances of adapter
|
||||
that.objects.getObjectView('system', 'instance', {startkey: objName + '.', endkey: objName + '.\u9999'}, (err, _obj) => {
|
||||
if (_obj) {
|
||||
for (let i = 0; i < _obj.rows.length; i++) {
|
||||
that.states.pushMessage(_obj.rows[i].id, obj);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
// force subscribe even no messagebox enabled
|
||||
if (!that.common.messagebox && !that.mboxSubscribed) {
|
||||
that.mboxSubscribed = true;
|
||||
that.states.subscribeMessage('system.adapter.' + that.namespace);
|
||||
}
|
||||
|
||||
obj.callback = {
|
||||
message: message,
|
||||
id: callbackId++,
|
||||
ack: false,
|
||||
time: Date.now()
|
||||
};
|
||||
if (callbackId >= 0xFFFFFFFF) {
|
||||
callbackId = 1;
|
||||
}
|
||||
if (!that.callbacks) that.callbacks = {};
|
||||
that.callbacks['_' + obj.callback.id] = {cb: callback};
|
||||
|
||||
// delete too old callbacks IDs
|
||||
const now = Date.now();
|
||||
for (const _id in that.callbacks) {
|
||||
if (that.callbacks.hasOwnProperty(_id) && now - that.callbacks[_id].time > 3600000) {
|
||||
delete that.callbacks[_id];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj.callback = callback;
|
||||
obj.callback.ack = true;
|
||||
}
|
||||
}
|
||||
|
||||
that.states.pushMessage(objName, obj);
|
||||
}
|
||||
};
|
||||
|
||||
// Send message to specific host or to all hosts
|
||||
that.sendToHost = function sendToHost(objName, command, message, callback) {
|
||||
if (typeof message === 'undefined') {
|
||||
message = command;
|
||||
command = 'send';
|
||||
}
|
||||
const obj = {command: command, message: message, from: 'system.adapter.' + that.namespace};
|
||||
|
||||
if (objName && objName.substring(0, 'system.host.'.length) !== 'system.host.') objName = 'system.host.' + objName;
|
||||
|
||||
if (!objName) {
|
||||
// Send to all hosts
|
||||
that.objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.' + '\u9999'}, null, (err, res) => {
|
||||
if (!err && res.rows.length) {
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
const parts = res.rows[i].id.split('.');
|
||||
// ignore system.host.name.alive and so on
|
||||
if (parts.length === 3) {
|
||||
that.states.pushMessage(res.rows[i].id, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
// force subscribe even no messagebox enabled
|
||||
if (!that.common.messagebox && !that.mboxSubscribed) {
|
||||
that.mboxSubscribed = true;
|
||||
that.states.subscribeMessage('system.adapter.' + that.namespace);
|
||||
}
|
||||
|
||||
obj.callback = {
|
||||
message: message,
|
||||
id: callbackId++,
|
||||
ack: false,
|
||||
time: Date.now()
|
||||
};
|
||||
if (callbackId >= 0xFFFFFFFF) callbackId = 1;
|
||||
if (!that.callbacks) that.callbacks = {};
|
||||
that.callbacks['_' + obj.callback.id] = {cb: callback};
|
||||
} else {
|
||||
obj.callback = callback;
|
||||
obj.callback.ack = true;
|
||||
}
|
||||
}
|
||||
|
||||
that.states.pushMessage(objName, obj);
|
||||
}
|
||||
};
|
||||
|
||||
that.setState = function setState(id, state, ack, options, callback) {
|
||||
if (typeof state === 'object' && typeof ack !== 'boolean') {
|
||||
callback = options;
|
||||
options = ack;
|
||||
ack = undefined;
|
||||
}
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
id = that._fixId(id, 'state');
|
||||
|
||||
if (typeof ack === 'function') {
|
||||
callback = ack;
|
||||
ack = undefined;
|
||||
}
|
||||
|
||||
if (typeof state !== 'object' || state === null || state === undefined) state = {val: state};
|
||||
|
||||
if (ack !== undefined) {
|
||||
state.ack = ack;
|
||||
}
|
||||
|
||||
state.from = 'system.adapter.' + that.namespace;
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'setState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.setState(id, state, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.setState(id, state, callback);
|
||||
}
|
||||
};
|
||||
|
||||
that.setForeignState = function setForeignState(id, state, ack, options, callback) {
|
||||
if (typeof state === 'object' && typeof ack !== 'boolean') {
|
||||
callback = options;
|
||||
options = ack;
|
||||
ack = undefined;
|
||||
}
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (typeof ack === 'function') {
|
||||
callback = ack;
|
||||
ack = undefined;
|
||||
}
|
||||
|
||||
if (typeof state !== 'object' || state === null || state === undefined) state = {val: state};
|
||||
|
||||
if (ack !== undefined) {
|
||||
state.ack = ack;
|
||||
}
|
||||
|
||||
state.from = 'system.adapter.' + that.namespace;
|
||||
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'setState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.setState(id, state, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.setState(id, state, callback);
|
||||
}
|
||||
};
|
||||
|
||||
that.getState = function getState(id, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
id = that._fixId(id, 'state');
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'getState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.getState(id, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.getState(id, callback);
|
||||
}
|
||||
};
|
||||
|
||||
that.getStateHistory = function getStateHistory(id, start, end, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
id = that._fixId(id, 'state');
|
||||
that.getForeignStateHistory(id, start, end, options, callback);
|
||||
};
|
||||
|
||||
that.getForeignStateHistory = function getForeignStateHistory(id, start, end, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (typeof start === 'function') {
|
||||
callback = start;
|
||||
start = undefined;
|
||||
end = undefined;
|
||||
} else if (typeof end === 'function') {
|
||||
callback = end;
|
||||
end = undefined;
|
||||
}
|
||||
|
||||
start = start || Math.round((new Date()).getTime() / 1000) - 31536000; // - 1 year
|
||||
end = end || Math.round((new Date()).getTime() / 1000) + 5000;
|
||||
|
||||
const history = [];
|
||||
const docs = [];
|
||||
|
||||
// get data from states
|
||||
that.log.debug('get states history ' + id + ' ' + start + ' ' + end);
|
||||
that.getFifo(id, (err, res) => {
|
||||
if (!err && res) {
|
||||
let iProblemCount = 0;
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (!res[i]) {
|
||||
iProblemCount++;
|
||||
continue;
|
||||
}
|
||||
if (res[i].ts < start) {
|
||||
continue;
|
||||
} else if (res[i].ts > end) {
|
||||
break;
|
||||
}
|
||||
history.push(res[i]);
|
||||
}
|
||||
if (iProblemCount) that.log.warn('got null states ' + iProblemCount + ' times for ' + id);
|
||||
|
||||
that.log.debug('got ' + res.length + ' datapoints for ' + id);
|
||||
} else {
|
||||
if (err !== 'Not exists') {
|
||||
that.log.error(err);
|
||||
} else {
|
||||
that.log.debug('datapoints for ' + id + ' do not yet exist');
|
||||
}
|
||||
}
|
||||
|
||||
// fetch a history document from objectDB
|
||||
function getObjectsLog(cid, callback) {
|
||||
that.log.info('getObjectLog ' + cid);
|
||||
that.getForeignObject(cid, options, (err, res) => {
|
||||
if (!err && res.common.data) {
|
||||
for (let i = 0; i < res.common.data.length; i++) {
|
||||
if (res.common.data[i].ts < start) {
|
||||
continue;
|
||||
} else if (res.common.data[i].ts > end) {
|
||||
break;
|
||||
}
|
||||
history.push(res.common.data[i]);
|
||||
}
|
||||
} else {
|
||||
that.log.warn(cid + ' not found');
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
// queue objects history documents fetching
|
||||
function queue(ts) {
|
||||
if (ts < start) {
|
||||
callback(null, history);
|
||||
return;
|
||||
}
|
||||
const cid = 'history.' + id + '.' + ts2day(ts);
|
||||
if (docs.indexOf(cid) !== -1) {
|
||||
getObjectsLog(cid, err => queue(ts - 86400)); // - 1 day
|
||||
} else {
|
||||
queue(ts - 86400); // - 1 day
|
||||
}
|
||||
}
|
||||
|
||||
// get list of available history documents
|
||||
that.objects.getObjectList({startkey: 'history.' + id, endkey: 'history.' + id + '\u9999'}, options, (err, res) => {
|
||||
if (!err && res.rows.length) {
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
docs.push(res.rows[i].id);
|
||||
}
|
||||
queue(end);
|
||||
} else {
|
||||
callback(null, history);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// normally only foreign history has interest, so there is no getHistory and getForeignHistory
|
||||
that.getHistory = function getHistory(id, options, callback) {
|
||||
options = options || {};
|
||||
options.end = options.end || Math.round((new Date()).getTime() / 1000) + 5000;
|
||||
if (!options.count && !options.start) {
|
||||
options.start = options.start || Math.round((new Date()).getTime() / 1000) - 604800; // - 1 week
|
||||
}
|
||||
|
||||
if (!options.instance) {
|
||||
if (!that.defaultHistory) {
|
||||
// read default history instance from system.config
|
||||
return getDefaultHistory(() => that.getHistory(id, options, callback));
|
||||
} else {
|
||||
options.instance = that.defaultHistory;
|
||||
}
|
||||
}
|
||||
|
||||
that.sendTo(options.instance || 'history.0', 'getHistory', {id: id, options: options}, res => {
|
||||
setImmediate(() => callback(res.error, res.result, res.step));
|
||||
});
|
||||
};
|
||||
|
||||
// Convert ID adapter.instance.device.channel.state
|
||||
// Convert ID to {device: D, channel: C, state: S}
|
||||
that.idToDCS = function idToDCS(id) {
|
||||
if (!id) return null;
|
||||
const parts = id.split('.');
|
||||
if (parts[0] + '.' + parts[1] !== that.namespace) {
|
||||
that.log.warn("Try to decode id not from this adapter");
|
||||
return null;
|
||||
}
|
||||
return {device: parts[2], channel: parts[3], state: parts[4]};
|
||||
};
|
||||
|
||||
that.getForeignState = function getForeignState(id, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'getState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.getState(id, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.getState(id, callback);
|
||||
}
|
||||
};
|
||||
|
||||
that.delForeignState = function delForeignState(id, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'delState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.delState(id, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.delState(id, callback);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
that.delState = function delState(id, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
id = that._fixId(id);
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(id, options, 'delState', err => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
} else {
|
||||
that.states.delState(id, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
that.states.delState(id, callback);
|
||||
}
|
||||
};
|
||||
|
||||
that.getStates = function getStates(pattern, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
pattern = that._fixId(pattern, 'state');
|
||||
that.getForeignStates(pattern, options, callback);
|
||||
};
|
||||
|
||||
that.getForeignStates = function getForeignStates(pattern, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
const list = {};
|
||||
if (typeof pattern === 'function') {
|
||||
callback = pattern;
|
||||
pattern = '*';
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
logger.error('getForeignStates invalid callback for ' + pattern);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof pattern === 'object') {
|
||||
that.states.getStates(pattern, (err, arr) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
|
||||
list[pattern[i]] = arr[i] || {};
|
||||
}
|
||||
callback(null, list);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const keys = [];
|
||||
let params = {};
|
||||
if (pattern && pattern !== '*') {
|
||||
params = {
|
||||
startkey: pattern.replace('*', ''),
|
||||
endkey: pattern.replace('*', '\u9999')
|
||||
};
|
||||
}
|
||||
that.objects.getObjectView('system', 'state', params, options, (err, res) => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
keys.push(res.rows[i].id);
|
||||
}
|
||||
|
||||
if (options && options.user && options.user !== 'system.user.admin') {
|
||||
checkStates(keys, options, 'getState', (err, keys) => {
|
||||
if (err) {
|
||||
if (typeof callback === 'function') callback(err);
|
||||
return;
|
||||
}
|
||||
that.states.getStates(keys, function (err, arr) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
|
||||
list[keys[i]] = arr[i] || {};
|
||||
}
|
||||
if (typeof callback === 'function') callback(null, list);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
that.states.getStates(keys, function (err, arr) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < res.rows.length; i++) {
|
||||
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
|
||||
list[keys[i]] = arr[i] || {};
|
||||
}
|
||||
if (typeof callback === 'function') callback(null, list);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
that.subscribeForeignStates = function subscribeForeignStates(pattern, options) {
|
||||
if (!pattern) pattern = '*';
|
||||
that.states.subscribe(pattern, options);
|
||||
};
|
||||
|
||||
that.unsubscribeForeignStates = function unsubscribeForeignStates(pattern, options) {
|
||||
if (!pattern) pattern = '*';
|
||||
that.states.unsubscribe(pattern, options);
|
||||
};
|
||||
|
||||
that.subscribeStates = function subscribeStates(pattern, options) {
|
||||
// Exception. Threat the '*' case automatically
|
||||
if (!pattern || pattern === '*') {
|
||||
that.states.subscribe(that.namespace + '.*', options);
|
||||
} else {
|
||||
pattern = that._fixId(pattern, 'state');
|
||||
that.states.subscribe(pattern, options);
|
||||
}
|
||||
};
|
||||
|
||||
that.unsubscribeStates = function unsubscribeStates(pattern, options) {
|
||||
if (!pattern || pattern === '*') {
|
||||
that.states.unsubscribe(that.namespace + '.*', options);
|
||||
} else {
|
||||
pattern = that._fixId(pattern, 'state');
|
||||
that.states.unsubscribe(pattern, options);
|
||||
}
|
||||
};
|
||||
|
||||
that.pushFifo = function pushFifo(id, state, callback) {
|
||||
that.states.pushFifo(id, state, callback);
|
||||
};
|
||||
|
||||
that.trimFifo = function trimFifo(id, start, end, callback) {
|
||||
that.states.trimFifo(id, start, end, callback);
|
||||
};
|
||||
|
||||
that.getFifoRange = function getFifoRange(id, start, end, callback) {
|
||||
that.states.getFifoRange(id, start, end, callback);
|
||||
};
|
||||
|
||||
that.getFifo = function getFifo(id, callback) {
|
||||
that.states.getFifo(id, callback);
|
||||
};
|
||||
|
||||
that.lenFifo = function lenFifo(id, callback) {
|
||||
that.states.lenFifo(id, callback);
|
||||
};
|
||||
|
||||
that.subscribeFifo = function subscribeFifo(pattern) {
|
||||
that.states.subscribeFifo(pattern);
|
||||
};
|
||||
|
||||
that.getSession = function getSession(id, callback) {
|
||||
that.states.getSession(id, callback);
|
||||
};
|
||||
that.setSession = function setSession(id, ttl, data, callback) {
|
||||
that.states.setSession(id, ttl, data, callback);
|
||||
};
|
||||
that.destroySession = function destroySession(id, callback) {
|
||||
that.states.destroySession(id, callback);
|
||||
};
|
||||
|
||||
that.getMessage = function getMessage(callback) {
|
||||
that.states.getMessage('system.adapter.' + that.namespace, callback);
|
||||
};
|
||||
|
||||
that.lenMessage = function lenMessage(callback) {
|
||||
that.states.lenMessage('system.adapter.' + that.namespace, callback);
|
||||
};
|
||||
|
||||
// Write binary block into redis, e.g image
|
||||
that.setBinaryState = function setBinaryState(id, binary, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
that.states.setBinaryState(id, binary, callback);
|
||||
};
|
||||
|
||||
// Read binary block fromredis, e.g. image
|
||||
that.getBinaryState = function getBinaryState(id, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
that.states.getBinaryState(id, callback);
|
||||
};
|
||||
|
||||
logger.debug(that.namespace + ' statesDB connected');
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
setImmediate(() => cb(), 0);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
module.exports = States;
|
||||
92
test/testPackageFiles.js
Normal file
92
test/testPackageFiles.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/* jshint -W097 */
|
||||
/* jshint strict:false */
|
||||
/* jslint node: true */
|
||||
/* jshint expr: true */
|
||||
'use strict';
|
||||
const expect = require('chai').expect;
|
||||
const fs = require('fs');
|
||||
|
||||
describe('Test package.json and io-package.json', () => {
|
||||
it('Test package files', function (done) {
|
||||
console.log();
|
||||
|
||||
const fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
|
||||
const ioPackage = JSON.parse(fileContentIOPackage);
|
||||
|
||||
const fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
|
||||
const npmPackage = JSON.parse(fileContentNPMPackage);
|
||||
|
||||
expect(ioPackage).to.be.an('object');
|
||||
expect(npmPackage).to.be.an('object');
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
|
||||
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
|
||||
|
||||
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
|
||||
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
|
||||
console.log();
|
||||
}
|
||||
|
||||
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
|
||||
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
|
||||
|
||||
if (ioPackage.common.name.indexOf('template') !== 0) {
|
||||
if (Array.isArray(ioPackage.common.authors)) {
|
||||
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
|
||||
if (ioPackage.common.authors.length === 1) {
|
||||
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
|
||||
console.log();
|
||||
}
|
||||
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
|
||||
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
|
||||
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
|
||||
console.log();
|
||||
}
|
||||
if (
|
||||
ioPackage.common.title.indexOf('iobroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('ioBroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('adapter') !== -1 ||
|
||||
ioPackage.common.title.indexOf('Adapter') !== -1
|
||||
) {
|
||||
console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.');
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (ioPackage.common.name.indexOf('vis-') !== 0) {
|
||||
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
|
||||
console.log('WARNING: Admin3 support is missing! Please add it');
|
||||
console.log();
|
||||
}
|
||||
if (ioPackage.common.materialize) {
|
||||
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
|
||||
}
|
||||
}
|
||||
|
||||
const licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
|
||||
const fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
|
||||
if (fileContentReadme.indexOf('## Changelog') === -1) {
|
||||
console.log('Warning: The README.md should have a section ## Changelog');
|
||||
console.log();
|
||||
}
|
||||
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
|
||||
if (!licenseFileExists) {
|
||||
console.log('Warning: The License should also exist as LICENSE file');
|
||||
console.log();
|
||||
}
|
||||
if (fileContentReadme.indexOf('## License') === -1) {
|
||||
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
|
||||
console.log();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
264
test/testServer.js
Normal file
264
test/testServer.js
Normal file
@@ -0,0 +1,264 @@
|
||||
/* jshint -W097 */
|
||||
// jshint strict:true
|
||||
/*jslint node: true */
|
||||
/*jslint esversion: 6 */
|
||||
'use strict';
|
||||
let expect = require('chai').expect;
|
||||
let setup = require(__dirname + '/lib/setup');
|
||||
|
||||
let objects = null;
|
||||
let states = null;
|
||||
let mqttClientEmitter = null;
|
||||
let mqttClientDetector = null;
|
||||
let connected = false;
|
||||
let lastReceivedTopic1;
|
||||
let lastReceivedMessage1;
|
||||
let lastReceivedTopic2;
|
||||
let lastReceivedMessage2;
|
||||
|
||||
let clientConnected1 = false;
|
||||
let clientConnected2 = false;
|
||||
let brokerStarted = false;
|
||||
|
||||
let rules = {
|
||||
'tele/sonoff_4ch/STATE': {send: '{"Time":"2017-10-02T19:26:06", "Uptime":0, "Vcc":3.226, "POWER1":"OFF", "POWER2":"OFF", "POWER3":"OFF", "POWER4":"OFF", "Wifi":{"AP":1, "SSId":"AAA", "RSSI": 15}}', expect: {Vcc: 3.226, Wifi_RSSI: 15}},
|
||||
'tele/sonoff/SENSOR': {send: '{"Time":"2017-10-05T17:43:19", "DS18x20":{"DS1":{"Type":"DS18B20", "Address":"28FF9A9876815022A", "Temperature":12.2}}, "TempUnit":"C"}', expect: {DS18x20_DS1_Temperature: 12.2}},
|
||||
'tele/sonoff5/SENSOR': {send: '{"Time":"2017-10-03T14:02:25", "AM2301-14":{"Temperature":21.6, "Humidity":54.7}, "TempUnit":"C"}', expect: {'AM2301-14_Temperature': 21.6, 'AM2301-14_Humidity': 54.7}},
|
||||
'tele/SonoffPOW/INFO1': {send: '{"Module":"Sonoff Pow", "Version":"5.8.0", "FallbackTopic":"SonoffPOW", "GroupTopic":"sonoffs"}', expect: {'INFO.Module': 'Sonoff Pow', 'INFO.Version': '5.8.0'}},
|
||||
'tele/SonoffPOW/INFO2': {send: '{"WebServerMode":"Admin", "Hostname":"Sonoffpow", "IPAddress":"192.168.2.182"}', expect: {'INFO.Hostname': 'Sonoffpow', 'INFO.IPAddress': '192.168.2.182'}},
|
||||
'tele/SonoffPOW/INFO3': {send: '{"RestartReason":"Software/System restart"}', expect: {'INFO.RestartReason': 'Software/System restart'}},
|
||||
'tele/sonoff_4ch/ENERGY': {send: '{"Time":"2017-10-02T19:24:32", "Total":1.753, "Yesterday":0.308, "Today":0.205, "Period":0, "Power":3, "Factor":0.12, "Voltage":221, "Current":0.097}', expect: {'ENERGY.Total': 1.753, 'ENERGY.Current': 0.097}},
|
||||
'tele/sonoff_4ch/ENERGY1': {send: '"Time":"2017-10-02T19:24:32", "Total":1.753, "Yesterday":0.308, "Today":0.205, "Period":0, "Power":3, "Factor":0.12, "Voltage":221, "Current":0.097}', expect: {}},
|
||||
'tele/sonoff_1ch/STATE': {send: '{"Time":"2017-10-02T19:24:32", "Color": "112233"}', expect: {}},
|
||||
'tele/sonoff/STATE': {send: '{"Time":"2018-06-19T06:39:33","Uptime":"0T23:47:32","Vcc":3.482,"POWER":"OFF","Dimmer":100,"Color":"000000FF","HSBColor":"0,0,0","Channel":[0,0,0,100],"Scheme":0,"Fade":"OFF","Speed":4,"LedTable":"OFF","Wifi":{"AP":1,"SSId":"WLAN-7490","RSSI":50,"APMac":"34:31:C4:C6:EB:0F"}}',
|
||||
expect:{}},
|
||||
'tele/sonoff1/SENSOR': {send: '{"Time":"2018-06-15T10:03:24","DS18B20":{"Temperature":0.0},"TempUnit":"C"}', expect: {'DS18B20_Temperature': 0}},
|
||||
'/ESP_BOX/BM280/Pressure': {send: '1010.09', expect: {'Pressure': 1010.09}},
|
||||
'/ESP_BOX/BM280/Humidity': {send: '42.39', expect: {'Humidity': 42.39}},
|
||||
'/ESP_BOX/BM280/Temperature': {send: '25.86', expect: {'Temperature': 25.86}},
|
||||
'/ESP_BOX/BM280/Approx. Altitude': {send: '24', expect: {'Approx_Altitude': 24}},
|
||||
'stat/sonoff/POWER': {send: 'ON', expect: {'POWER': true}},
|
||||
'cmnd/sonoff/POWER': {send: '', expect: {}},
|
||||
'stat/sonoff/RESULT': {send: '{"POWER": "ON"}', expect: {'RESULT': null}},
|
||||
'stat/sonoff/LWT': {send: 'someTopic', expect: {'LWT': null}},
|
||||
'stat/sonoff/ABC': {send: 'text', expect: {'ABC': null}}
|
||||
};
|
||||
function decrypt(key, value) {
|
||||
let result = '';
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function startClients(_done) {
|
||||
// start mqtt client
|
||||
const MqttClient = require(__dirname + '/lib/mqttClient.js');
|
||||
|
||||
// Start client to emit topics
|
||||
mqttClientEmitter = new MqttClient(connected => {
|
||||
// on connected
|
||||
if (connected) {
|
||||
console.log('Test MQTT Emitter is connected to MQTT broker');
|
||||
clientConnected1 = true;
|
||||
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
|
||||
_done();
|
||||
_done = null;
|
||||
}
|
||||
}
|
||||
}, (topic, message) => {
|
||||
console.log(Date.now() + ' emitter received ' + topic + ': ' + message.toString());
|
||||
// on receive
|
||||
lastReceivedTopic1 = topic;
|
||||
lastReceivedMessage1 = message ? message.toString() : null;
|
||||
}, {name: 'Emitter*1', user: 'user', pass: 'pass1'});
|
||||
|
||||
// Start client to receive topics
|
||||
mqttClientDetector = new MqttClient(connected => {
|
||||
// on connected
|
||||
if (connected) {
|
||||
console.log('Test MQTT Detector is connected to MQTT broker');
|
||||
clientConnected2 = true;
|
||||
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
|
||||
_done();
|
||||
_done = null;
|
||||
}
|
||||
}
|
||||
}, (topic, message) => {
|
||||
console.log(Date.now() + ' detector received ' + topic + ': ' + message.toString());
|
||||
// on receive
|
||||
lastReceivedTopic2 = topic;
|
||||
lastReceivedMessage2 = message ? message.toString() : null;
|
||||
console.log(JSON.stringify(lastReceivedMessage2));
|
||||
}, {name: 'Detector-1', user: 'user', pass: 'pass1'});
|
||||
}
|
||||
|
||||
function checkMqtt2Adapter(id, task, _it, _done) {
|
||||
_it.timeout(1000);
|
||||
|
||||
lastReceivedMessage1 = null;
|
||||
lastReceivedTopic1 = null;
|
||||
lastReceivedTopic2 = null;
|
||||
lastReceivedMessage2 = null;
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Publish ${id}: ${task.send}`);
|
||||
mqttClientEmitter.publish(id, task.send, err => {
|
||||
expect(err).to.be.undefined;
|
||||
|
||||
setTimeout(() => {
|
||||
let count = 0;
|
||||
for (let e in task.expect) {
|
||||
if (! task.expect.hasOwnProperty(e)) continue;
|
||||
count++;
|
||||
(function (_id, _val) {
|
||||
objects.getObject('sonoff.0.Emitter_1.' + _id, (err, obj) => {
|
||||
if (_val !== null) {
|
||||
if (!obj) console.error('Object sonoff.0.Emitter_1.' + _id + ' not found');
|
||||
expect(obj).to.be.not.null.and.not.undefined;
|
||||
expect(obj._id).to.be.equal('sonoff.0.Emitter_1.' + _id);
|
||||
expect(obj.type).to.be.equal('state');
|
||||
|
||||
states.getState(obj._id, function (err, state) {
|
||||
expect(state).to.be.not.null.and.not.undefined;
|
||||
expect(state.val).to.be.equal(_val);
|
||||
expect(state.ack).to.be.true;
|
||||
if (!--count) _done();
|
||||
});
|
||||
} else {
|
||||
expect(obj).to.be.undefined;
|
||||
|
||||
states.getState('sonoff.0.Emitter_1.' + _id, (err, state) => {
|
||||
expect(state).to.be.undefined;
|
||||
if (!--count) _done();
|
||||
});
|
||||
}
|
||||
});
|
||||
})(e, task.expect[e]);
|
||||
}
|
||||
if (!count) _done();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function checkAdapter2Mqtt(id, mqttid, value, _done) {
|
||||
console.log(new Date().toISOString() + ' Send ' + id + ' with value '+ value);
|
||||
|
||||
lastReceivedTopic1 = null;
|
||||
lastReceivedMessage1 = null;
|
||||
lastReceivedTopic2 = null;
|
||||
lastReceivedMessage2 = null;
|
||||
|
||||
states.setState(id, {
|
||||
val: value,
|
||||
ack: false
|
||||
}, (err, id) => {
|
||||
setTimeout(() => {
|
||||
if (!lastReceivedTopic1) {
|
||||
setTimeout(() => {
|
||||
expect(lastReceivedTopic1).to.be.equal(mqttid);
|
||||
expect(lastReceivedMessage1).to.be.equal(value ? 'ON' : 'OFF');
|
||||
_done();
|
||||
}, 200);
|
||||
} else {
|
||||
expect(lastReceivedTopic1).to.be.equal(mqttid);
|
||||
expect(lastReceivedMessage1).to.be.equal(value ? 'ON' : 'OFF');
|
||||
_done();
|
||||
}
|
||||
}, 400);
|
||||
});
|
||||
}
|
||||
|
||||
function checkConnection(value, done, counter) {
|
||||
counter = counter || 0;
|
||||
if (counter > 20) {
|
||||
done && done('Cannot check ' + value);
|
||||
return;
|
||||
}
|
||||
|
||||
states.getState('sonoff.0.info.connection', (err, state) => {
|
||||
if (err) console.error(err);
|
||||
if (state && typeof state.val === 'string' && ((value && state.val.indexOf(',') !== -1) || (!value && state.val.indexOf(',') === -1))) {
|
||||
connected = value;
|
||||
done();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
checkConnection(value, done, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Sonoff server: Test mqtt server', () => {
|
||||
before('Sonoff server: Start js-controller', function (_done) { //
|
||||
this.timeout(600000); // because of first install from npm
|
||||
setup.adapterStarted = false;
|
||||
|
||||
setup.setupController(systemConfig => {
|
||||
let config = setup.getAdapterConfig();
|
||||
// enable adapter
|
||||
config.common.enabled = true;
|
||||
config.common.loglevel = 'debug';
|
||||
config.native.user = 'user';
|
||||
config.native.pass = decrypt(systemConfig.native.secret, 'pass1');
|
||||
|
||||
setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController((_objects, _states) => {
|
||||
objects = _objects;
|
||||
states = _states;
|
||||
brokerStarted = true;
|
||||
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
|
||||
_done();
|
||||
_done = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
startClients(_done);
|
||||
});
|
||||
|
||||
it('Sonoff Server: Check if connected to MQTT broker', done => {
|
||||
if (!connected) {
|
||||
checkConnection(true, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}).timeout(2000);
|
||||
|
||||
for (let r in rules) {
|
||||
(function(id, task) {
|
||||
it('Sonoff Server: Check receive ' + id, function (done) { // let FUNCTION here
|
||||
checkMqtt2Adapter(id, task, this, done);
|
||||
});
|
||||
})(r, rules[r]);
|
||||
}
|
||||
|
||||
// give time to client to receive all messages
|
||||
it('wait', done => {
|
||||
setTimeout(() => done(), 1000);
|
||||
}).timeout(3000);
|
||||
|
||||
it('Sonoff server: detector must receive cmnd/sonoff/POWER', done => {
|
||||
checkAdapter2Mqtt('sonoff.0.Emitter_1.POWER', 'cmnd/sonoff/POWER', false, done);
|
||||
}).timeout(2000);
|
||||
|
||||
it('Sonoff Server: check reconnection', done => {
|
||||
mqttClientEmitter.stop();
|
||||
mqttClientDetector.stop();
|
||||
checkConnection(false, error => {
|
||||
expect(error).to.be.not.ok;
|
||||
startClients();
|
||||
checkConnection(true, error => {
|
||||
expect(error).to.be.not.ok;
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).timeout(10000);
|
||||
|
||||
after('Sonoff Server: Stop js-controller', function (_done) { // let FUNCTION and not => here
|
||||
this.timeout(5000);
|
||||
mqttClientEmitter.stop();
|
||||
mqttClientDetector.stop();
|
||||
setup.stopController(() => _done());
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user