diff --git a/alexa4p3/README.md b/alexa4p3/README.md
index 942fb6c02..c60f19b05 100755
--- a/alexa4p3/README.md
+++ b/alexa4p3/README.md
@@ -4,34 +4,34 @@ Alexa4PayloadV3
## Table of Content
-1. [Generell](#generell)
-2. [Change Log](#changelog)
-3. [Requrirements](#requirements)
- [Einrichtung Amazon-Skill / Lambda-Funktion](#Skill) **Neu**
-4. [Icon / Display Categories](#Icons) **Update**
-5. [Entwicklung / Einbau von neuen Skills](#Entwicklung)
-6. [Alexa-ThermostatController](#ThermostatController) + [Thermosensor](#Thermostatsensor)
-7. [Alexa-PowerController](#PowerController)
-8. [Alexa-BrightnessController](#BrightnessController)
-9. [Alexa-PowerLevelController](#PowerLevelController)
-10. [Alexa-PercentageController](#PercentageController)
-11. [Alexa-LockController](#LockController)
-12. [Alexa-CameraStreamController](#CameraStreamController) **Update**
-13. [Alexa-SceneController](#SceneController)
-14. [Alexa-ContactSensor](#ContactSensor)
-15. [Alexa-ColorController](#ColorController)
-16. [Alexa-RangeController](#RangeController) **Neu**
-17. [Alexa-ColorTemperaturController](#ColorTemperaturController) **Neu**
-18. [Alexa-PlaybackController](#PlaybackController) **Neu**
-19. [Web-Interface](#webinterface) **Neu**
-
-
-
-## [Beispiel-Konfigurationen](#Beispiel) **Neu**
-
-
-- [der fast perfekte Rolladen](#perfect_blind) **Neu**
-- [Items in Abhängikeit des letzten benutzten Echos-Devices schalten](#get_last_alexa) **Neu**
+1. Generell
+2. Change Log
+3. Requrirements
+ Einrichtung Amazon-Skill / Lambda-Funktion **Neu**
+4. Icon / Display Categories **Update**
+5. Entwicklung / Einbau von neuen Skills
+6. Alexa-ThermostatController + Thermosensor
+7. Alexa-PowerController
+8. Alexa-BrightnessController
+9. Alexa-PowerLevelController
+10. Alexa-PercentageController
+11. Alexa-LockController
+12. Alexa-CameraStreamController **Update**
+13. Alexa-SceneController
+14. Alexa-ContactSensor
+15. Alexa-ColorController
+16. Alexa-RangeController **Neu**
+17. Alexa-ColorTemperaturController **Neu**
+18. Alexa-PlaybackController **Neu**
+19. Web-Interface **Neu**
+
+
+
+## Beispiel-Konfigurationen **Neu**
+
+
+- der fast perfekte Rolladen **Neu**
+- Items in Abhängikeit des letzten benutzten Echos-Devices schalten **Neu**
# --------------------------------------
## Generell
@@ -1030,7 +1030,7 @@ Default-Wert ist : RGB
Die Helligkeit wird bei Farbwechsel unverändert beibehalten. Bei HSB-Werten wird der ursprüngliche Wert gepuffert und als aktueller Wert wieder an das Item übergeben.
Zum Wechseln der Helligkeit einen BrightnessController hinzufügen
-Details siehe [BrightnessController](#BrightnessController)
+Details siehe BrightnessController
Beispiel Konfiguration (wobei R_Wert, G_Wert, B_Wert für die Gruppenadressen stehen :
%YAML 1.1
diff --git a/alexa4p3/README.not_convertable.md.off b/alexa4p3/README.not_convertable.md.off
new file mode 100755
index 000000000..942fb6c02
--- /dev/null
+++ b/alexa4p3/README.not_convertable.md.off
@@ -0,0 +1,1275 @@
+# alexa4p3
+
+Alexa4PayloadV3
+
+## Table of Content
+
+1. [Generell](#generell)
+2. [Change Log](#changelog)
+3. [Requrirements](#requirements)
+ [Einrichtung Amazon-Skill / Lambda-Funktion](#Skill) **Neu**
+4. [Icon / Display Categories](#Icons) **Update**
+5. [Entwicklung / Einbau von neuen Skills](#Entwicklung)
+6. [Alexa-ThermostatController](#ThermostatController) + [Thermosensor](#Thermostatsensor)
+7. [Alexa-PowerController](#PowerController)
+8. [Alexa-BrightnessController](#BrightnessController)
+9. [Alexa-PowerLevelController](#PowerLevelController)
+10. [Alexa-PercentageController](#PercentageController)
+11. [Alexa-LockController](#LockController)
+12. [Alexa-CameraStreamController](#CameraStreamController) **Update**
+13. [Alexa-SceneController](#SceneController)
+14. [Alexa-ContactSensor](#ContactSensor)
+15. [Alexa-ColorController](#ColorController)
+16. [Alexa-RangeController](#RangeController) **Neu**
+17. [Alexa-ColorTemperaturController](#ColorTemperaturController) **Neu**
+18. [Alexa-PlaybackController](#PlaybackController) **Neu**
+19. [Web-Interface](#webinterface) **Neu**
+
+
+
+## [Beispiel-Konfigurationen](#Beispiel) **Neu**
+
+
+- [der fast perfekte Rolladen](#perfect_blind) **Neu**
+- [Items in Abhängikeit des letzten benutzten Echos-Devices schalten](#get_last_alexa) **Neu**
+# --------------------------------------
+
+## Generell
+
+Die Daten des Plugin müssen in den Ordner /usr/local/smarthome/plugins/alexa4p3/ (wie gewohnt)
+Die Rechte entsprechend setzen.
+
+Das Plugin sollte ohne Änderungen die ursprünglichen Funktionen von Payload V2
+weiterverarbeiten können.
+
+Um die neuen Payload-Features nutzen zu können muss lediglich die Skill-Version in der Amazon Hölle auf PayLoad Version 3 umgestellt werden. Alles andere kann unverändert weiterverwendet werden.
+
+Das Plugin muss in der plugin.yaml eingefügt werden :
+
+
+Alexa4P3:
+ plugin_name: Alexa4P3
+ service_port: 9000
+
+
+Das ursprünglich Plugin kann deaktiviertwerden :
+
+
+#alexa:
+# plugin_name: alexa4p3
+# service_port: 9000
+
+
+Idealerweise kopiert man sich seine ganzen conf/yaml Files aus dem Items-Verzeichnis.
+und ersetzt dann die "alten" Actions durch die "Neuen". Nachdem der Skill auf Payload V3 umgestellt wurde
+muss ein Discover durchgeführt werden. Im besten Fall funktioniert dann alles wie gewohnt.
+
+In den Items sind die "neuen" V3 Actions zu definieren :
+
+Zum Beispiel :
+
+PayloadV2 : turnon
+
+PayloadV3 : TurnOn
+
+Die Actions unterscheiden sich zwischen Payload V2 und V3 oft nur durch Gross/Klein-Schreibung
+
+## Change Log
+
+### 20.10.2020
+- Doku von Schuma für die Einrichtung des Skills bei Amazon ergänzt - eingefügt bei Requirements
+
+### 11.04.2020
+- Version auf 1.0.2 für shNG Release 1.7 erhöht
+
+### 12.03.2020
+- Ergänzung bei Wertänderung durch das Plugin wid der "Plugin Identifier" "alexa4p3" an die Change Item-Methode übegeben (PR #332)
+
+### 07.12.2019
+- Web-Interface um Protokoll-Log ergänzt
+- PlaybackController realisiert
+- bux-fix for alias-Devices, es wurden nicht alle Eigenschaften an das Alias-Device übergeben. Voice-Steuerung funktionierte, Darstellung in der App war nicht korrekt.
+
+### 06.12.2019 - zum Nikolaus :-)
+- RangeController mit global "utterances" für Rolladen realisiert - endlich "Alexa, mach den Rolladen zu/auf - hoch/runter"
+
+### 01.12.2019
+- Web-Interface ergänzt
+- Prüfung auf Verwendung von gemischtem Payload V2/V3 im Web-Interface
+- Bug-Fix bei falsch definierten Devices (alexa_name fehlt) - Issue #300 - diese werden entfernt und ein Log-Eintrag erfolgt
+- Bug-Fix alexa-description (PR #292) - die Beschreibung in der App lautet nun "device.name" + "by smarthomeNG"
+- alexa_description beim Geräte Discovery ergänzt
+
+### 20.04.2019
+- Authentifizierungsdaten (Credentials) für AlexaCamProxy eingebaut
+- Umbennung des Plugin-Pfades auf "alexa4p3" !! Hier die Einträge in der plugin.yaml anpassen.
+
+### 17.02.2019
+- Version erhöht aktuell 1.0.1
+- CameraStreamController Integration für Beta-Tests fertiggestellt
+
+### 26.01.2019
+- ColorController eingebaut
+- Doku für ColorController erstellt
+- Neues Attribut für CameraStreamController (**alexa_csc_proxy_uri**) zum streamen von Kameras in lokalen Netzwerken in Verbindung mit CamProxy4AlexaP3
+
+### 19.01.2019
+- Version auf 1.0.0.2 erhöht
+- ContactSensor Interface eingebaut
+- Doku für ContactSensor Interface ergänzt
+- DoorLockController fertiggestellt
+- DoorLockController Doku ergänzt
+- ReportLockState eingebaut
+- Doku für die Erstellung des Alexa-Skill´s auf Amazon als PDF erstellt
+
+### 31.12.2018
+- Version auf 1.0.0.1 erhöht
+- CameraStreamController eingebaut
+- Dokumentation für CameraStreamController ergänzt
+- PowerLevelController eingebaut
+- Dokumentation für PowerLevelController ergänzt
+- Debugs und Testfunktionen kontrolliert und für Upload entfernt
+
+### 24.12.2018
+- Doku für PercentageController erstellt
+- Bug Fix für fehlerhafte Testfunktionen aus der Lambda
+
+### 12.12.2018
+- Scene Controller eingebaut
+- Doku für Scene Controller erstellt
+- PercentageController eingebaut
+
+
+## Requrirements
+
+Das Plugin benötigt Modul Python-Requests. Dies sollte mit dem Core immer auf dem aktuellen Stand mitkommen.
+
+
+## Amazon Skill / Lambda
+
+Es muss ein funktionierender Skill in der Amazon Developer Konsole / AWS Lambda erstellt werden.
+Eine ausführliche Dokumentation unter ./assets/Alexa_V3_plugin.pdf zu finden.
+Vielen Dank @schuma für die ausführliche Dokumentation
+
+Ansonsten keine Requirements.
+
+## Icons / Catagories
+Optional kann im Item angegeben werden welches Icon in der Alexa-App verwendet werden soll :
+
+alexa_icon = "LIGHT"
+
+
+
+
+
+
+
+
+
+ Value |
+ Description |
+
+
+
+
+ ACTIVITY_TRIGGER |
+ A combination of devices set to a specific state. Use activity triggers for scenes when the state changes must occur in a specific order. For example, for a scene named "watch Netflix" you might power on the TV first, and then set the input to HDMI1. |
+
+
+ CAMERA |
+ A media device with video or photo functionality. |
+
+
+ COMPUTER |
+ A non-mobile computer, such as a desktop computer. |
+
+
+ CONTACT_SENSOR |
+ An endpoint that detects and reports changes in contact between two surfaces. |
+
+
+ DOOR |
+ A door. |
+
+
+ DOORBELL |
+ A doorbell. |
+
+
+ EXTERIOR_BLIND |
+ A window covering on the outside of a structure. |
+
+
+ FAN |
+ A fan. |
+
+
+ GAME_CONSOLE |
+ A game console, such as Microsoft Xbox or Nintendo Switch |
+
+
+ GARAGE_DOOR |
+ A garage door. Garage doors must implement the ModeController interface to open and close the door. |
+
+
+ INTERIOR_BLIND |
+ A window covering on the inside of a structure. |
+
+
+ LAPTOP |
+ A laptop or other mobile computer. |
+
+
+ LIGHT |
+ A light source or fixture. |
+
+
+ MICROWAVE |
+ A microwave oven. |
+
+
+ MOBILE_PHONE |
+ A mobile phone. |
+
+
+ MOTION_SENSOR |
+ An endpoint that detects and reports movement in an area. |
+
+
+ MUSIC_SYSTEM |
+ A network-connected music system. |
+
+
+ NETWORK_HARDWARE |
+ A network router. |
+
+
+ OTHER |
+ An endpoint that doesn't belong to one of the other categories. |
+
+
+ OVEN |
+ An oven cooking appliance. |
+
+
+ PHONE |
+ A non-mobile phone, such as landline or an IP phone. |
+
+
+ SCENE_TRIGGER |
+ A combination of devices set to a specific state. Use scene triggers for scenes when the order of the state change is not important. For example, for a scene named "bedtime" you might turn off the lights and lower the thermostat, in any order. |
+
+
+ SCREEN |
+ A projector screen. |
+
+
+ SECURITY_PANEL |
+ A security panel. |
+
+
+ SMARTLOCK |
+ An endpoint that locks. |
+
+
+ SMARTPLUG |
+ A module that is plugged into an existing electrical outlet, and then has a device plugged into it. For example, a user can plug a smart plug into an outlet, and then plug a lamp into the smart plug. A smart plug can control a variety of devices. |
+
+
+ SPEAKER |
+ A speaker or speaker system. |
+
+
+ STREAMING_DEVICE |
+ A streaming device such as Apple TV, Chromecast, or Roku. |
+
+
+ SWITCH |
+ A switch wired directly to the electrical system. A switch can control a variety of devices. |
+
+
+ TABLET |
+ A tablet computer. |
+
+
+ TEMPERATURE_SENSOR |
+ An endpoint that reports temperature, but does not control it. The temperature data of the endpoint is not shown in the Alexa app. |
+
+
+ THERMOSTAT |
+ An endpoint that controls temperature, stand-alone air conditioners, or heaters with direct temperature control. |
+
+
+ TV |
+ A television. |
+
+
+ WEARABLE |
+ A network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear. |
+
+
+
+
+default = "Switch" (vergleiche : https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories )
+
+Optional kann im Item angegeben werden ob es durch Amazon abgefragt werden kann :
+
+ alexa_retrievable = true
+
+
+default = false
+**==!! Achtung das sorgt für Traffic auf der Lambda bei Benutzung der Alexa-App !!==**
+
+
+Die sonstigen Parameter aus dem ursprüngliche Alexa-Plugin bleiben erhalten und werden weiterhin genutzt.
+(alexa_name / alexa_device / alexa_description / alexa_actions /alexa_item_range)
+
+Beispiel für Item im .conf-Format:
+
+
+[OG]
+ [[Flur]]
+ name = Flur_Obeschoss
+ [[[Spots]]]
+ alexa_name = "Licht Flur OG"
+ alexa_device = Licht_Flur_OG
+ alexa_actions = "TurnOn TurnOff"
+ alexa_icon = "LIGHT"
+ type = bool
+ visu_acl = rw
+ knx_dpt = 1
+ knx_listen = 1/1/107
+ knx_send = 1/1/107
+ enforce_updates = true
+ [[[[dimmen]]]]
+ type = num
+ alexa_device = Licht_Flur_OG
+ alexa_actions = "AdjustBrightness SetBrightness"
+ alexa_retrievable= True
+ alexa_item_range = 0-255
+ visu_acl = rw
+ knx_dpt = 5
+ knx_listen = 1/4/100
+ knx_send = 1/3/100
+ knx_init = 1/4/100
+ enforce_updates = true
+ [[[Treppe]]]
+ type = bool
+ visu_acl = rw
+ knx_dpt = 1
+ knx_listen = 1/1/133
+ knx_send = 1/1/133
+ enforce_updates = true
+
+
+
+im .yaml-Format :
+
+
+%YAML 1.1
+---
+
+OG:
+
+ Flur:
+ name: Flur_Obeschoss
+ Spots:
+ alexa_name: Licht Flur OG
+ alexa_device: Licht_Flur_OG
+ alexa_actions: TurnOn TurnOff
+ alexa_icon: LIGHT
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ knx_listen: 1/1/107
+ knx_send: 1/1/107
+ enforce_updates: 'true'
+ dimmen:
+ type: num
+ alexa_device: Licht_Flur_OG
+ alexa_actions: AdjustBrightness SetBrightness
+ alexa_retrievable: 'True'
+ alexa_item_range: 0-255
+ visu_acl: rw
+ knx_dpt: 5
+ knx_listen: 1/4/100
+ knx_send: 1/3/100
+ knx_init: 1/4/100
+ enforce_updates: 'true'
+ Treppe:
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ knx_listen: 1/1/133
+ knx_send: 1/1/133
+ enforce_updates: 'true'
+
+
+## Entwicklung / Einbau von neuen Fähigkeiten
+Um weitere Actions hinzuzufügen muss die Datei p3_actions.py mit den entsprechenden Actions ergänzt werden.
+(wie ursprünglich als selbstregistrierende Funktion)
+
+
+
+@alexa('action_name', 'directive_type', 'response_type','namespace',[]) // in der Datei p3_actions.py
+@alexa('TurnOn', 'TurnOn', 'powerState','Alexa.PowerController',[]) // in der Datei p3_actions.py
+
+
+
+Hierbei ist zu beachten, das für die jeweilige Action die folgenden Paramter übergeben werden :
+
+action_name = neuer Action-Name z.B.: TurnOn (gleich geschrieben wie in der Amazon-Beschreibung - auch Gross/Klein)
+
+directive_type = gleich wie action_name (nur notwendig wegen Kompatibilität V2 und V3)
+
+response_type = Property des Alexa Interfaces
+siehe Amazon z.B. : https://developer.amazon.com/docs/device-apis/alexa-brightnesscontroller.html#properties
+
+namespace = NameSpace des Alexa Interfaces
+siehe Amazon z.B.: https://developer.amazon.com/docs/device-apis/list-of-interfaces.html
+
+[] = Array für Abhängigkeiten von anderen Capabilties (z.B. beim Theromcontroller ThermostatMode und TargetTemperatur)
+
+In der "service.py" muss für den ReportState der Rückgabewert für die neue Action hinzugefügt werden.
+(siehe Quellcode)
+
+## Alexa-ThermostatController + Thermosensor
+
+Es kann nun via Alexa die Solltemperatur verändert werden und der Modus des Thermostaten kann umgestellt werden.
+Die Konfiguration der YAML-Datei sieht wie folgt aus
+
+Es müssen beim Thermostaten in YAML die Einträge für :
+alexa_thermo_config, alexa_icon, alexa_actions vorgenommen werden.
+
+alexa_thermo_config = "0:AUTO 1:HEAT 2:COOL 3:ECO 4:ECO"
+Hierbei stehen die Werte für für die KNX-Werte von DPT 20
+
+
+$00 Auto
+$01 Comfort
+$02 Standby
+$03 Economy
+$04 Building Protection
+
+
+Die Modi AUTO / HEAT / COOL / ECO / OFF entsprechen den Alexa-Befehlen aus dem Theromstatconroller
+siehe Amazon : https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#thermostatmode
+
+
+alexa_icon = "THERMOSTAT" = Thermostatcontroller
+
+alexa_icon = "TEMPERATURE_SENSOR" = Temperatursensor
+
+
+### Thermostatsensor
+
+Der Temperartursensor wird beim Item der Ist-Temperatur hinterlegt.
+Der Thermostatconroller wird beim Thermostat-Item hinterlegt. An Amazon werden die Icons als Array übertragen.
+Die Abfrage der Ist-Temperatur muss mit der Action "ReportTemperature" beim Item der Ist-Temperatur hinterlegt werden.
+
+
+alexa_actions : "ReportTemperature"
+
+
+Alexa wie ist die Temperatur in der Küche ?
+
+### Verändern der Temperatur (SetTargetTemperature AdjustTargetTemperature)
+
+
+alexa_actions = "SetTargetTemperature AdjustTargetTemperature"
+
+
+Hiermit werden die Solltemperatur auf einen Wert gesetzt oder die Temperatur erhöht.
+Diese Actions müssen beim Item des Soll-Wertes des Thermostaten eingetragen werden
+
+Alexa erhöhe die Temperatur in der Küche um zwei Grad
+
+Alexa stelle die Temperatur in der Küche auf zweiundzwanzig Grad
+
+Alexa wie ist die Temperatur in der Küche eingestellt ?
+
+### Thermostatmode
+
+alexa_actions = "SetThermostatMode"
+
+Hier wird das Item des Modus angesteuert. Diese Action muss beim Item des Thermostat-Modes eingetragen werden.
+Falls keine Modes angegeben wurden wird "0:AUTO" als default gesetzt
+
+Alexa stelle den Thermostaten Küche auf Heizen
+
+
+
+%YAML 1.1
+---
+EG:
+ name: EG
+ sv_page: cat_seperator
+ Kueche:
+ temperature:
+ name: Raumtemperatur
+ alexa_description : "Küche Thermostat"
+ alexa_name : "Küche Thermostat"
+ alexa_device : thermo_Kueche
+ alexa_thermo_config : "0:AUTO 1:HEAT 2:OFF 3:ECO 4:ECO"
+ alexa_icon : "THERMOSTAT"
+ actual:
+ type: num
+ sqlite: 'yes'
+ visu: 'yes'
+ knx_dpt: 9
+ initial_value: 21.8
+ alexa_device : thermo_Kueche
+ alexa_retrievable : True
+ alexa_actions : "ReportTemperature"
+ alexa_icon : "TEMPERATURE_SENSOR"
+ SollBasis:
+ type: num
+ visu_acl: rw
+ knx_dpt: 9
+ initial_value: 21.0
+ alexa_device : thermo_Kueche
+ alexa_actions : "SetTargetTemperature AdjustTargetTemperature"
+ Soll:
+ type: num
+ sqlite: 'yes'
+ visu: 'yes'
+ visu_acl: rw
+ knx_dpt: 9
+ initial_value: 21.0
+ alexa_device : thermo_Kueche
+ mode:
+ type: num
+ visu_acl: rw
+ knx_dpt: 20
+ initial_value: 1.0
+ alexa_device : thermo_Kueche
+ alexa_actions : "SetThermostatMode"
+ state:
+ type: bool
+ visu_acl: r
+ sqlite: 'yes'
+ visu: 'yes'
+ knx_dpt: 1
+ cache: true
+ alexa_device : thermo_Kueche
+
+
+Beispiel für einen MDT-Glastron, der Modus wird auf Objekt 12 in der ETS-Parametrierung gesendet (Hierzu eine entsprechende
+Gruppenadresse anlegen)
+
+
+ temperature:
+ name: Raumtemperatur
+ alexa_description : "Küche Thermostat"
+ alexa_name : "Küche Thermostat"
+ alexa_device : thermo_Kueche
+ alexa_thermo_config : "0:AUTO 1:HEAT 2:OFF 3:ECO 4:ECO"
+ alexa_icon : "THERMOSTAT"
+ plan:
+ type: num
+ visu_acl: rw
+ database@mysqldb: init
+ knx_dpt: 9
+ knx_send: 2/1/2
+ knx_listen: 2/1/2
+ knx_cache: 2/1/2
+ alexa_device : thermo_Kueche
+ alexa_actions : "SetTargetTemperature AdjustTargetTemperature"
+ state:
+ type: num
+ visu_acl: r
+ database@mysqldb: init
+ knx_dpt: 9
+ knx_listen: 2/1/1
+ knx_cache: 2/1/1
+ alexa_device : thermo_Kueche
+ alexa_retrievable : True
+ alexa_actions : "ReportTemperature"
+ alexa_icon : "TEMPERATURE_SENSOR"
+ mode:
+ type: num
+ visu_acl: rw
+ knx_dpt: 20
+ initial_value: 1.0
+ alexa_device : thermo_Kueche
+ alexa_actions : "SetThermostatMode"
+
+ humidity:
+ type: num
+ visu_acl: r
+ database@mysqldb: init
+ knx_dpt: 9
+ knx_listen: 2/1/5
+ knx_cache: 2/1/5
+
+ actor_state:
+ type: num
+ visu_acl: r
+ database@mysqldb: init
+ knx_dpt: '5.001'
+ knx_listen: 2/1/3
+ knx_cache: 2/1/3
+
+
+
+## Alexa-PowerController
+
+Alexa schalte das Licht im Büro ein
+
+Mit dem PowerController können beliebige Geräte ein und ausgeschalten werden.
+Folgende Paramter sind anzugeben :
+
+
+ alexa_actions = "TurnOn TurnOff"
+
+
+Beispiel
+
+
+ [[[Licht]]]
+ type = bool
+ alexa_name = "Licht Büro"
+ alexa_description = "Licht Büro"
+ alexa_actions = "TurnOn TurnOff"
+ alexa_retrievable = true
+ alexa_icon = "LIGHT"
+ visu_acl = rw
+ knx_dpt = 1
+ knx_listen = 1/1/105
+ knx_send = 1/1/105
+ enforce_updates = true
+
+
+## Alexa-BrightnessController
+Alexa stelle das Licht am Esstisch auf fünfzig Prozent
+Alexa dimme das Licht am Esstisch um zehn Prozent
+Folgende Parameter sind anzugeben :
+
+
+ alexa_actions = "AdjustBrightness SetBrightness"
+ alexa_item_range = 0-255
+
+
+Es kann der BrightnessController mit dem PowerController kombiniert werden
+
+Beispiel :
+
+ [[[Licht_Esstisch]]]
+ type = bool
+ alexa_name = "Licht Esstisch"
+ alexa_actions = "TurnOn TurnOff"
+ alexa_device = licht_esstisch
+ alexa_description = "Licht Esstisch"
+ alexa_icon = "SWITCH"
+ alexa_retrievable= True
+ visu_acl = rw
+ knx_dpt = 1
+ knx_listen = 1/1/9
+ knx_send = 1/1/9
+ enforce_updates = true
+ [[[[dimmen]]]]
+ type = num
+ alexa_device = licht_esstisch
+ alexa_actions = "AdjustBrightness SetBrightness"
+ alexa_retrievable= True
+ alexa_item_range = 0-255
+ visu_acl = rw
+ knx_dpt = 5
+ knx_listen = 1/4/9
+ knx_send = 1/3/9
+ knx_init = 1/4/9
+ enforce_updates = true
+
+
+## Alexa-PowerLevelController
+## !!!! erst ab Plugin-Version 1.0.1 oder höher !!!!
+
+Alexa stelle Energie Licht Küche auf achtzig
+Alexa erhöhe Energie Licht Küche um zehn
+
+Es können Werte von 0-100 angesagt werden.
+
+Der PowerLevelController kann in Verbindung mit dem PowerController verwendet werden. Funktionsweise entspricht der von PercentageController und BrightnessController
+
+Folgende Parameter sind anzugeben :
+
+
+ alexa_actions = "SetPowerLevel AdjustPowerLevel"
+ alexa_item_range = 0-255
+
+
+## Alexa-PercentageController
+
+Alexa stelle Rolladen Essen West auf achtzig Prozent
+
+Mit dem PercentageController können Geräte auf einen bestimmten Prozentwert gestellt werden. Der PercentageController eignet sich für die Umsetzung von
+Rolladen/Jalousien.
+
+Folgende Parameter sind anzugeben :
+
+
+ alexa_actions = "SetPercentage AdjustPercentage"
+ alexa_item_range = 0-255
+
+
+In Verbindung mit dem PowerController (TurnOn / TurnOff) kann der Rolladen
+dann mit "Schalte Rolladen Büro EIN" zugefahren werden und mit "Schalte Rolladen Büro AUS" aufgefahren werden.
+(Zwar nicht wirklich schön aber funktioniert)
+
+'enforce_updates' sollte auf true gesetzt sein damit auch auf den Bus gesendet wird wenn keine Änderung des Wertes erfolgt.
+
+Beispiel Konfiguration im yaml-Format:
+
+ Rolladen:
+ alexa_name: Rollladen Büro
+ alexa_device: rolladen_buero
+ alexa_description: Rollladen Büro
+ alexa_icon: SWITCH
+
+ move:
+ type: num
+ alexa_device: rolladen_buero
+ alexa_actions: TurnOn TurnOff
+ alexa_retrievable: 'True'
+ visu_acl: rw
+ knx_dpt: 1
+ knx_send: 3/2/23
+ enforce_updates: 'true'
+
+ stop:
+ type: num
+ visu_acl: rw
+ enforce_updates: 'true'
+ knx_dpt: 1
+ knx_send: 3/1/23
+
+ pos:
+ type: num
+ visu_acl: rw
+ alexa_device: rolladen_buero
+ alexa_actions: SetPercentage AdjustPercentage
+ alexa_item_range: 0-255
+ knx_dpt: 5
+ knx_listen: 3/3/23
+ knx_send: 3/4/23
+ knx_init: 3/3/23
+ enforce_updates: 'true'
+
+
+
+## Alexa-LockController
+## !!!! erst ab Plugin-Version 1.0.1 oder höher !!!!
+Die Probleme in der Amazon-Cloud mit dem LockController sind behoben.
+
+Die Funktion ist im Moment so realisiert, das bei "Unlock" ein "ON" (=1) auf
+das Item geschrieben wird. Bei "Lock" wird ebenfalls ein "ON" (=1) auf die Gruppenadresse geschrieben. Eventuell die Werte mittels "eval"-Funktion direkt
+in der Item Config anpassen.
+Für den Zustand Smartlock geschlossen oder offen ist
+"OFF" (=0) Tür offen
+"ON" (=1) Tür geschlossen
+
+Wenn keine Rückmeldewert angegeben ist **(ReportLockState)** wird als default Wert "Locked" gemeldet.
+Es wird beim Öffnen oder Schliessen immer der
+ausgeführte Befehl als Rückmeldng gegeben.(Locked/Unlocked)
+
+Directive "Alexa schliesse die Haustür auf", Rückgabewert "Unlocked"
+Directive "Alexa schliesse die Haustür ab", Rückgabewert "Locked"
+
+Es muss nach dem das Smartlock gefunden wurden die Sprachsteuerung über die Alexa-App freigegeben werden. Es muss für die Sprachsteuerung ein 4-stelliger PIN eingegeben werden welcher immer bei öffnen abgefragt wird. (Vorgabe Amazon, kann nicht umgangen werden)
+
+Folgende Befehle sind möglich :
+
+Alexa, entsperre die Haustür
+
+Alexa, schliesse die Haustür auf
+
+Alexa, sperre die Haustür
+
+Alexa, schliesse die Haustür ab
+
+Folgende Parameter sind anzugeben :
+
+
+ alexa_actions : Lock Unlock ReportLockState
+ alexa_icon: SMARTLOCK
+
+
+Beispiel mit einem Aktor-Kanal für öffnen, ein Aktor-Kanal für schliessen mit virtueller Rückmeldung, rücksetzen des Aktorkanals nach 5 Sekunden via autotimer
+
+
+ haustuer:
+ name: haustuer
+ alexa_description: Haustür
+ alexa_name: Haustuer
+ alexa_device: haustuer
+ alexa_icon: SMARTLOCK
+ unlock:
+ knx_send: 9/9/1
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ alexa_device: haustuer
+ alexa_actions: Unlock
+ autotimer: 5 = 0
+ on_change:
+ - test.testzimmer.haustuer.state = 0 if sh.test.testzimmer.haustuer.unlock() == True else None
+ lock:
+ knx_send: 9/9/2
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ alexa_device: haustuer
+ alexa_actions: Lock
+ autotimer: 5 = 0
+ on_change:
+ - test.testzimmer.haustuer.state = 1 if sh.test.testzimmer.haustuer.lock() == True else None
+ state:
+ type: num
+ visu_acl: rw
+ alexa_device: haustuer
+ alexa_actions: ReportLockStatelexa_actions: ReportLockState
+
+
+Beispiel mit einem Aktor-Kanal für öffnen, ein Aktor-Kanal für schliessen mit KNX-Eingang für die Rückmeldung.Der jeweilige Aktor-Kanel ist als Treppenlicht-Automat konfiguriert und stellt selbstständig zurück.
+
+
+ haustuer:
+ name: haustuer
+ alexa_description: Haustür
+ alexa_name: Haustuer
+ alexa_device: haustuer
+ alexa_icon: SMARTLOCK
+ unlock:
+ knx_send: 9/9/1
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ alexa_device: haustuer
+ alexa_actions: Unlock
+ lock:
+ knx_send: 9/9/2
+ type: bool
+ visu_acl: rw
+ knx_dpt: 1
+ alexa_device: haustuer
+ alexa_actions: Lock
+ state:
+ knx_listen: 9/9/3
+ knx_init: 9/9/3
+ type: num
+ visu_acl: rw
+ knx_dpt: 20
+ alexa_device: haustuer
+ alexa_actions: ReportLockState
+
+
+## Alexa-CameraStreamContoller
+## !!!! erst ab Plugin-Version 1.0.1 oder höher !!!!
+
+Alexa zeige die Haustür Kamera.
+
+Der CameraController funktioniert mit Cameras die den Anforderungen von Amazon entsprechen.
+d.h. :
+- TLSv1.2 Verschlüsselung
+- Kamera auf Port 443 erreichbar
+
+##!! für Kameras im lokalen Netzwerk wird gerade noch ein Camera Proxy entwickelt - dieser gibt dann die Möglichkeit auch private Kameras einzubinden !!
+#Look out for : AlexaCamProxy4P3
+
+
+Aus den bereitgestellten Streams wird
+immer der mit der höchsten Auflösung an Alexa übermittelt.
+
+Folgende Parameter sind anzugeben :
+
+##### alexa_csc_proxy_uri **Update**: URL über DynDNS vergeben um die Kamera mittels CamProxy4AlexaP3 zu streamen
+
+##### alexa_proxy_credentials **Update**: Zugangsdaten für den AlexaCamProxy falls dieser mit Authentication "Basic" oder "Digest" parametriert wird. Angabe in der Form "USER":"PWD"
+
+
+##### alexa_camera_imageUri: die URL des Vorschau-Pictures der Kamera
+
+##### alexa_stream_1: Definition für den ersten Stream der Kamara, es werden bis zu 3 Streams unterstützt. Hier müssen die Details zum Stream definiert werden (protocol = rtsp, resolutions = Array mit der Auflösung, authorizationTypes = Autorisierung, videoCodecs = Array der VideoCodes, autoCodecs = Array der Audiocodes)
+
+##### alexa_csc_uri: Auflistung der Stream-URL´s für Stream1: / Stream2: / Stream3
+siehe Tabelle unten für mögliche Werte
+
+(Beispiel im YAML-Format):
+
+
+ doorcam:
+ name: doorcam
+ alexa_description: Haustürkamera
+ alexa_name: Doorcam
+ alexa_device: doorcam
+ alexa_icon: CAMERA
+ alexa_actions: InitializeCameraStreams
+ alexa_camera_imageUri: 'http://192.168.178.9/snapshot/view0.jpg'
+ alexa_csc_uri: '{"Stream1":"192.168.178.9","Stream2":"192.168.178./2","Stream3:...."}'
+ alexa_auth_cred: 'USER:PWD'
+ alexa_stream_1: '{
+ "protocols":["RTSP"],
+ "resolutions":[{"width":1920,"height":1080}],
+ "authorizationTypes":["BASIC"],
+ "videoCodecs":["H264"],
+ "audioCodecs":["G711"]
+ }'
+ alexa_stream_2: '{
+ "protocols":["RTSP"],
+ "resolutions":[{"width":1920,"height":1080}],
+ "authorizationTypes":["NONE"],
+ "videoCodecs":["H264"],
+ "audioCodecs":["AAC"]
+ }'
+ alexa_stream_3: '{.......
+ }'
+ alexa_csc_proxy_uri: alexatestcam.ddns.de:443
+ alexa_proxy_credentials: user:pwd
+
+
+Als Action ist fix "alexa_actions: InitializeCameraStreams" anzugeben.
+Als Icon empfiehlt sich "alexa_icon: CAMERA".
+
+Es können aktuell bis zu drei Streams pro Kamera definiert werden. In "alexa_csc_uri" werden die URL´s der Streams definiert. Die Items "alexa_csc_uri" und "alexa_stream_X" werden beim Laden der Items als Json geladen.
+
+
+!! Unbedingt auf korrekte Struktur im Json-Format achten !!
+
+
+Die Kamera URL´s müssen in der gleichen Reihenfolge zu den Streams (alexa_stream_X) sein.
+
+
+Mit dem Eintrag "alexa_auth_cred" werden User und Passwort für den Zugriff auf die Kamera hinterlegt.
+
+Mit dem Eintrag "alexa_camera_imageUri" wird die URL für den eventuell Snapshot der Kamera definiert.
+
+Für die Streams werden folgende Einstellungen untersützt:
+
+
+protocols : RTSP
+resolutions : alle die von der Kamera unterstützt werden
+authorizationTypes : "BASIC", "DIGEST" or "NONE"
+videoCodecs : "H264", "MPEG2", "MJPEG", oder "JPG"
+audioCodecs : "G711", "AAC", or "NONE"
+
+
+!! alle Einstellungen sind als Array definiert [] !!
+## Alexa-SceneController
+
+Alexa aktiviere Szene kommen
+
+Mit dem Scene-Controller können Szenen aufgerufen werden.
+Folgende Parameter sind anzugeben:
+
+alexa_actions = "Activate"
+alexa_item_turn_on = 3
+alexa_icon = "SCENE_TRIGGER"
+
+
+Das "alexa_item_turn_on" ist die Nummer der Szene die aufgerufen werden soll.
+
+
+Beispiel Konfiguration :
+
+scene:
+ type: num
+ name: scene_kommen
+ alexa_description : "Szene Kommen"
+ alexa_name : "Szene Kommen"
+ alexa_device : Szene_Kommen
+ alexa_icon : "SCENE_TRIGGER"
+ alexa_item_turn_on : 3
+ alexa_actions : "Activate"
+ alexa_retrievable : false
+
+
+## ContactSensor Interface
+
+Alexa ist das Küchenfenster geschlossen ?
+Alexa ist das Küchenfenster geöffnet ?
+
+Folgende Parameter sind anzugeben:
+
+alexa_actions = "ReportContactState"
+alexa_icon = "CONTACT_SENSOR"
+
+
+Beispiel Konfiguration :
+
+fensterkontakt:
+ type: bool
+ name: kuechenfenster
+ alexa_description: Küchenfenster
+ alexa_name: kuechenfenster
+ alexa_device: kuechenfenster
+ alexa_icon: CONTACT_SENSOR
+ alexa_actions: ReportContactState
+ alexa_retrievable: 'True'
+
+
+
+## ColorController
+
+Alexa, setze Licht Speicher auf rot
+
+Folgende Paramter sind anzugeben :
+
+
+alexa_actions = "SetColor"
+alexa_color_value_type = RGB
+alexa_icon = "LIGHT"
+
+
+
+**"alexa_color_value_type" = RGB oder HSB**
+
+Der Parameter "alexa_color_value_type" gibt an ob die Werte von Alexa
+als RGB-Werte [120, 40, 65] oder als HSB-Werte[350.5, 0.7138, 0.6524] im list-Objekt an das Item übergeben werden.
+Default-Wert ist : RGB
+
+Die Helligkeit wird bei Farbwechsel unverändert beibehalten. Bei HSB-Werten wird der ursprüngliche Wert gepuffert und als aktueller Wert wieder an das Item übergeben.
+
+Zum Wechseln der Helligkeit einen BrightnessController hinzufügen
+Details siehe [BrightnessController](#BrightnessController)
+Beispiel Konfiguration (wobei R_Wert, G_Wert, B_Wert für die Gruppenadressen stehen :
+
+%YAML 1.1
+---
+Speicher:
+ Lampe_Speicher:
+ alexa_description: Licht Speicher
+ alexa_device: DALI_RGB_Speicher
+ alexa_name: Licht Speicher
+ alexa_icon: LIGHT
+ Dimmwert:
+ type: num
+ alexa_device: DALI_RGB_Speicher
+ alexa_actions: AdjustBrightness SetBrightness
+ alexa_retrievable: True
+ alexa_item_range: 0-255
+ Farbwert_RGB:
+ type: list
+ alexa_device: DALI_RGB_Speicher
+ alexa_color_value_type: RGB
+ alexa_actions: SetColor
+ alexa_retrievable: True
+ alexa_color_value_type: RGB
+ on_change:
+ - R_WERT = list[0]
+ - G_WERT = list[1]
+ - B_WERT = list[2]
+
+
+
+## RangeController
+
+
+Folgende Paramter sind anzugeben :
+
+
+alexa_actions: SetRangeValue AdjustRangeValue
+alexa_range_delta: 20
+alexa_item_range: 0-255
+
+
+ergänzt um das entsprechende Categorie-Icon
+
+
+alexa_icon: EXTERIOR_BLIND
+
+
+oder
+
+
+alexa_icon: INTERIOR_BLIND
+
+
+Der RangeController kann mit dem Percentage-Controller kombiniert werden
+
+
+alexa_actions: SetRangeValue AdjustRangeValue SetPercentage
+alexa_range_delta: 20
+alexa_item_range: 0-255
+
+
+
+## ColorTemperaturController
+
+Es müssen die Parameter für den einstellbaren Weiss-Bereich unter "alexa_item_range" in Kelvin von/bis angegeben werden.
+Da die Geräte der verschiedenen Hersteller unterschiedliche Weißbereiche abdecken ist wird dieser Wert benötigt.
+Falls ein Weißwert angefordert wird den das jeweilige Gerät nicht darstellen kann wird auf den Minimum bzw. den Maximumwert gestellt.
+
+Als Alexa-Actions müssen SetColorTemperature/IncreaseColorTemperature/DecreaseColorTemperature angegeben werden.
+Als Rückgabewert wird das entsprechende Item vom plugin auf den Wert von 0 (warmweiss) bis 255 (kaltweiss) gesetzt.
+
+Hinweis : Alexa unterstützt 1.000 Kelvin - 10.000 Kelvin
+
+
+alexa_item_range: 3000-6500
+alexa_actions: SetColorTemperature IncreaseColorTemperature DecreaseColorTemperature
+
+
+## PlaybackController
+
+Eingebaut um fahrende Rolladen zu stoppen.
+
+#### Alexa, stoppe den Rolladen Büro
+
+Das funktioniert nur, wenn beim Rolladen/Jalousie kein TurnOn/TurnOff definiert sind. Die Rolladen müssen mittels "AdjustPercentage" und "SetPercentage" angesteuert werden. Dann kann mit dem "Stop" Befehl der Rolladen angehalten werden.
+
+Die Action lautet "Stop". Es wird an dieser Stelle der Alexa.PlaybackController zweck entfremded. Dieser Controller hat eine "Stop" Funktion implementiert welche hier genutzt wird.
+Beim ausführen des Befehls wird eine "1" an das Item übergeben. Das Item muss der Stopbefehl für den Rolladen sein. enforce_update muss auf True stehen.
+
+Alle Actions senden jeweils ein "True" bzw. "EIN" bzw. "1"
+
+implementierte Funktionen:
+
+alexa_actions: Stop / Play / Pause / FastForward / Next / Previous / Rewind / StartOver
+
+
+# Web-Interface
+
+Das Plugin bietet ein Web-Interface an.
+
+Auf der ersten Seite werden alle Alexa-Geräte, die definierten Actions sowie die jeweiligen Aliase angezeigt. Actions in Payload-Version 3 werden grün angezeigt. Actions in Payload-Version 2 werden in rot angezeigt.
+Eine Zusammenfassung wird oben rechts dargestellt. Durch anklicken eine Zeile kann ein Alexa-Geräte für die Testfunktionen auf Seite 3 des Web-Interfaces auswewählt werden
+
+
+Auf der Zweiten Seite wird ein Kommunikationsprotokoll zur Alexa-Cloud angezeigt.
+
+
+Auf Seite drei können "Directiven" ähnlich wie in der Lambda-Test-Funktion der Amazon-Cloud ausgeführt werden. Der jeweilige Endpunkt ist auf Seite 1 duch anklicken zu wählen. Die Kommunikation wird auf Seite 2 protokolliert.
+So könnne einzelne Geräte und "Actions" getestet werden.
+
+
+
+Auf Seite 4 kann interaktiv ein YAML-Eintrag für einen Alexa-Kamera erzeugt werden. Der fertige YAML-Eintrag wird unten erzeugt und kann via Cut & Paste in die Item-Definition von shNG übernommen werden.
+
+
+
+
+# Beispiele
+
+## Der fast perfekte Rolladen
+
+Mit diesen Einstellungen kann ein Rolladen wie folgt gesteuert werden :
+
+Alexa,
+
+mache den Rolladen hoch
+
+mache den Rolladen runter
+
+öffne den Rolladen im Büro
+
+mache den Rolladen im Büro auf
+
+schliesse den Rolladen im Büro
+
+mache den Rolladen im Büro zu
+
+fahre Rolladen Büro auf siebzig Prozent
+
+stoppe Rolladen Büro
+
+
+
+Es wird zum einen der RangeController mit erweiterten Ausdrücken verwendet zum anderen wird
+der PlaybackController zweckentfremdet für das Stop-Signal verwendet.
+
+### !! Wichtig !!
+
+
+Die erweiterten Ausdrücke (öffnen/schliessen - hoch/runter) werden durch das Plugin automatisch
+beim RangeController eingebunden wenn als Alexa-Icon "EXTERIOR_BLIND" oder "INTERIOR_BLIND" parametriert werden.
+
+Beim Stop des Rolladen-Items muss "alexa_actions: Stop" angegeben werden
+Um das Item automatisch zurückzusetzen empfiehlt sich der autotimer-Eintrag.
+
+
+Bei der Positionierung des Rolladen muss "alexa_range_delta: xx" angegeben werden.
+"xx" ist hier der Wert der beim Kommando hoch/runter gesendet wird.
+Bei xx=20 und "Rolladen runter" würde der Rolladen 20 Prozent nach unten fahren.
+Bei xx=20 und "Rolladen hoch" würde der Rolladen 20 Prozent nach oben fahren.
+Wenn der Rolladen bei "hoch/runter" komplett fahren soll kann hier auch 100 angegeben werden.
+
+Für die Positionierung ist "alexa_item_range: 0-255" anzugeben.
+
+
+ Rolladen:
+ alexa_name: Rollladen Büro
+ alexa_device: rolladen_buero
+ alexa_description: Rollladen Büro
+ alexa_icon: EXTERIOR_BLIND
+ alexa_proactivelyReported: 'False'
+ alexa_retrievable: 'True'
+
+ move:
+ type: num
+ visu_acl: rw
+ knx_dpt: 1
+ knx_send: 3/2/23
+ enforce_updates: 'true'
+
+ stop:
+ type: num
+ visu_acl: rw
+ enforce_updates: 'true'
+ knx_dpt: 1
+ knx_send: 3/1/23
+ alexa_device: rolladen_buero
+ alexa_actions: Stop
+ alexa_retrievable: 'False'
+ alexa_proactivelyReported: 'False'
+ autotimer: 1 = 0
+
+ pos:
+ type: num
+ visu_acl: rw
+ knx_dpt: 5
+ knx_listen: 3/3/23
+ knx_send: 3/4/23
+ knx_init: 3/3/23
+ enforce_updates: 'true'
+ alexa_actions: SetRangeValue AdjustRangeValue
+ alexa_retrievable: 'True'
+ alexa_range_delta: 20
+ alexa_item_range: 0-255
+
+
+
+## Items in Abhängikeit des letzten benutzten Echos-Devices schalten
+
+Wenn das AlexaRc4shNG-Plugin aktiviert ist kann über eine Logik das letzte Echo-Gerät welches einen Sprachbefehl bekommen hat ermittelt werden und abhängig davon können Items geschalten werden. So kann z.b. eine raumabhängige Steuerung für das Licht und Rolladen erstellt werden.
+
+Es wird ein Item für Licht pauschal erstellt :
+```
+ Licht_pauschal:
+ alexa_name: Licht
+ alexa_device: Licht_pauschal
+ alexa_description: Licht Pauschal
+ alexa_icon: OTHER
+ alexa_actions: TurnOn TurnOff
+ alexa_proactivelyReported: 'False'
+ type: num
+ visu_acl: rw
+ enforce_updates: 'true'
+```
+
+eine entsprechende Logik welche durch das item "Licht_pauschal" getriggert wird schaltet dann die entsprechenden Items.
+```
+#!/usr/bin/env python3
+#last_alexa.py
+
+myAlexa = sh.alexarc4shng.get_last_alexa()
+if myAlexa != None:
+ triggeredItem=trigger['source']
+ triggerValue = trigger['value']
+ if triggeredItem == "test.testzimmer.Licht_pauschal":
+ if myAlexa == "ShowKueche":
+ sh.EG.Kueche.Spots_Sued(triggerValue)
+ if myAlexa == "Wohnzimmer":
+ sh.OG.Wohnzimmer.Spots_Nord(triggerValue)
+ sh.OG.Wohnzimmer.Spots_Sued(triggerValue)
+
+```
diff --git a/alexarc4shng/README.md b/alexarc4shng/README.not_convertable.md.off
similarity index 100%
rename from alexarc4shng/README.md
rename to alexarc4shng/README.not_convertable.md.off
diff --git a/alexarc4shng/plugin.yaml b/alexarc4shng/plugin.yaml
index 1e00fb5f5..ec99a371b 100755
--- a/alexarc4shng/plugin.yaml
+++ b/alexarc4shng/plugin.yaml
@@ -36,8 +36,18 @@ parameters:
type: str
default: ''
description:
- de: 'Ein Item welches verwendet wird um die Freigabe für die Kommunikation zu erteilen (USZU)'
- en: 'An Item to give the plugin permission to remote control the echo-devices (USZU)'
+ de: 'Ein Item welches verwendet wird um die Freigabe für die Kommunikation zu erteilen (z.B. via UZSU)'
+ en: 'An Item to give the plugin permission to remote control the echo-devices (e.g. via UZSU)'
+ description_long:
+ de: 'Item, das beispielsweise durch eine Zeitschaltuhr oder etwas anderem geschaltet wird,
+ um die Kommunikation mit Alexa-Amazon-Geräten zu ermöglichen.\n
+ Ist der Wert leer oder nicht angegeben, ist die Kommunikation jederzeit rund um die Uhr aktiviert.\n
+ Dieses Item wird nur während update_item in smarthomeNG überprüft. Wenn die API direkt von einer Logik oder
+ über die Benutzeroberfläche verwendet wird, wird das Item nicht überprüft.'
+ en: 'Item controlled by UZSU or something else which enables the communication to Alexa-Amazon-devices.\n
+ If left blank/not configured the communication is enabled all the time 24/7.\n
+ This item is only checked during update_item in smarthomeNG.
+ If you use the API directly from a logic or from the Webinterface the item will not be checked.'
alexa_credentials:
type: str
@@ -45,6 +55,13 @@ parameters:
description:
de: 'Zugangsdaten für das Amazon-Alexa-Web-Site :, base64 encodiert'
en: 'credentials for the amazon-alexa-website :, base64 encoded'
+ description_long:
+ de: 'Die Zugangsdaten können entweder über der Web Interface kodiert werden oder direkt über eine Python-Konsole mit den zwei Zeilen\n
+ import base64\n
+ base64.b64encode("user.test@gmail.com:your_pwd".encode("utf-8"))'
+ en: 'The access credentials can be encoded either through the web interface or directly via a Python console using the following two lines.\n
+ import base64\n
+ base64.b64encode("user.test@gmail.com:your_pwd".encode("utf-8"))'
login_update_cycle:
type: num
@@ -80,8 +97,8 @@ plugin_functions:
send_cmd:
type: str
description:
- de: "Sendet einen Befehl an Alexa."
- en: "Sends a command to Alexa."
+ de: "Sendet einen Befehl an Alexa. Es können auch Platzhalter genutzt werden. Das Resultat wird der HTTP Status des Requests als String sein."
+ en: "Sends a command to Alexa. Placeholders can be used. The result will be the HTTP-Status of the request as string (str)"
parameters:
dvName:
type: str
@@ -115,10 +132,9 @@ plugin_functions:
- 'TO_DO'
-
+
get_last_alexa:
type: str
description:
de: "Liefert die Geräte-ID des zuletzt verwendeten Alexa-Gerätes zurück"
en: "delivers the Device-ID of the last used Alexa-Device"
-
diff --git a/alexarc4shng/user_doc.rst b/alexarc4shng/user_doc.rst
index 73f3f6089..482055d9a 100755
--- a/alexarc4shng/user_doc.rst
+++ b/alexarc4shng/user_doc.rst
@@ -9,13 +9,50 @@ alexarc4shng
.. image:: webif/static/img/plugin_logo.png
:alt: plugin logo
- :width: 300px
- :height: 300px
+ :width: 650px
+ :height: 350px
:scale: 50 %
:align: left
-Plugin zur Steuerung von Amazon Echo Geräten Zugriff via Web-Browser API und Cookie.
+Das Plugin bietet die Möglichkeit, ein Alexa-Echo-Gerät über smartHomeNG fernzusteuern.
+So ist es möglich, einen TuneIn-Radio-Kanal einzuschalten, Nachrichten über Text2Speech zu senden,
+wenn ein Ereignis auf dem knx-Bus oder auf der Visu eintritt, etc.
+
+Voraussetzungen
+===============
+
+* Python requests
+* ein gültiges Cookie aus einer Sitzung auf einer alexa.amazon-Webseite
+* "base64"-codierte Anmeldedaten in der etc/plugin.yaml Datei
+
+Cookie
+------
+
+Erste Möglichkeit - ohne Anmeldedaten:
+
+Es gibt Plugins für die meisten gängigen Browser. Nach der Installation des Plugins müssen Sie sich in Ihrer alexa.amazon-Webkonsole anmelden.
+Exportieren Sie nun das Cookie mithilfe des Plugins. Öffnen Sie die Cookie-Datei mit einem Texteditor,
+markieren Sie alles und kopieren Sie es in die Zwischenablage. Gehen Sie zur Web-Benutzeroberfläche des Plugins
+und fügen Sie den Inhalt der Cookie-Datei in das Textfeld auf dem Tab "Cookie-Handling" ein. Speichern Sie das Cookie.
+Wenn das Cookie erfolgreich gespeichert wurde, finden Sie Ihre Echo-Geräte im Tab mit den Alexa-Geräten.
+
+Zweite Möglichkeit - mit Anmeldedaten:
+
+Wenn das Plugin gestartet wird und Anmeldedaten in der plugin.yaml gefunden werden, überprüft das Plugin,
+ob die Informationen in der Cookie-Datei noch gültig sind. Falls nicht, versucht das Plugin, sich selbst mit den
+Anmeldedaten anzumelden und speichert die Informationen in der Cookie-Datei. Das Cookie wird im unter "login_update_cycle"
+in der plugin.yaml angegebenen Zyklus aktualisiert.
+
+Anmeldedaten
+------------
+
+Nutzername und Passwort können im Web Interface oder via Python entsprechend kodiert werden
+
+.. code-block:: python
+
+ import base64
+ base64.b64encode("user.test@gmail.com:your_pwd".encode("utf-8"))
Konfiguration
@@ -23,49 +60,299 @@ Konfiguration
Die Informationen zur Konfiguration des Plugins sind unter :doc:`/plugins_doc/config/alexarc4shng` beschrieben.
+plugin.yaml
+-----------
-Web Interface
-=============
+.. code-block:: yaml
-Das AlexaRc4shNG Plugin verfügt über ein Webinterface.Hier werden die Zugangsdaten zur Amazon-Web-Api (Cookie) gepflegt.
-Es können neue Kommandos erstellt werden
+ AlexaRc4shNG:
+ plugin_name: alexarc4shng
+ cookiefile: /usr/local/smarthome/plugins/alexarc4shng/cookies.txt
+ host: alexa.amazon.de
+ item_2_enable_alexa_rc: -
+ alexa_credentials: : (kodiert!)
+ login_update_cycle: 432000
+ mfa_secret:
-.. important::
+items.yaml
+----------
- Das Webinterface des Plugins kann mit SmartHomeNG v1.5.2 und davor **nicht** genutzt werden.
- Es wird dann nicht geladen. Diese Einschränkung gilt nur für das Webinterface. Ansonsten gilt
- für das Plugin die in den Metadaten angegebene minimale SmartHomeNG Version.
+Sie können bis zu 99 Befehle pro shng-Element angeben.
+Das Plugin scannt die item.yaml während der Initialisierung nach Befehlen von 01 bis 99.
+.. important::
+ Bitte starten Sie jedes Mal mit 01 pro Item, also alexa_cmd_01. Die Befehlsnummern müssen fortlaufend sein, vergessen Sie keine.
+ Der Scan der Befehle endet, wenn kein Befehl mit der nächsten Nummer gefunden wird.**
+
+Ein Command ist wie folgt aufgebaut:
-Aufruf des Webinterfaces
-------------------------
+.. code-block:: yaml
-Das Plugin kann aus dem backend aufgerufen werden. Dazu auf der Seite Plugins in der entsprechenden
-Zeile das Icon in der Spalte **Web Interface** anklicken.
+ alexa_cmd_01: comparison:EchoDevice:Commandlet:Value_to_Send
-Außerdem kann das Webinterface direkt über ``http://smarthome.local:8383/plugins/alexarc4shng`` aufgerufen werden.
+Unterstützte Vergleiche (comparison):
+- "True" oder "False" für boolsche Werte
+- für numerische Werte "<=", ">=", "=", "<", ">"
Beispiele
----------
+=========
+
+Radiostation
+------------
+
+.. code-block:: yaml
+
+ alexa_cmd_01: True:EchoDotKueche:StartTuneInStation:s96141
+
+- Value = True bedeutet, dass das item() "ON" wird
+- EchodotKueche = Gerätename, an das der Befehl gesendet werden soll
+- StartTuneInStation = Name des Befehls
+- s96141 = Wert der Radiostation als guideID, Stationsnamen werden nicht unterstützt (hier S96141 = baden.fm)
+
+Um die Stations-ID zu finden, suchen Sie nach Ihrer Station auf TuneIn.com. Greifen Sie auf Ihre Seite zu und
+verwenden Sie die letzten Ziffern der resultierenden URL für die ID. Zum Beispiel:
+Wenn Ihre TuneIn.com-URL `http://tunein.com/radio/tuneinstation-s######/` ist, dann wäre Ihre Stations-ID `"s######"`.
+
+Text senden
+-----------
+
+Beispiel zum Senden von Text mit dem im Wert enthaltenen Element basierend auf einem Wert unter 20 Grad:
+
+.. code-block:: yaml
+
+ alexa_cmd_01: <20.0:EchoDotKueche:Text2Speech:Die Temperatur in der Küche ist niedriger als 20 Grad. Die Temperatur ist jetzt #test.testzimmer.temperature.actual/#
+
+- Value = <20.0 - Befehl senden, wenn der Wert des Elements kleiner als 20.0 wird
+- EchodotKueche = Gerätename, an das der Befehl gesendet werden soll
+- Text2Speech = Name des Befehls
+- Value_to_Send = Die Temperatur in der Küche ist niedriger als 20 Grad. Die Temperatur ist jetzt #test.testzimmer.temperature.actual/#
+- #test.testzimmer.temperature.actual/# = Elementpfad des einzufügenden Werts
+
+Beispiel Itemdefinition
+-----------------------
+
+.. code-block:: yaml
+
+ OG:
+ Buero:
+ name: Buero
+ Licht:
+ type: bool
+ alexa_name: Licht Büro
+ alexa_description: Licht Büro
+ alexa_actions: Einschalten Ausschalten
+ alexa_icon: LICHT
+ alexa_cmd_01: True:EchoDotKueche:StartTuneInStation:s96141
+ alexa_cmd_02: True:EchoDotKueche:Text2Speech:Hallo das Licht im Büro ist eingeschaltet
+ alexa_cmd_03: False:EchoDotKueche:Text2Speech:Hallo das Licht im Büro ist aus
+ alexa_cmd_04: 'False:EchoDotKueche:Pause: '
+ visu_acl: rw
+ knx_dpt: 1
+ knx_listen: 1/1/105
+ knx_send: 1/1/105
+ enforce_updates: 'true'
+
+Logiken
+=======
+
+Beispiellogik, um Items mit Listeninformationen (Todo, Shopping) zu füllen
+
+.. code-block:: Python
+
+ from datetime import datetime
+
+ # get the Todo-List
+ myList=sh.AlexaRc4shNG.get_list('TO_DO')
+ for entry in myList:
+ if entry['completed'] == True:
+ entry['icon'] = 'control_clear'
+ else:
+ entry['icon'] = 'control_home'
+ entry['date'] = datetime.fromtimestamp((entry['updatedDateTime']/1000)).strftime("%d.%m.%Y, %H:%M:%S")
+
+ # Write list to Item - type should be list
+ sh.Alexa_Lists.list.todo(myList)
+
+ # get the shopping-List
+ myList=sh.AlexaRc4shNG.get_list('SHOPPING_LIST')
+ for entry in myList:
+ if entry['completed'] == True:
+ entry['icon'] = 'control_clear'
+ else:
+ entry['icon'] = 'jquery_shop'
+ entry['date'] = datetime.fromtimestamp((entry['updatedDateTime']/1000)).strftime("%d.%m.%Y, %H:%M:%S")
+
+ # Write list to Item - type should be list
+ sh.Alexa_Lists.list.shopping(myList)
+
+Einbinden in der smartVISU
+
+.. code-block:: HTML
+
+ status.activelist('','Alexa_Lists.list.todo','value','date','value','info')
+ status.activelist('','Alexa_Lists.list.shopping','value','date','value','info')
+
+.. image:: assets/Alexa_lists.jpg
+ :class: screenshot
+
+
+Platzhalter
+===========
-Folgende Informationen können im Webinterface angezeigt werden:
+- = Value to send as alpha
+- = Value to send as numeric
+- "#item.path/#" = item-path of the value that should be inserted into text or ssml
+- = SerialNo. of the device where the command should go to
+- = device family
+- = deviceType
+- = OwnerID of the device
-Oben rechts werden allgemeine Parameter zum Plugin angezeigt.
+.. important::
+
+ Platzhalter sind mit "<", ">", "#" und "/#" kennzuzeichnen!
+
+Kommandos erstellen
+===================
+
+Öffnen Sie das Web-Interface für Alexa auf Amazon. Wählen Sie die Seite aus, die Sie überwachen möchten.
+Bevor Sie auf den Befehl klicken, öffnen Sie den Debugger des Browsers (F12). Wählen Sie den Netzwerk-Tab aus.
+Wenn Sie auf den Befehl klicken, den Sie überwachen möchten, wird der Netzwerkverkehr im Debugger angezeigt.
+Hier erhalten Sie alle Informationen, die Sie benötigen.
+Normalerweise werden Informationen an Amazon gesendet. Konzentrieren Sie sich also auf die Post-Methoden.
+
+.. image:: assets/pic1.jpg
+ :alt: Browser Debugger
+ :class: screenshot
+
+Als Beispiel zum Überwachen der Station-ID einer TuneIn Radio-Station sehen Sie dies direkt im Kontext, wenn Sie Ihre Maus auf den Post-Befehl bewegen.
+Sie können die URL in die Zwischenablage kopieren und sie im Plugin verwenden.
+
+Sie können sie auch als cUrl kopieren, in einen Editor einfügen und die Payload im --data-Abschnitt des cUrl finden.
+
+.. image:: assets/pic2.jpg
+ :alt: Post Befehl
+ :class: screenshot
+
+Für einige Befehle müssen Sie die Payload kennen. Dies können Sie durch Überwachung der Daten herausfinden.
+Wählen Sie den Netzwerkbefehl aus. Wählen Sie dann den Tab mit den Headern aus. Unten finden Sie die Formulardaten.
+Sie können die Payload in die Zwischenablage kopieren und sie im Web Interface einfügen.
+
+.. image:: assets/pic3.jpg
+ :alt: Header
+ :class: screenshot
+
+Vergessen Sie nicht, die Werte für deviceOwnerCustomerId, customerID, serialNumber, family und Werte durch die Platzhalter zu ersetzen
+
+.. code-block:: text
+
+
+
+
+
+ (für Alpha-Werte)
+ (für numerische Werte)
+
+Web Interface
+=============
+
+Funktionen
+----------
+
+Auf dem Web-Interface können eigene Commandlets (Funktionen) definiert werden. Die folgenden Funktionen sind auf dem Web-Interface verfügbar:
+
+- Speichern einer Cookie-Datei, um Zugang zum Alexa-Web-Interface zu erhalten
+- Manuelles Login mit Ihren Zugangsdaten (gespeichert in der /etc/plugin.yaml)
+- Sehen Sie alle verfügbaren Geräte, wählen Sie eines aus um Test-Funktionen zu senden
+- Commandlets definieren - Sie können Commandlets laden, speichern, löschen, prüfen und testen
+- die Commandlets können mit einem Klick auf die Liste in das Webinterface geladen werden
+- die Json-Struktur kann auf dem WebInterface geprüft werden
-Im ersten Tab kann das Cookie File gespeichert werden - in die Textarea via Cut & Paste einfügen und speichern:
+In der API-URL und im JSON-Payload müssen die echten Werte aus dem Alexa-Webinterface durch Platzhalter ersetzt werden, siehe oben.
+Für Testfunktionen ist die Verwendung der Platzhalter nicht unbedingt notwendig.
+
+Cookies
+-------
+
+Im ersten Tab kann das Cookie File gespeichert werden.
.. image:: assets/webif1.jpg
:class: screenshot
+Exportieren Sie es mit einem Cookie.txt-Add-On Ihres Browsers. Kopieren Sie es in die Zwischenablage.
+Fügen Sie es in das Textfeld in der Web-Benutzeroberfläche ein und speichern Sie es ab.
+Nun werden die verfügbaren Geräte aus Ihrem Alexa-Konto erkannt und auf dem zweiten Tab angezeigt.
+
+Geräte
+------
+
Im zweiten Tab werden die verfügbaren Geräte angezeigt - Durch click auf ein Gerät wird dieses selektiert und steht für Tests zur Verfügung:
.. image:: assets/webif2.jpg
:class: screenshot
-Im dritten Tab werden die Commandlets verwaltet - mit Click auf die Liste der Commandlets wird dieses ins WebIF geladen:
+Kommandos verwalten
+-------------------
+
+Im dritten Tab werden die Commandlets verwaltet - mit Klick auf die Liste der Commandlets wird dieses ins WebIF geladen:
.. image:: assets/webif3.jpg
:class: screenshot
+Bestehende Commandlets
+^^^^^^^^^^^^^^^^^^^^^^
+
+- Play (Spielt das zuletzt pausierte Medium ab)
+- Pause (Pausiert das aktuelle Medium)
+- Text2Speech (Sendet einen Text an das Echo, das Echo spricht ihn)
+- StartTuneInStation (Startet eine TuneIn-Radiostation mit der angegebenen GuideID, siehe auch Beispiele weiter oben)
+- SSML (Sprachausgabe von Text mit Speech Synthesis Markup Language)
+- VolumeAdj (Regelt die Lautstärke während der Wiedergabe einiger Medien; funktioniert nicht über die Testfunktionen der Web-Benutzeroberfläche)
+- VolumeSet (Setzt die Lautstärke auf einen Wert zwischen 0 und 100 Prozent)
+
+Sie können Testwerte im Feld für die Werte eingeben. Drücken Sie "Test", und der Befehl wird an das Gerät gesendet.
+Sie erhalten den HTTP-Status der Anfrage zurück.
+
+.. important::
+
+ Für Tests sollten Sie die Payload nicht ändern, sondern einfach das Testwert-Feld verwenden.
+
+SSML Hinweise
+^^^^^^^^^^^^^
+
+Auszugebender Text ist in Tags einzubetten.
+
+Beispiel
+
+.. code-block::
+
+
+ I want to tell you a secret.I am not a real human..
+ Can you believe it?
+
+
+Außerdem können SpeechCons wie folgt genutzt werden.
+
+.. code-block::
+
+
+ Here is an example of a speechcon.
+ ach du liebe zeit..
+
+
+Weitere Infos: `SSML `_ und `SpeechCons `_
+
+Danksagung
+==========
+
+- `Alex von Loetzimmer `_
+- `Ingo `_
+- `Michael, OpenHAB2 `_
+- Jonofe vom Edomi-Forum
+
+Disclaimer
+==========
+TuneIn, Amazon Echo, Amazon Echo Spot, Amazon Echo Show, Amazon Music, Amazon Prime, Alexa und alle anderen Produkte und Unternehmen von Amazon,
+TuneIn und anderen sind Marken™ oder eingetragene® Marken ihrer jeweiligen Inhaber.
+Die Verwendung bedeutet nicht, dass eine Verbindung zu ihnen besteht oder dass sie sie unterstützen.
diff --git a/ebus/__init__.py b/ebus/__init__.py
index e4e4539b0..081300bf8 100755
--- a/ebus/__init__.py
+++ b/ebus/__init__.py
@@ -4,22 +4,23 @@
# Copyright 2018- Martin Sinn m.sinn@gmx.de
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
#########################################################################
-# This file is part of SmartHomeNG.py.
-# Visit: https://github.com/smarthomeNG/
-# https://knx-user-forum.de/forum/supportforen/smarthome-py
+# This file is part of SmartHomeNG.
+# https://www.smarthomeNG.de
+# https://knx-user-forum.de/forum/supportforen/smarthome-py
#
-# SmartHomeNG.py is free software: you can redistribute it and/or modify
+# SmartHomeNG is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
-# SmartHomeNG.py is distributed in the hope that it will be useful,
+# SmartHomeNG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with SmartHomeNG.py. If not, see .
+# along with SmartHomeNG. If not, see .
+#
#########################################################################
import logging
@@ -36,7 +37,7 @@ class eBus(SmartPlugin):
the update functions for the items
"""
- PLUGIN_VERSION = '1.5.1'
+ PLUGIN_VERSION = '1.6.0'
_items = []
@@ -58,6 +59,9 @@ def __init__(self, sh, *args, **kwargs):
returns the value in the datatype that is defined in the metadata.
"""
+ # Call init code of parent class (SmartPlugin)
+ super().__init__()
+
logger = logging.getLogger(__name__) # remove for shNG v1.6
self.host = self.get_parameter_value('host')
self.port = self.get_parameter_value('port')
@@ -95,7 +99,7 @@ def run(self):
"""
self.logger.debug("Run method called".format(self.get_fullname()))
self.alive = True
- self.scheduler_add('eBusd', self.refresh, prio=5, cycle=self._cycle, offset=2)
+ self.scheduler_add(self.get_fullname(), self.refresh, prio=5, cycle=self._cycle, offset=2)
def refresh(self):
@@ -113,7 +117,7 @@ def refresh(self):
value = self.request(request)
#if reading fails (i.e. at broadcast-commands) the value will not be updated
if 'command not found' not in str(value) and value is not None:
- item(value, 'eBus', 'refresh')
+ item(value, self.get_fullname(), 'refresh')
if not self.alive:
break
@@ -126,8 +130,13 @@ def request(self, request):
:type request: str
"""
if not self.connected:
- self.logger.info("eBusd not connected")
+ self.logger.info("eBusd not connected, try to connect")
+ self.connect()
+
+ if not self.connected:
+ self.logger.info("eBusd not connected, giving up")
return
+
self._lock.acquire()
try:
self._sock.send(request.encode())
@@ -165,7 +174,7 @@ def connect(self):
except Exception as e:
self._connection_attempts -= 1
if self._connection_attempts <= 0:
- self.logger.error('eBus: could not connect to ebusd at {0}:{1}: {2}'.format(self.host, self.port, e))
+ self.logger.error('eBus: could not connect to ebusd at {0}:{1}: {2}'.format(self.host, self.port, e))
self._connection_attempts = self._connection_errorlog
self._lock.release()
return
@@ -198,6 +207,7 @@ def stop(self):
"""
self.logger.debug("Stop method called".format(self.get_fullname()))
self.close()
+ self.scheduler_remove(self.get_fullname())
self.alive = False
@@ -209,7 +219,7 @@ def update_item(self, item, caller=None, source=None, dest=None):
:param source: if given it represents the source
:param dest: if given it represents the dest
"""
- if caller != 'eBus':
+ if caller != self.get_fullname():
value = str(int(item()))
cmd = item.conf['ebus_cmd']
request = "write -c " + cmd + " " + value + "\n"
diff --git a/ebus/README.md.old b/ebus/_pv_1_5_1/README.md
old mode 100755
new mode 100644
similarity index 100%
rename from ebus/README.md.old
rename to ebus/_pv_1_5_1/README.md
diff --git a/ebus/_pv_1_5_1/__init__.py b/ebus/_pv_1_5_1/__init__.py
new file mode 100644
index 000000000..e4e4539b0
--- /dev/null
+++ b/ebus/_pv_1_5_1/__init__.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+# Copyright 2018- Martin Sinn m.sinn@gmx.de
+# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
+#########################################################################
+# This file is part of SmartHomeNG.py.
+# Visit: https://github.com/smarthomeNG/
+# https://knx-user-forum.de/forum/supportforen/smarthome-py
+#
+# SmartHomeNG.py is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SmartHomeNG.py is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SmartHomeNG.py. If not, see .
+#########################################################################
+
+import logging
+import socket
+import threading
+import time
+
+from lib.model.smartplugin import *
+
+
+class eBus(SmartPlugin):
+ """
+ Main class of the Plugin. Does all plugin specific stuff and provides
+ the update functions for the items
+ """
+
+ PLUGIN_VERSION = '1.5.1'
+
+ _items = []
+
+ def __init__(self, sh, *args, **kwargs):
+ """
+ Initalizes the plugin. The parameters descriptions for this method are pulled from the entry in plugin.yaml.
+
+ :param sh: **Deprecated**: The instance of the smarthome object. For SmartHomeNG versions **beyond** 1.3: **Don't use it**!
+ :param *args: **Deprecated**: Old way of passing parameter values. For SmartHomeNG versions **beyond** 1.3: **Don't use it**!
+ :param **kwargs:**Deprecated**: Old way of passing parameter values. For SmartHomeNG versions **beyond** 1.3: **Don't use it**!
+
+ If you need the sh object at all, use the method self.get_sh() to get it. There should be almost no need for
+ a reference to the sh object any more.
+
+ The parameters *args and **kwargs are the old way of passing parameters. They are deprecated. They are imlemented
+ to support older plugins. Plugins for SmartHomeNG v1.4 and beyond should use the new way of getting parameter values:
+ use the SmartPlugin method get_parameter_value(parameter_name) instead. Anywhere within the Plugin you can get
+ the configured (and checked) value for a parameter by calling self.get_parameter_value(parameter_name). It
+ returns the value in the datatype that is defined in the metadata.
+ """
+
+ logger = logging.getLogger(__name__) # remove for shNG v1.6
+ self.host = self.get_parameter_value('host')
+ self.port = self.get_parameter_value('port')
+ self._cycle = self.get_parameter_value('cycle')
+
+ self._sock = False
+ self.connected = False
+ self._connection_attempts = 0
+ self._connection_errorlog = 60
+ self._lock = threading.Lock()
+ # self.refresh_cycle = self._cycle # not used
+
+
+ def parse_item(self, item):
+ """
+ Default plugin parse_item method. Is called when the plugin is initialized.
+ The plugin can, corresponding to its attribute keywords, decide what to do with
+ the item in future, like adding it to an internal array for future reference
+ :param item: The item to process.
+ :return: If the plugin needs to be informed of an items change you should return a call back function
+ like the function update_item down below. An example when this is needed is the knx plugin
+ where parse_item returns the update_item function when the attribute knx_send is found.
+ This means that when the items value is about to be updated, the call back function is called
+ with the item, caller, source and dest as arguments and in case of the knx plugin the value
+ can be sent to the knx with a knx write function within the knx plugin.
+ """
+ if 'ebus_type' in item.conf and 'ebus_cmd' in item.conf:
+ self._items.append(item)
+ return self.update_item
+
+
+ def run(self):
+ """
+ Run method for the plugin
+ """
+ self.logger.debug("Run method called".format(self.get_fullname()))
+ self.alive = True
+ self.scheduler_add('eBusd', self.refresh, prio=5, cycle=self._cycle, offset=2)
+
+
+ def refresh(self):
+ """
+ Refresh items with data from ebusd
+ """
+ for item in self._items:
+ time.sleep(1)
+ ebus_type = item.conf['ebus_type']
+ ebus_cmd = item.conf['ebus_cmd']
+ if ebus_cmd == "cycle":
+ request = ebus_type + " " + ebus_cmd + "\n" # build command
+ else:
+ request = "read" + " -c " + ebus_cmd + "\n" # build command
+ value = self.request(request)
+ #if reading fails (i.e. at broadcast-commands) the value will not be updated
+ if 'command not found' not in str(value) and value is not None:
+ item(value, 'eBus', 'refresh')
+ if not self.alive:
+ break
+
+
+ def request(self, request):
+ """
+ send request to ebusd deamon
+
+ :param request: Command to send to ebusd
+ :type request: str
+ """
+ if not self.connected:
+ self.logger.info("eBusd not connected")
+ return
+ self._lock.acquire()
+ try:
+ self._sock.send(request.encode())
+ self.logger.debug("REQUEST: {0}".format(request))
+ except Exception as e:
+ self._lock.release()
+ self.close()
+ self.logger.warning("error sending request: {0} => {1}".format(request, e))
+ return
+ try:
+ answer = self._sock.recv(256).decode()[:-2]
+ self.logger.debug("ANSWER: {0}".format(answer))
+ except socket.timeout:
+ self._lock.release()
+ self.logger.warning("error receiving answer: timeout")
+ return
+ except Exception as e:
+ self._lock.release()
+ self.close()
+ self.logger.warning("error receiving answer: {0}".format(e))
+ return
+ self._lock.release()
+ return answer
+
+
+ def connect(self):
+ """
+ Open socket connection to ebusd deamon
+ """
+ self._lock.acquire()
+ try:
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.settimeout(2)
+ self._sock.connect((self.host, self.port))
+ except Exception as e:
+ self._connection_attempts -= 1
+ if self._connection_attempts <= 0:
+ self.logger.error('eBus: could not connect to ebusd at {0}:{1}: {2}'.format(self.host, self.port, e))
+ self._connection_attempts = self._connection_errorlog
+ self._lock.release()
+ return
+ self.logger.info('Connected to {0}:{1}'.format(self.host, self.port))
+ self.connected = True
+ self._connection_attempts = 0
+ self._lock.release()
+
+
+ def close(self):
+ """
+ Close socket connection
+ """
+ self.connected = False
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+ try:
+ self._sock.close()
+ self._sock = False
+ self.logger.info('Connection closed to {0}:{1}'.format(self.host, self.port))
+ except:
+ pass
+
+
+ def stop(self):
+ """
+ Stop method for the plugin
+ """
+ self.logger.debug("Stop method called".format(self.get_fullname()))
+ self.close()
+ self.alive = False
+
+
+ def update_item(self, item, caller=None, source=None, dest=None):
+ """
+ Write items values
+ :param item: item to be updated towards the plugin
+ :param caller: if given it represents the callers name
+ :param source: if given it represents the source
+ :param dest: if given it represents the dest
+ """
+ if caller != 'eBus':
+ value = str(int(item()))
+ cmd = item.conf['ebus_cmd']
+ request = "write -c " + cmd + " " + value + "\n"
+ set_answer = self.request(request)
+ #just check if set was no broadcast-message
+ if 'broadcast done' not in set_answer:
+ request = "read -c " + cmd + "\n"
+ answer = self.request(request)
+ #transfer value and answer to float for better comparsion
+ if float(answer) != float(value) or answer is None:
+ self.logger.warning("Failed to set parameter: value: {0} cmd: {1} answer {2}".format(value, request, answer))
diff --git a/ebus/_pv_1_5_1/plugin.yaml b/ebus/_pv_1_5_1/plugin.yaml
new file mode 100644
index 000000000..dbe420e78
--- /dev/null
+++ b/ebus/_pv_1_5_1/plugin.yaml
@@ -0,0 +1,79 @@
+# Metadata for the classic-plugin
+plugin:
+ # Global plugin attributes
+ type: interface # plugin type (gateway, interface, protocol, system, web)
+ description:
+ de: 'Unterstützt eBus Heizungen (z.B. Vailant, Wolf, Kromschroeder) - Dieses Plugin verbindet sich zu einem ebusd Deamon (http://www.cometvisu.de/wiki/Ebusd), welcher mit einer eBus Heizung kommuniziert. Voraussetzungen: Ein ebusd Deamon läuft auf dem Netzwerk. (Anmerkung: Der ebusd benötigt ein ebus-Interface um mit ihm zu kommunizieren.'
+ en: 'Supports eBus heating systems (e.g. Vailant, Wolf, Kromschroeder) - The plugin connects to a ebusd damon (http://www.cometvisu.de/wiki/Ebusd) which is communicating with eBus heatings. Requirements: running ebusd deamon on the network (note: ebusd also requires an ebus-interface)'
+ maintainer: '? (msinn)'
+ tester: Sandman60
+ state: ready
+# keywords: kwd1 kwd2 # keywords, where applicable
+# documentation: https://github.com/smarthomeNG/plugins/blob/develop/mqtt/README.md # url of documentation (wiki) page
+# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
+
+# Following entries are for Smart-Plugins:
+ version: 1.5.1 # Plugin version
+ sh_minversion: 1.5 # minimum shNG version to use this plugin
+ #sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
+ multi_instance: False
+ restartable: unknown
+ classname: eBus # class containing the plugin
+
+
+parameters:
+ # Definition of parameters to be configured in etc/plugin.yaml
+
+ host:
+ type: ip
+ default: 127.0.0.1
+ description:
+ de: 'IP Adresse des ebusd Deamons'
+ en: 'ip address of ebusd deamon'
+
+ port:
+ type: int
+ valid_min: 0
+ default: 8888
+ description:
+ de: 'Port auf dem der ebusd Deamon lauscht'
+ en: 'port of ebusd deamon'
+
+ cycle:
+ type: int
+ valid_min: 0
+ default: 240
+ description:
+ de: 'Cycle Zeit zur Abfrage jedes Items'
+ en: 'cycle of each item'
+
+item_attributes:
+ # Definition of item attributes defined by this plugin
+
+ ebus_cmd:
+ type: str
+ description:
+ de: "ebus_cmd ist das Kommando, welches durch die Telnet Verbindung zum ebusd übertragen wird - z.B. 'cir2 heat_pump_curr', 'mv yield_sum' or 'short hw_load'"
+ en: "ebus_cmd is the command you use though the telnet-connection to ebusd - e.g. 'cir2 heat_pump_curr', 'mv yield_sum' or 'short hw_load'"
+
+ ebus_type:
+ type: str
+ default: 'get'
+ valid_list: ['get', 'set']
+ valid_list_description:
+ de: ['get', 'set']
+ en: ['Items will only be readable, i.e. sensors', 'Items are read/write. All "set"-items will be read cyclic too!']
+ description:
+ de: 'ebus_type legt fest, ob vom ebusd Deamon nur gelesen werden soll oder ob auch Daten an ebusd übertragen werden sollen.'
+ en: 'ebus_type determins, if data should only be read from the ebusd deamon or if data should be written to ebusd too.'
+
+
+item_structs: NONE
+ # Definition of item-structure templates for this plugin
+
+logic_parameters: NONE
+ # Definition of logic parameters defined by this plugin
+
+plugin_functions: NONE
+ # Definition of function interface of the plugin
+
diff --git a/ebus/plugin.yaml b/ebus/plugin.yaml
index 9519b28b0..9d25ddfe0 100755
--- a/ebus/plugin.yaml
+++ b/ebus/plugin.yaml
@@ -3,17 +3,24 @@ plugin:
# Global plugin attributes
type: interface # plugin type (gateway, interface, protocol, system, web)
description:
- de: 'Unterstützt eBus Heizungen (z.B. Vailant, Wolf, Kromschroeder) - Dieses Plugin verbindet sich zu einem ebusd Deamon (http://www.cometvisu.de/wiki/Ebusd), welcher mit einer eBus Heizung kommuniziert. Voraussetzungen: Ein ebusd Deamon läuft auf dem Netzwerk. (Anmerkung: Der ebusd benötigt ein ebus-Interface um mit ihm zu kommunizieren.'
- en: 'Supports eBus heating systems (e.g. Vailant, Wolf, Kromschroeder) - The plugin connects to a ebusd damon (http://www.cometvisu.de/wiki/Ebusd) which is communicating with eBus heatings. Requirements: running ebusd deamon on the network (note: ebusd also requires an ebus-interface)'
+ de: 'Unterstützt eBus Heizungen (z.B. Vailant, Wolf, Kromschroeder)
+ Dieses Plugin verbindet sich zu einem ebusd Deamon (https://ebusd.de/),
+ welcher mit einer eBus Heizung kommuniziert.
+ Voraussetzungen: Ein ebusd Deamon läuft auf dem Netzwerk.
+ Anmerkung: Der ebusd benötigt ein ebus-Interface um mit ihm zu kommunizieren.
+ '
+ en: 'Supports eBus heating systems (e.g. Vailant, Wolf, Kromschroeder)
+ The plugin connects to a ebusd damon (https://ebusd.de/)
+ which is communicating with eBus heatings.
+ Requirements: running ebusd deamon on the network (note: ebusd also requires an ebus-interface)
+ '
maintainer: '? (msinn)'
- tester: Sandman60
+ tester: android, z1marco
state: ready
-# keywords: kwd1 kwd2 # keywords, where applicable
-# documentation: https://github.com/smarthomeNG/plugins/blob/develop/mqtt/README.md # url of documentation (wiki) page
-# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
+ support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1925957
# Following entries are for Smart-Plugins:
- version: 1.5.0 # Plugin version
+ version: 1.6.0 # Plugin version
sh_minversion: 1.5 # minimum shNG version to use this plugin
#sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
multi_instance: False
diff --git a/ebus/user_doc.rst b/ebus/user_doc.rst
new file mode 100644
index 000000000..73ec8d1d1
--- /dev/null
+++ b/ebus/user_doc.rst
@@ -0,0 +1,99 @@
+.. index:: Plugins; ebus
+.. index:: ebus
+
+
+====
+ebus
+====
+
+Dieses Plugin verbindet sich zu einem ebus daemon und kann über diesen mit Geräten mit eBus Schnittstellen kommunizieren.
+
+Anforderungen
+=============
+
+Eine ebus Schnittstelle
+
+Notwendige Software
+-------------------
+
+Ein konfigurierter und funktionierender ebus Daemon der im Netzwerk erreichbar ist.
+
+Unterstützte Geräte
+-------------------
+
+Beispielsweise Geräte von Vaillant, Wolf, Kromschroeder und andere die über eine ebus Schnittstelle kommunizieren können.
+
+
+Konfiguration
+=============
+
+Die Plugin Parameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind
+unter :doc:`/plugins_doc/config/ebus` beschrieben.
+
+
+plugin.yaml
+-----------
+
+Zu den Informationen, welche Parameter in der ../etc/plugin.yaml konfiguriert werden können bzw. müssen, bitte
+bitte die Dokumentation :doc:`Dokumentation ` lesen, die aus
+den Metadaten der plugin.yaml erzeugt wurde (siehe oben).
+
+items.yaml
+----------
+
+Zu den Informationen, welche Attribute in der Item Konfiguration verwendet werden können bzw. müssen, bitte
+bitte die Dokumentation :doc:`Dokumentation ` lesen, die aus
+den Metadaten der plugin.yaml erzeugt wurde (siehe oben).
+
+logic.yaml
+----------
+
+Zu den Informationen, welche Konfigurationsmöglichkeiten für Logiken bestehen, bitte
+bitte die Dokumentation :doc:`Dokumentation ` lesen, die aus
+den Metadaten der plugin.yaml erzeugt wurde (siehe oben).
+
+Funktionen
+----------
+
+Zu den Informationen, welche Funktionen das Plugin bereitstellt (z.B. zur Nutzung in Logiken), bitte
+bitte die Dokumentation :doc:`Dokumentation ` lesen, die aus
+den Metadaten der plugin.yaml erzeugt wurde (siehe oben).
+
+Beispiele
+=========
+
+.. code:: yaml
+
+ ebus_geraet:
+
+ hk_pumpe_perc:
+ type: num
+ knx_dpt: 5
+ knx_send: 8/6/110
+ knx_reply: 8/6/110
+ ebus_cmd: cir2 heat_pump_curr
+ ebus_type: get
+ # akt. PWM-Wert Heizkreizpumpe
+
+ ernergie_summe:
+ type: num
+ knx_dpt: 12
+ knx_send: 8/6/22
+ knx_reply: 8/6/22
+ ebus_cmd: mv yield_sum
+ ebus_type: get
+ # Energieertrag
+
+ speicherladung:
+ type: bool
+ knx_dpt: 1
+ knx_listen: 8/7/1
+ ebus_cmd: short hw_load
+ ebus_type: set
+ # Quick - WW Speicherladung
+
+
+Web Interface
+=============
+
+Das Plugin hat derzeit kein Web Interface
\ No newline at end of file
diff --git a/enocean/user_doc.rst b/enocean/user_doc.rst
index 94b102c41..0d4b9c0e4 100755
--- a/enocean/user_doc.rst
+++ b/enocean/user_doc.rst
@@ -29,8 +29,8 @@ Es wird ein Hardware Radio Transceiver Modul benötigt, z.B.:
.. important::
- Der user `smarthome`, unter dem smarthomeNG ausgeführt wird, muss die nötigen Zugriffsrechte
- auf die Linux Gruppe `dialout` besitzen, damit die Hardware über Linux devices angesprochen und konfiguriert werden kann.
+ Der user `smarthome`, unter dem smarthomeNG ausgeführt wird, muss die nötigen Zugriffsrechte
+ auf die Linux Gruppe `dialout` besitzen, damit die Hardware über Linux devices angesprochen und konfiguriert werden kann.
Hierzu folgendes in der Linux Konsole ausführen:
@@ -73,8 +73,8 @@ Unter Linux wird empfohlen, das entsprechende Linux Uart device über eine Udev-
Die tx_id ist die Transmitter ID der Enocean Hardware und als achtstelliger Hexadezimalwert definiert. Die Angabe ist erstmal optional und muss nur zwingend angegeben werden,
falls Enocean Aktoren geschaltet werden sollen, d.h. das Plugin auch Sendebefehle absetzen soll.
-
-Werden mehrere Aktuatoren betrieben, sollte die Base-ID (**not Unique-ID or Chip-ID**) der Enocean Hardware als Transmitter ID angegeben werden.
+
+Werden mehrere Aktuatoren betrieben, sollte die Base-ID (**not Unique-ID or Chip-ID**) der Enocean Hardware als Transmitter ID angegeben werden.
Weitere Information zum Unterschied zwischen Base-ID und Chip-ID finden sich unter:
https://www.enocean.com/en/knowledge-base-doku/enoceansystemspecification%3Aissue%3Awhat_is_a_base_id/
@@ -91,14 +91,14 @@ Es gibt zwei verschiedene Wege, um die Base ID der Enocean Hardware auszulesen:
Zu a)
-
+
1. Konfiguriere das Enocean plugin in der plugin.yaml mit leerer tx_id (oder tx_id = 0).
2. Starte SmarthomeNG neu.
3. Öffne das Enocean webinterface des Plugins unter: http://smarthome.local:8383/enocean
-4. Ablesen der Transceiver's BaseID, welches auf der oberen recten Seite angezeigt wird.
+4. Ablesen der Transceiver's BaseID, welches auf der oberen recten Seite angezeigt wird.
5. Übernahme der im Webinterface angezeigten Base-ID in die plugin.yaml als Parameter `tx_id`.
@@ -134,7 +134,7 @@ Sendende Items, z.B. um Schaltaktoren zu schalten, benötigen weiterhin eine Tra
Enocean Equipment Profiles
==========================
-Das Encoean Protokoll basiert auf sogenannten EnOcean Equipment Profilen (EEPs). Sie definieren den Nachrichtentyp der vm EnOcean Gerät gesendet wird.
+Das Encoean Protokoll basiert auf sogenannten EnOcean Equipment Profilen (EEPs). Sie definieren den Nachrichtentyp der vm EnOcean Gerät gesendet wird.
EEPs sind standardisiert. Informationen dazu können unter http://www.enocean-alliance.org/eep/ gefunden werden.
@@ -162,7 +162,7 @@ Die folgenden Status EEPs werden vom Plugin aktuell unterstützt:
* F6_02_02 2-Button-Rocker
* F6_02_03 2-Button-Rocker, Status feedback from manual buttons on different actors, e.g. Eltako FT55, FSUD-230, FSVA-230V, FSB61NP-230V or Gira switches.
* F6_10_00 Mechanical Handle (value: 0(closed), 1(open), 2(tilted)
- * F6_0G_03 Feedback of shutter actor (Eltako FSB14, FSB61, FSB71 - actor for Shutter) if reaching the endposition and if motor is active
+ * F6_0G_03 Feedback of shutter actor (Eltako FSB14, FSB61, FSB71 - actor for Shutter) if reaching the endposition and if motor is active
Eine vollständige Liste aller EEPs mit detallierten Informationen findet sich unter [EnOcean Alliance](http://www.enocean-alliance.org/eep/)
@@ -185,12 +185,12 @@ A F6_02_03 bool letzter Status des linken Tas
B F6_02_03 bool letzter Status des linken Tasters
STATUS F6_10_0, D5_00_01 num Fenstergriff- Türstatus
- F6_0G_03
+ F6_0G_03
TMP A5_02_05 num Außentemperatur
BRI A5_06_01, A5_08_01 num Helligkeit
MOV A5_06_01, A5_08_01 bool Bewegung
-
+
STAT A5_11_04, D2_01_07 bool Schaltstatus
ILL A5_07_03 num Lux
@@ -237,7 +237,7 @@ Zeile das Icon in der Spalte **Web Interface** anklicken.
Außerdem kann das Webinterface direkt über http://smarthome.local:8383/enocean aufgerufen werden.
-Das Webinterface zeigt oben rechts allgemeine Informationen, wie
+Das Webinterface zeigt oben rechts allgemeine Informationen, wie
* die BaseID der verwendeten Hardware
* ob der Sendemodus aktiviert ist
@@ -252,12 +252,12 @@ Weiterhin können über Schaltflächen
Unter dem TAB `Übersicht` werden alle konfigurierten Enocean items angezeigt.
-Unter dem TAB 'Neu anlerenen' können neue Enocean Aktuatoren angelernt werden. Hierzu wird
+Unter dem TAB 'Neu anlerenen' können neue Enocean Aktuatoren angelernt werden. Hierzu wird
a) Der entsprechende Aktor/Stellglied in den Anlernmodus gebracht (siehe jeweilige Bedienungsanleitung)
b) Eine Transmit ID ausgewählt (TX ID). Enocean unterstützt bis zu 127 verschiedene IDs.
c) Als Hinweis bzw. Vorschlag wird die erste freie ID auf der linken Seite angezeigt.
-d) Der Aktortyp ausgewählt. Im Plugin wird anhand des Typs das Lerntelegram ausgewählt.
+d) Der Aktortyp ausgewählt. Im Plugin wird anhand des Typs das Lerntelegram ausgewählt.
e) Auf die Schaltfläche ``Anlernen`` klicken. Das Anlerntelegram wird gesendet und der Aktor sollte den Anlernvorgang quittieren (siehe jeweilige Bedienungsanleitung).
@@ -268,309 +268,309 @@ Beispiele für eine Item.yaml mit verschiedenen Enocean Sensoren und Aktoren:
.. code-block:: yaml
- EnOcean_Item:
- Outside_Temperature:
- type: num
- enocean_rx_id: 0180924D
- enocean_rx_eep: A5_02_05
- enocean_rx_key: TMP
-
- Door:
- enocean_rx_id: 01234567
- enocean_rx_eep: D5_00_01
- status:
- type: bool
- enocean_rx_key: STATUS
-
- FT55switch:
- enocean_rx_id: 012345AA
- enocean_rx_eep: F6_02_03
- up:
- type: bool
- enocean_rx_key: BO
- down:
- type: bool
- enocean_rx_key: BI
-
- Brightness_Sensor:
- name: brightness_sensor_east
- remark: Eltako FAH60
- type: num
- enocean_rx_id: 01A51DE6
- enocean_rx_eep: A5_06_01
- enocean_rx_key: BRI
- visu_acl: rw
- sqlite: 'yes'
-
- dimmer1:
- remark: Eltako FDG14 - Dimmer
- enocean_rx_id: 00112233
- enocean_rx_eep: A5_11_04
- light:
- type: bool
- enocean_rx_key: STAT
- enocean_tx_eep: A5_38_08_02
- enocean_tx_id_offset: 1
- level:
+ EnOcean_Item:
+ Outside_Temperature:
type: num
- enocean_rx_key: D
- enocean_tx_eep: A5_38_08_03
- enocean_tx_id_offset: 1
- ref_level: 80
- dim_speed: 100
- block_dim_value: 'False'
-
- handle:
- enocean_rx_id: 01234567
- enocean_rx_eep: F6_10_00
- status:
- type: num
- enocean_rx_key: STATUS
+ enocean_rx_id: 0180924D
+ enocean_rx_eep: A5_02_05
+ enocean_rx_key: TMP
- actor1:
- enocean_rx_id: FFAABBCC
- enocean_rx_eep: A5_12_01
- power:
+ Door:
+ enocean_rx_id: 01234567
+ enocean_rx_eep: D5_00_01
+ status:
+ type: bool
+ enocean_rx_key: STATUS
+
+ FT55switch:
+ enocean_rx_id: 012345AA
+ enocean_rx_eep: F6_02_03
+ up:
+ type: bool
+ enocean_rx_key: BO
+ down:
+ type: bool
+ enocean_rx_key: BI
+
+ Brightness_Sensor:
+ name: brightness_sensor_east
+ remark: Eltako FAH60
type: num
- enocean_rx_key: VALUE
-
- actor1B:
- remark: Eltako FSR61, FSR61NP, FSR61G, FSR61LN, FLC61NP - Switch for Ligths
- enocean_rx_id: 1A794D3
- enocean_rx_eep: F6_02_03
- light:
- type: bool
- enocean_tx_eep: A5_38_08_01
- enocean_tx_id_offset: 1
- enocean_rx_key: B
- block_switch: 'False'
- cache: 'True'
- enforce_updates: 'True'
+ enocean_rx_id: 01A51DE6
+ enocean_rx_eep: A5_06_01
+ enocean_rx_key: BRI
visu_acl: rw
+ sqlite: 'yes'
- actor_D2:
- remark: Actor with VLD Command
- enocean_rx_id: FFDB7381
- enocean_rx_eep: D2_01_07
- move:
+ dimmer1:
+ remark: Eltako FDG14 - Dimmer
+ enocean_rx_id: 00112233
+ enocean_rx_eep: A5_11_04
+ light:
type: bool
enocean_rx_key: STAT
- enocean_tx_eep: D2_01_07
+ enocean_tx_eep: A5_38_08_02
enocean_tx_id_offset: 1
- # pulsewith-attribute removed use autotimer functionality instead
- autotimer: 1 = 0
-
- actorD2_01_12:
- enocean_rx_id: 050A2FF4
- enocean_rx_eep: D2_01_12
- switch:
- cache: 'on'
- type: bool
- enocean_rx_key: STAT_A
- enocean_channel: A
- enocean_tx_eep: D2_01_12
- enocean_tx_id_offset: 2
-
- awning:
- name: Eltako FSB14, FSB61, FSB71
- remark: actor for Shutter
- type: str
- enocean_rx_id: 1A869C3
- enocean_rx_eep: F6_0G_03
- enocean_rx_key: STATUS
- move:
- type: num
- enocean_tx_eep: A5_3F_7F
- enocean_tx_id_offset: 0
- enocean_rx_key: B
- enocean_rtime: 60
- block_switch: 'False'
- enforce_updates: 'True'
- cache: 'True'
- visu_acl: rw
+ level:
+ type: num
+ enocean_rx_key: D
+ enocean_tx_eep: A5_38_08_03
+ enocean_tx_id_offset: 1
+ ref_level: 80
+ dim_speed: 100
+ block_dim_value: 'False'
+
+ handle:
+ enocean_rx_id: 01234567
+ enocean_rx_eep: F6_10_00
+ status:
+ type: num
+ enocean_rx_key: STATUS
+
+ actor1:
+ enocean_rx_id: FFAABBCC
+ enocean_rx_eep: A5_12_01
+ power:
+ type: num
+ enocean_rx_key: VALUE
+
+ actor1B:
+ remark: Eltako FSR61, FSR61NP, FSR61G, FSR61LN, FLC61NP - Switch for Ligths
+ enocean_rx_id: 1A794D3
+ enocean_rx_eep: F6_02_03
+ light:
+ type: bool
+ enocean_tx_eep: A5_38_08_01
+ enocean_tx_id_offset: 1
+ enocean_rx_key: B
+ block_switch: 'False'
+ cache: 'True'
+ enforce_updates: 'True'
+ visu_acl: rw
+
+ actor_D2:
+ remark: Actor with VLD Command
+ enocean_rx_id: FFDB7381
+ enocean_rx_eep: D2_01_07
+ move:
+ type: bool
+ enocean_rx_key: STAT
+ enocean_tx_eep: D2_01_07
+ enocean_tx_id_offset: 1
+ # pulsewith-attribute removed use autotimer functionality instead
+ autotimer: 1 = 0
+
+ actorD2_01_12:
+ enocean_rx_id: 050A2FF4
+ enocean_rx_eep: D2_01_12
+ switch:
+ cache: 'on'
+ type: bool
+ enocean_rx_key: STAT_A
+ enocean_channel: A
+ enocean_tx_eep: D2_01_12
+ enocean_tx_id_offset: 2
- rocker:
- enocean_rx_id: 0029894A
- enocean_rx_eep: F6_02_01
- short_800ms_directly_to_knx:
- type: bool
- enocean_rx_key: AI
- enocean_rocker_action: **toggle**
- enocean_rocker_sequence: released **within** 0.8
- knx_dpt: 1
- knx_send: 3/0/60
+ awning:
+ name: Eltako FSB14, FSB61, FSB71
+ remark: actor for Shutter
+ type: str
+ enocean_rx_id: 1A869C3
+ enocean_rx_eep: F6_0G_03
+ enocean_rx_key: STATUS
+ move:
+ type: num
+ enocean_tx_eep: A5_3F_7F
+ enocean_tx_id_offset: 0
+ enocean_rx_key: B
+ enocean_rtime: 60
+ block_switch: 'False'
+ enforce_updates: 'True'
+ cache: 'True'
+ visu_acl: rw
+
+ rocker:
+ enocean_rx_id: 0029894A
+ enocean_rx_eep: F6_02_01
+ short_800ms_directly_to_knx:
+ type: bool
+ enocean_rx_key: AI
+ enocean_rocker_action: '**toggle**'
+ enocean_rocker_sequence: 'released **within** 0.8'
+ knx_dpt: 1
+ knx_send: 3/0/60
- long_800ms_directly_to_knx:
- type: bool
- enocean_rx_key: AI
- enocean_rocker_action: toggle
- enocean_rocker_sequence: released **after** 0.8
- knx_dpt: 1
- knx_send: 3/0/61
+ long_800ms_directly_to_knx:
+ type: bool
+ enocean_rx_key: AI
+ enocean_rocker_action: toggle
+ enocean_rocker_sequence: 'released **after** 0.8'
+ knx_dpt: 1
+ knx_send: 3/0/61
- rocker_double_800ms_to_knx_send_1:
- type: bool
- enforce_updates: true
- enocean_rx_key: AI
- enocean_rocker_action: **set**
- enocean_rocker_sequence: **released within 0.4, pressed within 0.4**
- knx_dpt: 1
- knx_send: 3/0/62
-
- brightness_sensor:
- enocean_rx_id: 01234567
- enocean_rx_eep: A5_08_01
- lux:
- type: num
- enocean_rx_key: BRI
+ rocker_double_800ms_to_knx_send_1:
+ type: bool
+ enforce_updates: true
+ enocean_rx_key: AI
+ enocean_rocker_action: '**set**'
+ enocean_rocker_sequence: '**released within 0.4, pressed within 0.4**'
+ knx_dpt: 1
+ knx_send: 3/0/62
+
+ brightness_sensor:
+ enocean_rx_id: 01234567
+ enocean_rx_eep: A5_08_01
+ lux:
+ type: num
+ enocean_rx_key: BRI
+
+ movement:
+ type: bool
+ enocean_rx_key: MOV
- movement:
- type: bool
- enocean_rx_key: MOV
+ occupancy_sensor:
+ enocean_rx_id: 01234567
+ enocean_rx_eep: A5_07_03
+ lux:
+ type: num
+ enocean_rx_key: ILL
- occupancy_sensor:
- enocean_rx_id: 01234567
- enocean_rx_eep: A5_07_03
- lux:
- type: num
- enocean_rx_key: ILL
+ movement:
+ type: bool
+ enocean_rx_key: PIR
- movement:
- type: bool
- enocean_rx_key: PIR
+ voltage:
+ type: bool
+ enocean_rx_key: SVC
- voltage:
- type: bool
- enocean_rx_key: SVC
+ temperature_sensor:
+ enocean_rx_id: 01234567
+ enocean_rx_eep: A5_04_02
+ temperature:
+ type: num
+ enocean_rx_key: TMP
- temperature_sensor:
- enocean_rx_id: 01234567
- enocean_rx_eep: A5_04_02
- temperature:
- type: num
- enocean_rx_key: TMP
+ humidity:
+ type: num
+ enocean_rx_key: HUM
- humidity:
- type: num
- enocean_rx_key: HUM
+ power_status:
+ type: num
+ enocean_rx_key: ENG
- power_status:
- type: num
- enocean_rx_key: ENG
-
- sunblind:
- name: Eltako FSB14, FSB61, FSB71
- remark: actor for Shutter
- type: str
- enocean_rx_id: 1A869C3
- enocean_rx_eep: F6_0G_03
- enocean_rx_key: STATUS
- # runtime Range [0 - 255] s
- enocean_rtime: 80
- Tgt_Position:
- name: Eltako FSB14, FSB61, FSB71
- remark: Pos. 0...255
- type: num
- enocean_rx_id: ..:.
- enocean_rx_eep: ..:.
- enforce_updates: 'True'
- cache: 'True'
- visu_acl: rw
- Act_Position:
- name: Eltako FSB14, FSB61, FSB71
- remark: Ist-Pos. 0...255 berechnet aus (letzer Pos. + Fahrzeit * 255/rtime)
- type: num
- enocean_rx_id: ..:.
- enocean_rx_eep: ..:.
- enocean_rx_key: POSITION
- enforce_updates: 'True'
- cache: 'True'
- visu_acl: rw
- eval: min(max(value, 0), 255)
- on_update:
- - EnOcean_Item.sunblind = 'stopped'
- Run:
+ sunblind:
name: Eltako FSB14, FSB61, FSB71
- remark: Ansteuerbefehl 0x00, 0x01, 0x02
- type: num
- enocean_rx_id: ..:.
- enocean_rx_eep: ..:.
- enocean_tx_eep: A5_3F_7F
- enocean_tx_id_offset: 0
- enocean_rx_key: B
- enocean_rtime: ..:.
- # block actuator
- block_switch: 'True'
- enforce_updates: 'True'
- cache: 'True'
- visu_acl: rw
- struct: uzsu.child
- Movement:
- name: Eltako FSB14, FSB61, FSB71
- remark: Wenn Rolladen gestoppt wurde steht hier die gefahrene Zeit in s und die Richtung
- type: num
- enocean_rx_id: ..:.
- enocean_rx_eep: A5_0G_03
- enocean_rx_key: MOVE
- cache: 'False'
- enforce_updates: 'True'
- eval: value * 255/int(sh.EnOcean_Item.sunblind.property.enocean_rtime)
- on_update:
- - EnOcean_Item.sunblind = 'stopped'
- - EnOcean_Item.sunblind.Act_Position = EnOcean_Item.sunblind.Act_Position() + value
-
- RGBdimmer:
- type: num
- remark: Eltako FRGBW71L - RGB Dimmer
- enocean_rx_id: 1A869C3
- enocean_rx_eep: A5_3F_7F
- enocean_rx_key: DI_0
- red:
+ remark: actor for Shutter
+ type: str
+ enocean_rx_id: 1A869C3
+ enocean_rx_eep: F6_0G_03
+ enocean_rx_key: STATUS
+ # runtime Range [0 - 255] s
+ enocean_rtime: 80
+ Tgt_Position:
+ name: Eltako FSB14, FSB61, FSB71
+ remark: Pos. 0...255
+ type: num
+ enocean_rx_id: ..:.
+ enocean_rx_eep: ..:.
+ enforce_updates: 'True'
+ cache: 'True'
+ visu_acl: rw
+ Act_Position:
+ name: Eltako FSB14, FSB61, FSB71
+ remark: Ist-Pos. 0...255 berechnet aus (letzer Pos. + Fahrzeit * 255/rtime)
+ type: num
+ enocean_rx_id: ..:.
+ enocean_rx_eep: ..:.
+ enocean_rx_key: POSITION
+ enforce_updates: 'True'
+ cache: 'True'
+ visu_acl: rw
+ eval: min(max(value, 0), 255)
+ on_update:
+ - EnOcean_Item.sunblind = 'stopped'
+ Run:
+ name: Eltako FSB14, FSB61, FSB71
+ remark: Ansteuerbefehl 0x00, 0x01, 0x02
+ type: num
+ enocean_rx_id: ..:.
+ enocean_rx_eep: ..:.
+ enocean_tx_eep: A5_3F_7F
+ enocean_tx_id_offset: 0
+ enocean_rx_key: B
+ enocean_rtime: ..:.
+ # block actuator
+ block_switch: 'True'
+ enforce_updates: 'True'
+ cache: 'True'
+ visu_acl: rw
+ struct: uzsu.child
+ Movement:
+ name: Eltako FSB14, FSB61, FSB71
+ remark: Wenn Rolladen gestoppt wurde steht hier die gefahrene Zeit in s und die Richtung
+ type: num
+ enocean_rx_id: ..:.
+ enocean_rx_eep: A5_0G_03
+ enocean_rx_key: MOVE
+ cache: 'False'
+ enforce_updates: 'True'
+ eval: value * 255/int(sh.EnOcean_Item.sunblind.property.enocean_rtime)
+ on_update:
+ - EnOcean_Item.sunblind = 'stopped'
+ - EnOcean_Item.sunblind.Act_Position = EnOcean_Item.sunblind.Act_Position() + value
+
+ RGBdimmer:
type: num
- enocean_tx_eep: 07_3F_7F
- enocean_tx_id_offset: 1
+ remark: Eltako FRGBW71L - RGB Dimmer
+ enocean_rx_id: 1A869C3
+ enocean_rx_eep: A5_3F_7F
enocean_rx_key: DI_0
- ref_level: 80
- dim_speed: 100
- color: red
- green:
- type: num
- enocean_tx_eep: 07_3F_7F
- enocean_tx_id_offset: 1
- enocean_rx_key: DI_1
- ref_level: 80
- dim_speed: 100
- color: green
- blue:
- type: num
- enocean_tx_eep: 07_3F_7F
- enocean_tx_id_offset: 1
- enocean_rx_key: DI_2
- ref_level: 80
- dim_speed: 100
- color: blue
- white:
- type: num
- enocean_tx_eep: 07_3F_7F
- enocean_tx_id_offset: 1
- enocean_rx_key: DI_3
- ref_level: 80
- dim_speed: 100
- color: white
- water_sensor:
- enocean_rx_id: 00000000
- enocean_rx_eep: A5_30_03
-
- alarm:
- type: bool
- enocean_rx_key: ALARM
- visu_acl: ro
+ red:
+ type: num
+ enocean_tx_eep: 07_3F_7F
+ enocean_tx_id_offset: 1
+ enocean_rx_key: DI_0
+ ref_level: 80
+ dim_speed: 100
+ color: red
+ green:
+ type: num
+ enocean_tx_eep: 07_3F_7F
+ enocean_tx_id_offset: 1
+ enocean_rx_key: DI_1
+ ref_level: 80
+ dim_speed: 100
+ color: green
+ blue:
+ type: num
+ enocean_tx_eep: 07_3F_7F
+ enocean_tx_id_offset: 1
+ enocean_rx_key: DI_2
+ ref_level: 80
+ dim_speed: 100
+ color: blue
+ white:
+ type: num
+ enocean_tx_eep: 07_3F_7F
+ enocean_tx_id_offset: 1
+ enocean_rx_key: DI_3
+ ref_level: 80
+ dim_speed: 100
+ color: white
+ water_sensor:
+ enocean_rx_id: 00000000
+ enocean_rx_eep: A5_30_03
+
+ alarm:
+ type: bool
+ enocean_rx_key: ALARM
+ visu_acl: ro
+
+ temperature:
+ type: num
+ enocean_rx_key: TEMP
+ visu_acl: ro
- temperature:
- type: num
- enocean_rx_key: TEMP
- visu_acl: ro
-
diff --git a/harmony/README.md b/harmony/README.md
index f525b9334..dfc85dc7b 100755
--- a/harmony/README.md
+++ b/harmony/README.md
@@ -9,7 +9,7 @@ For support, questions and bug reports, please refer to [KNX-User-Forum](https:/
- an Harmony Hub device
- SmarthomeNG version >= 1.3
- Python3 module sleekxmpp
-- (optional) create a dummy Harmony Hub activity, [see remarks](#dummy)
+- (optional) create a dummy Harmony Hub activity, see remarks
```
sudo pip3 install sleekxmpp
diff --git a/indego4shng/README.md b/indego4shng/README.md
index ac6bc8b95..9e23216a3 100755
--- a/indego4shng/README.md
+++ b/indego4shng/README.md
@@ -2,16 +2,16 @@
## Table of Content
-1. [Generell](#generell)
-2. [Credits](#credits)
-3. [Change Log](#changelog) **Neu**
-4. [Konfiguration](#konfiguration) **Update**
-5. [Web-Interface](#webinterface)
-6. [Logik-Trigger](#logiktrigger)
-7. [öffentlich Funktionen (API)](#api)
-8. [Gartenkarte "pimpen"](#gardenmap)
-9. [Nutzung der Original Bosch-Mäher-Symbole](#boschpics)
-10. [Die Bosch-Api 4.0.1 - behind the scenes](#boschapi)
+1. Generell
+2. Credits
+3. Change Log **Neu**
+4. Konfiguration **Update**
+5. Web-Interface
+6. Logik-Trigger
+7. öffentlich Funktionen (API)
+8. Gartenkarte "pimpen"
+9. Nutzung der Original Bosch-Mäher-Symbole
+10. Die Bosch-Api 4.0.1 - behind the scenes
## Generell
@@ -194,7 +194,7 @@ Es können auf dieser Seite zusätzlich Vektoren eingefügt werden welche die Ga
Hier kann die Location auf den Bosch-Servern gespeichert werden.
Es müssen Längen/Breitengrad angegeben werden. Wenn noch keine Koordinaten in den Items gespeichert sind werden
die Long/Lat von shNG vorgeschlagen.
-[Sieh auch hier](#gardenmap)
+Sieh auch hier: gardenmap
Es können hier bis zu 4 Trigger für Stati gewählt werden. 999999 - kein Status gewählt.
@@ -317,7 +317,7 @@ sh.Indego4shNG.send_command('{"state":"returnToDock"}','Logic')
Die Gartenkarte wird vom Bosch-Server heruntergeladen und als Item für die Visu verwendet.
Die Datei wird als Vorlage zum Erweitern unter dem angegebenen Pfad gespeichert ( vgl. ```img_pfad``` im Konfig-Teil).
-Man kann die Karte als Vorlage in einem [Online-Tool](#https://editor.method.ac/) als Vorlage laden.
+Man kann die Karte als Vorlage in einem Online-Tool (#https://editor.method.ac/) als Vorlage laden.
Es werden dann einfach die zusätzlichen Vektoren eingezeichnet oder via "File / Import Image" hinzugeladen.
Man kann die veränderte Karte auch lokal zwischenspeichern.
diff --git a/indego4shng/README.not_convertable.md.off b/indego4shng/README.not_convertable.md.off
new file mode 100755
index 000000000..ac6bc8b95
--- /dev/null
+++ b/indego4shng/README.not_convertable.md.off
@@ -0,0 +1,694 @@
+# Indego4shNG
+
+## Table of Content
+
+1. [Generell](#generell)
+2. [Credits](#credits)
+3. [Change Log](#changelog) **Neu**
+4. [Konfiguration](#konfiguration) **Update**
+5. [Web-Interface](#webinterface)
+6. [Logik-Trigger](#logiktrigger)
+7. [öffentlich Funktionen (API)](#api)
+8. [Gartenkarte "pimpen"](#gardenmap)
+9. [Nutzung der Original Bosch-Mäher-Symbole](#boschpics)
+10. [Die Bosch-Api 4.0.1 - behind the scenes](#boschapi)
+
+## Generell
+
+Das Indego-Plugin wurde durch ein Reverse-Engineering der aktuellen (Version 3.0) App
+von Bosch entwickelt. Als Basis diente das ursprüngliche Plugin von Marcov. Es werden alle Funktionen der App für den Betrieb sowie einige zusätzliche bereitgestellt.
+Für die Ersteinrichtung wird weiterhin die Bosch-App benötigt.
+Das Plugin erhält die Version der aktuellen Bosch-API. (4.0.1)
+
+## Credits
+
+Vielen Dank an schuma für die tolle Unterstützung während der Entwicklungsphase,
+die Umsetzung vieler Teile in der Visu sowie den vielen unzähligen Tests und sehr viel Geduld.
+
+Vielen Dank an bmx für das Umstellen des Plugins auf Smart-Plugin.
+Vielen Dank an psilo für die Erlaubnis zur Verwendung der LED-Grafiken im Web-Interface.
+Vielen Dank an Marcov für die Entwicklung des ursprünglichen Plugins.
+Vielen Dank an das Core-Team für die Einführung der STRUCTS, das hat die Arbeit deutlich vereinfacht.
+Vielen Dank an Jan Odvarko für die Entwicklung des Color-Pickers (http://jscolor.com) unter Freigabe für Opensource mit GPLv3
+
+
+## Change Log
+#### 2023-05-06 V4.0.1
+- Login via Single-Key-ID eingebaut
+- Endpoit der Bosch-API wurde geändert (siehe Konfiguration)
+
+#### 2023-03-08 V4.0.0
+- Login via Bosch-ID eingebaut
+
+#### 2023-02-05 V3.0.2
+- Anpassungen für die geänderten Daten für das Wetter (es werden nun 7 Tage statt 5 übermittelt, die Sonnenstunden je Tag wurden entfern)
+
+#### 2021-05-16 V3.0.1
+- rücksetzen des Messerzählers eingebaut
+- besseres Handling beim automatischen AUS/EIN-Loggen
+- Einstellen des Mäher-Standorts über das Web-Interface (die Location wird durch die Bosch-App nicht richtig gesetzt, die Wetterdaten werden dann nicht mehr korrekt übermittelt.)
+
+#### 2019-10-28 V3.0.0
+- Kommunikation auf requests geändert
+- Verwendung von vordefinierten STRUCTS für alle benötigten Items
+- verbessertes Login/Session-Handling
+- Umstellung auf Code64 verschlüsselte Credentials
+- Integration eines Wintermodus wenn der Mäher stillgelegt ist
+- Integration der Mähkalenderverwaltung
+- Integration der SmartMow-Einstellungen
+- Integration "Mähen nach UZSU"
+- verbesserte Darstellung der Icons für das Wetter
+- Gartenkarte als Item in Visu integriert
+- "pimpen" der Gartenkarte mit eigenen Vektoren
+- Mähspurdarstellung für die IndegoConnect 350/400
+- Aktualisierung der Mäherposition beim Mähen alle 7 Sekunden
+- Darstellung der Informationen zum genutzten GSM-Netz sowie zum verwendeten Standort
+- Updatefunktionen für Firmware integriert
+- Integration der Sensorempfindlichkeit
+- Integration von unterschiedlichen Bilder für Große/Kleine Mäher
+- Alarme / Meldungen werden in einem Popup dargestellt und können gelesen/gelöscht werden.
+- VISU um Batterie-Informationen erweitert
+- diverse Charts für Batterie, Temperatur, Mäheffizienz, Mäh-/Ladezeiten
+- Protokoll für Mäher STATI und Bosch-Kommunikation im Web-Interface
+- Unterstützung für base64 codierte Credentials im Web-Interface
+- Trigger für Alarme und STATI des Mähers im Web-Interface
+- Mäherfarbe für die Darstellung der Kartenkarte im Web-Interface wählbar
+
+
+
+
+## Requirements
+
+Das UZSU-Plugin wird genutzt. Das UZSU-Plugin sollte vor dem Indego4shNG-Plugin geladen sein
+(Reihenfolge in der smarthome/etc/plugin.yaml)
+
+### benötigte Software
+
+* SmartVISU 2.9 oder höher (es werden Dropins verwendet)
+* smarthomeNg 1.6 oder höher (es werden vordefinierte STRUCTS verwendet)
+* für die Darstellung der Charts muss eine Database-plugin aktiviert sein
+
+
+### Supported Hardware
+
+* #### Indego Connect 350/S+350/400/S+400, im folgenden die "Kleinen" genannt
+* #### Indego Connect 800/1000/1200/1300, im folgenden die "Großen" genannt
+
+Die Firmware der "Kleinen" und der "Großen" liefern unterschiedliche Informationen
+ und stellen unterschiedliche Funktionen zur Verfügung. Hier werden kurz die Unterschiede erläutert:
+
+Bei den "Großen" gibt es folgende Einschränkungen:
+* der Ladezustand des Akkus wird auf Grund der abfallenden Spannung berechnet (35 Volt = 100 %, 28 Volt = 0%)
+Langzeitbeobachtungen haben gezeigt, dass die Mähe bei 31 Volt zurück in die Ladestation fahren. Es wird unterstellt,
+ dass 31 Volt noch 20 % Akkuladestand sind.
+* Es werden aktuell keine Informationen zur Netznutzung bereitgestellt.
+* Die Aktualisierung der Mäherposition erfolgt nur ca. alle 30 Minuten während des Mähens
+* Die Sensor-Empfindlichkeit kann nicht eingestellt werden
+
+Bei den "Kleinen" gibt es folgende Einschränkungen:
+* Es wird von Bosch keine gemähte Fläche übermittelt. Diese kann mittels des "MowTracks" aber angezeigt werden.
+
+
+## Konfiguration
+
+### plugin.yaml
+
+folgende Einträge werden in der "./etc/plugin.yaml" benötigt.
+
+* `plugin_name: Indego4shNG`: fix "Indego4shNG"
+* `class_path: plugins.indego4shng`: fix "plugins.indego4shng"
+* `path_2_weather_pics: XXXXXXX`: ist der Pfad zu den Bilder des Wetter-Widgets.
+(default ="/smartvisu/lib/weather/pics/")
+* `img_pfad: XXXXXXX`: ist der Pfad unter dem die Gartenkarte gespeichert wird.
+(default = "/tmp/garden.svg")
+Die Datei wird nicht für die VISU benötigt. Man kann die Datei als Vorlage
+zum "pimpen" der Gartenkarte verwenden
+* `indego_credentials : XXXXXXX`: sind die Zugangsdaten für den Bosch-Server im Format base64 encoded.
+* `parent_item : indego`: name des übergeordneten items für alle Child-Items
+* `cycle : 30`: Intervall in Sekunden für das Abrufen des Mäher-Status (default = 30 Sekunden)
+* `url: https://api.indego-cloud.iot.bosch-si.com/api/v1/` : Url des Bosch-Endpoints
+
+Die Zugangsdaten (indego_credentials) können nach dem Erststart des Plugins im Web-Interface erfasst und gespeichert werden
+
+!! Das parent-Item kann umbenannt werden ,es müssen dann aber alle items in der indego.html angepasst werden !!
+
+Beispiel:
+
+```yaml
+Indego4shNG:
+ plugin_name: Indego4shNG
+ class_path: plugins.indego4shng
+ path_2_weather_pics: /smartvisu/lib/weather/pics/
+ img_pfad: /tmp/garden.svg
+ indego_credentials:
+ parent_item: indego
+ cycle: '30'
+ url: https://api.indego-cloud.iot.bosch-si.com/api/v1/
+```
+
+
+
+### items.yaml
+
+Es wird ledigliche folgender Eintrag für die Items benötigt.
+Die restlichen Informationen werden aus der mitgelieferten Struct-Definition gelesen.
+Eine entsprechende Config-Datei ist im Ordner "items" des Plugins bereits vorhanden und
+muss nur in den Ordner "./smarthome/items" kopiert werden.
+
+```yaml
+%YAML 1.1
+---
+
+indego:
+ struct: indego4shng.child
+```
+
+### SmartVisu
+
+Die Inhalte des Ordners "./sv_widgets" müssen in den entsprechenden Ordner der VISU.
+In der Regel "/var/www/html/smartvisu/dropins" kopiert werden.
+Wenn das smartvisu-Plugin verwendet wird und das kopieren der Widget nicht abgeschalten ist, werden die Dateien beim Start von shNG automatisch in den Dropin-Ordner kopiert.
+Ansonsten müssen die Daten manuell in das Verzeichnigs "./dropins" kopiert werden.
+
+Die Icons aus "indego4shng/pages/icons/" müssen in das visu-dir "dropins/icons/ws/" kopiert werden.
+
+Im Ordner "/pages" des plugins ist eine vorgefertigte Raumseite für die SmartVISU. (indego.html)
+Diese muss in den Ordner "/pages/DeinName/" kopiert werden und die Raumnavigation entsprechend ergänzt werden.
+
+!!! Immer auf die Rechte achten !!!
+
+
+## Web-Interface
+Kurze Erläuterung zum Web-Interface
+### erster Tab - Übersicht Indego-Items
+
+
+
+
+### zweiter Tab - Originalgartenkarte / Settings
+Hier wird die Original-Gartenkarte wie sie von Bosch übertragen wird angezeigt.
+Es kann mit dem Colour-Picker die Farbe des Mähers in der Visu angepasst werden.
+Die Originalkarte bleibt unverändert. Im ersten Tab wird unter dem Item indego.visu.map_2_show
+die modifizierte Karte angzeigt.
+Es können auf dieser Seite zusätzlich Vektoren eingefügt werden welche die Gartenkarte erweitern bzw."aufhübschen"
+Hier kann die Location auf den Bosch-Servern gespeichert werden.
+Es müssen Längen/Breitengrad angegeben werden. Wenn noch keine Koordinaten in den Items gespeichert sind werden
+die Long/Lat von shNG vorgeschlagen.
+[Sieh auch hier](#gardenmap)
+
+
+Es können hier bis zu 4 Trigger für Stati gewählt werden. 999999 - kein Status gewählt.
+Immer wenn der Status des Mähers auf den gewählten Status wechselt wird das Trigger-item
+"indego.trigger.state_trigger_X:" (X = 1-4 ) gesetzt. Die Trigger können in einer Logik
+verarbeitet werden. Beispiel siehe bei Logiken.
+Es können bis zu 4 Texte für Meldungen erfasst werden. Wenn der Text in der Überschrift oder im Inhalt der
+Meldung ist wird der Trigger "indego.trigger.alarm_trigger_X:" (X = 1-4 ) beim eintreffen der Meldung gesetzt.
+
+
+
+
+
+### dritter Tab - State-Protokoll
+Hier können die einzelnen Statuswechsel des Mähers eingesehen werden.
+Es erfolgt bei jedem Statuswechsel ein Eintrag, das Protokoll ist selbst rotierend und hat
+maximal 500 Einträge
+
+
+
+
+
+### vierter Tab - Kommunikationsprotokoll
+Hier können Protokoll-Einträge zu den einzelnen Kommunikationsanfragen mit dem Bosch-Server eingesehen werden.
+Es erfolgt bei jedem Statuswechsel ein Eintrag, das Protokoll ist selbst rotierend und hat
+maximal 500 Einträge
+
+
+
+## Logik-Trigger
+
+Über die Items :
+
+indego.trigger_state_trigger_1(2)(3)(4)
+
+und
+
+indego.trigger_alarm_trigger_1(2)(3)(4)
+
+können Events auf state-Wechsel und Meldungen in Logiken ausgeführt werden.
+Die Trigger werden über das Web-Interface definiert. Bei den Alarmen wird ein Teil des
+Textes der Alarm-Meldung oder der Überschrift angegeben. Groß- Kleinschreibung spielt keine Rolle
+Wenn der Text in der Meldung bzw. der Überschrift enthalten ist wird der Trigger ausgelöst.
+
+Beispiel :
+
+```
+#!/usr/bin/env python3
+# indego2alexa.py
+
+text=''
+try:
+ triggeredItem=trigger['source']
+ triggerValue = trigger['value']
+
+ # Check the State-Items
+ if triggeredItem == 'indego.trigger.state_trigger_1':
+ if triggerValue == True:
+ text = 'Achtung der Indego nimmt seine Arbeit auf'
+
+ elif triggeredItem == 'indego.trigger.state_trigger_2':
+ if triggerValue == True:
+ text = 'Der Indego hat seine Arbeit getan Danke Indego'
+
+ elif triggeredItem == 'indego.trigger.state_trigger_3':
+ if triggerValue == True:
+ text = ''
+
+ elif triggeredItem == 'indego.trigger.state_trigger_4':
+ if triggerValue == True:
+ text = ''
+
+ # Now the Alarm-Items
+ if triggeredItem == 'indego.trigger.alarm_trigger_1':
+ if triggerValue == True:
+ text = 'Achtung der Indego benötigt Wartung'
+
+ elif triggeredItem == 'indego.trigger.alarm_trigger_2':
+ if triggerValue == True:
+ text = 'Achtung der Indego benötigt neue Messer'
+
+ elif triggeredItem == 'indego.trigger.alarm_trigger_3':
+ if triggerValue == True:
+ text = ''
+
+ elif triggeredItem == 'indego.trigger.alarm_trigger_4':
+ if triggerValue == True:
+ text = ''
+
+ if text != '':
+ sh.alexarc4shng.send_cmd('Kueche', 'Text2Speech', text);
+except:
+ pass
+```
+
+
+## öffentliche Funkionen
+
+Es gibt eine Funktion die z.B. über Logiken aufgerufen werden kann.
+#### send_command(Payload as String)
+
+Man kann so z.B. den Mäher bei einsetzendem Regen der durch die Wetterstation erkannt wird
+zurück in die Ladestation schicken.
+Anderes Beispiel wäre beim Verlassen des Hauses den Mäher starten.
+```
+#!/usr/bin/env python3
+# indego_rc.py
+
+sh.Indego4shNG.send_command('{"state":"returnToDock"}','Logic')
+#sh.Indego4shNG.send_command('{"state":"mow"}','Logic')
+#sh.Indego4shNG.send_command('{"state":"pause"}','Logic')
+
+
+
+```
+
+
+## Gardenkarte "pimpen"
+
+Die Gartenkarte wird vom Bosch-Server heruntergeladen und als Item für die Visu verwendet.
+Die Datei wird als Vorlage zum Erweitern unter dem angegebenen Pfad gespeichert ( vgl. ```img_pfad``` im Konfig-Teil).
+
+Man kann die Karte als Vorlage in einem [Online-Tool](#https://editor.method.ac/) als Vorlage laden.
+Es werden dann einfach die zusätzlichen Vektoren eingezeichnet oder via "File / Import Image" hinzugeladen.
+
+Man kann die veränderte Karte auch lokal zwischenspeichern.
+
+Am Ende wählt man im Menü die Ansicht "View" den Eintrag "Source". Hier kann man die erweiterten Vektoren
+einfach in die Zwischenablage kopieren und im Web-Interface unter Tab-2 einfügen.
+Der letzte Original-Eintrag der Bosch-Karte ist die Zeile mit
+
+```
+
+```
+
+Die Werte können abweichen, da hier auch die Position des Mähers sowie die ID enthalten ist. Am besten auf "circle" und den Farbwert "#FFF601" achten.
+
+Diese Zeile ist der gelbe Punkt (Mäher) in der Originalkarte.
+Beim Verlassen der Textarea werden die neuen Vektoren sofort in ein Item gespeichert und die Gartenkarte neu gerendert.
+Das Ergebnis ist in der VISU sofort sichtbar.
+
+## Beispiel :
+
+
+
+
+## Nutzung der Original Bosch-Mäher-Symbole
+
+Es werden die Bilder der Bosch 2.2.8 App verwenden.
+Man kann sich die Bilder aus der "Legacy Bosch Smart Gardening"-App extrahieren.
+Die APK-Datei ist im Internet zu finden.
+
+
+Die apk-Datei mit einer Archiv-Verwaltung öffnen und dort im Pfad "/assets/www/assets" die Bilder extrahieren und in den Dropins-Ordner kopieren.
+Die Bilder haben folgende Dateinamen :
+
+Für die "Großen"
+```
+indego.png
+indego-docked.png
+indego-mowing.png
+```
+
+Für die "Kleinen"
+```
+indego-s.png
+indego-docked-s.png
+indego-mowing-s.png
+```
+Sobald die Dateien mit den Bildern vorhanden sind findet das Widget diese und verwendet sie automatisch.
+Die entsprechenden Bilder für die "Großen"/"Kleinen" werden auf Grund des Mähertyps automatisch gewählt und dargestellt.
+
+
+## Die Bosch-Api 4.0.1 - behind the scenes
+
+Hier ist die Schnittstelle der Bosch-API kurz beschrieben und die Implementierung im Plugin dokumentiert.
+Der Header ist in den meisten Fällen mit der Session-ID zu füllen :
+```
+headers = {'accept' : '*/*',
+ 'authorization' : 'Bearer '+ self._bearer,
+ 'connection' : 'Keep-Alive',
+ 'host' : 'api.indego-cloud.iot.bosch-si.com',
+ 'user-agent' : 'Indego-Connect_4.0.0.12253',
+ 'content-type' : 'application/json'
+ }
+```
+@Get - steht für einen get-request in Python. Die URL lautet : "https://api.indego-cloud.iot.bosch-si.com/api/v1/" gefolgt vom entsprechenden Zugriffspunkt
+```
+url = "https://api.indego-cloud.iot.bosch-si.com/api/v1/" +"alms/{}/automaticUpdate".format(alm_sn)
+response = requests.get(url, headers=headers)
+```
+
+Über die Items :
+
+
+
+
+ plugin-Supp. |
+ API-URL |
+ Payload |
+
+
+
+
+ ja |
+ "@DELETE("alerts/{alert_id}")" |
+ - |
+
+
+ nein |
+ @POST("users") |
+ {} |
+
+
+ nein |
+ @DELETE("alerts") |
+ - |
+
+
+ nein |
+ @DELETE("alms/{alm_serial}/map") |
+ - |
+
+
+ nein |
+ @DELETE("users/{user_id}") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/config") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}") |
+ {"needs_service": false, "alm_firmware_version": "00647.01043", "service_counter": 159551, "bareToolnumber": "3600HA2300", "alm_name": "Indego", "alm_sn": "XXXXXXXXXX", "alm_mode": "manual"} |
+
+
+ nein |
+ @GET("alms/{alm_serial}") |
+ - |
+
+
+ nein |
+ @GET("pub/accessories") |
+ - |
+
+
+ ja |
+ @GET("alerts") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/automaticUpdate") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/updates") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/operatingData") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/calendar") |
+ {} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/location") |
+ {} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/lastcutting") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/map") |
+ - |
+
+
+ nein |
+ @GET("alms/{alm_serial}/info") |
+ {"bareToolnumber": "3600HA2300"} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/location") |
+ {"longitude": x.xxxx, "latitude": xx.xxxxx} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/network") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/nextcutting") |
+ - |
+
+
+ nein |
+ @GET("alms/{alm_serial}/info") |
+ - |
+
+
+ nein |
+ @GET("pub/accessories/{accessory_code}") |
+ - |
+
+
+ nein |
+ @GET("alms/{alm_serial}/security") |
+ {"enabled": true, "autolock": false} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive") |
+ {"enabled": false} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/schedule") |
+ {'exclusion_days': [{'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 14, 'En': True, 'Attr': 'tTD', 'StMin': 0}, {'StHr': 18, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 't', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 0}, {'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 12, 'En': True, 'Attr': 'tT', 'StMin': 0}, {'StHr': 18, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 'tT', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 1}, {'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 14, 'En': True, 'Attr': 'tTD', 'StMin': 0}, {'StHr': 16, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 'tT', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 2}, {'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 'tTD', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 3}, {'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 'tTD', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 4}, {'slots': [{'StHr': 0, 'EnMin': 0, 'EnHr': 8, 'En': True, 'Attr': 'C', 'StMin': 0}, {'StHr': 8, 'EnMin': 0, 'EnHr': 14, 'En': True, 'Attr': 'tTD', 'StMin': 0}, {'StHr': 16, 'EnMin': 0, 'EnHr': 22, 'En': True, 'Attr': 'tT', 'StMin': 0}, {'StHr': 22, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 5}, {'slots': [{'StHr': 0, 'EnMin': 59, 'EnHr': 23, 'En': True, 'Attr': 'C', 'StMin': 0}], 'day': 6}], 'schedule_days': [{'slots': [{'En': True, 'StHr': 14, 'EnMin': 0, 'StMin': 0, 'EnHr': 16}], 'day': 0}]} |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/setup") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/state") |
+ {"svg_xPos": 768, "runtime": {"session": {"charge": 0, "operate": 4}, "total": {"charge": 34352, "operate": 193907}}, "mowed": 89, "mowmode": 0, "xPos": 14, "yPos": 96, "svg_yPos": 792, "mapsvgcache_ts": 1573585675296, "state": 64513, "map_update_available": false} |
+
+
+ nein |
+ @GET("pub/static/{resource_id}") |
+ - |
+
+
+ nein |
+ @GET("pub/support/DE") |
+ {'email': 'indego.support@de.bosch.com', 'phone': '+49 711 400 40 470'} |
+
+
+ nein |
+ @GET("users/{user_id}") |
+ {'optInApp': False, 'display_name': 'XXXXXXXXX', 'email': 'xxxxx.xxxx@xxxxxxxx.xxx', 'country': 'DE', 'language': 'de', 'optIn': False} |
+
+
+ nein |
+ @GET("pub/video&country_code=DE&language=de&mowerType=XXXX") |
+ - |
+
+
+ ja |
+ @GET("alms/{alm_serial}/predictive/weather") |
+ - |
+
+
+ ja |
+ @POST("authenticate") |
+ - |
+
+
+ nein |
+ @POST("authenticate?facebook") |
+ - |
+
+
+ ja |
+ @DELETE("authenticate") |
+ - |
+
+
+ nein |
+ @POST("alms/{alm_serial}/pair") |
+ - |
+
+
+ nein |
+ @POST("alms/{alm_serial}/map") |
+ - |
+
+
+ ja |
+ @POST("alms/{alm_serial}/requestPosition") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}") |
+ - |
+
+
+ ja |
+ @PUT("alerts/{alert_id}") |
+ - |
+
+
+ nein |
+ @PUT("alerts") |
+ - |
+
+
+ ja* |
+ @PUT("alms/{alm_serial}/config") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/automaticUpdate") |
+ {} |
+
+
+ nein |
+ @PUT("alms/{alm_serial}/updates/notification/{process_id}") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/calendar") |
+ - |
+
+
+ nein |
+ @PUT("alms/{alm_serial}/dateAndTime") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/predictive/location") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/updates") |
+ - |
+
+
+ nein |
+ @PUT("alms/{alm_serial}/predictive/reset") |
+ @Query("reinitialize") |
+
+
+ nein |
+ @PUT("alms/{alm_serial}/security") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/predictive") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/predictive/setup") |
+ - |
+
+
+ ja |
+ @PUT("alms/{alm_serial}/state") |
+ - |
+
+
+ nein |
+ @PUT("users/{user_id}") |
+ - |
+
+
+ ja |
+ @POST("authenticate/check") |
+ - |
+
+
+ nein |
+ @POST("pub/resetpassword") |
+ - |
+
+
+ nein |
+ @DELETE("alms/{alm_serial}/pair") |
+ - |
+
+
+
+
+'* nur bei 350/350+/400/400+
diff --git a/mieleathome/README.md b/mieleathome/README.md
deleted file mode 100755
index f02766aa1..000000000
--- a/mieleathome/README.md
+++ /dev/null
@@ -1,116 +0,0 @@
-# mieleathome
-
-## Version 1.0.0
-
-Das Plugin ermöglicht den Zugriff auf die Miele@Home API. Es werden Stati abgefragt und
-im Rahmen der Möglichkeiten der API können Geräte gesteuert werden.
-Es wird das Pollen von Informationen sowie das Event-gestütze Empfangen von Daten unterstützt.
-Für das Event-Listening wird ein Stream-request zum Miele-Server aufgebaut. Falls durch den Trennung der
-Internet-Verbindung der Stream abreisst wird dies durch das Plugin erkannt und eine neuer Stream
-aufgebaut.
-
-
-## table of content
-
-1. [Change Log](#changelog)
-2. [Aktivierung des Zugriffs für 3rd party-Apps](#activate)
-3. [Einstellungen in der plugin.yaml](#plugin_yaml)
-4. [Ermittln der Device-ID´s](#device_id)
-5. [Items definieren](#create_items)
-6. [Darstellung in der VISU](#visu)
-7. [known issues](#issues)
-
-## ChangeLog
-
-### 2021-11-21
-- Version 1.0.0
-- first Commit für Tests
-- Bedienen und Überwachen von Trocknern und Gefrierschränken ist implementiert
-- Folgende Funktionen sind realisiert
-
- - Status
- - programPhase
- - programType
- - remainingTime
- - targetTemperature
- - temperature
- - signalInfo
- - signalFailure
- - signalDoor
- - dryingStep
- - elapsedTime
- - ecoFeedback
- - batteryLevel
- - processAction ( start / stop / pause / start_superfreezing / stop_superfreezing / start_supercooling / stop_supercooling / PowerOn / PowerOff)
-
-
-### Todo in Version 1.0.0
-
-- Verarbeitung von "Programmen"
-- Verarbeitung von "ambientLight", "light", "ventilationStep", "colors"
-- Verarbeiten von "modes"
-
-## Aktivierung des Zugriffs für 3rd party-Apps
-
-
-Eine App unter https://www.miele.com/f/com/en/register_api.aspx registrieren. Nach Erhalt der Freischalt-Mail die Seite aufrufen und das Client-Secret und die Client-ID kopieren und merken (speichern).
-Dann einmalig über das Swagger-UI der API (https://www.miele.com/developer/swagger-ui/swagger.html) mittels Client-ID und Client-Secret über den Button "Authorize" (in grün, auf der rechten Seite) Zugriff erteilen. Wenn man Client-Id und Client-Secret eingetragen hat wird man einmalig aufgefordert mittels mail-Adresse, Passwort und Land der App-Zugriff zu erteilen.
-
-Die erhaltenen Daten für Client-ID und Client-Secret in der ./etc/plugin.yaml wie unten beschrieben eintragen.
-
-##Settings für die /etc/plugin.yaml
-
-
-mieleathome:
- plugin_name: mieleathome
- class_path: plugins.mieleathome
- miele_cycle: 120
- miele_client_id: ''
- miele_client_secret: ''
- miele_client_country: 'de-DE'
- miele_user: '' # email-Adress
- miele_pwd: '' # Miele-PWD
-
-
-## Ermitteln der benötigten Device-ID´s
-
-Das Plugin kann ohne item-Definitionen gestartet werden. Sofern gültige Zugangsdaten vorliegen
-werden die registrierten Mielegeräte abgerufen. Die jeweiligen Device-Id´s können im WEB-IF auf dem
-zweiten Tab eingesehen werden.
-
-## Anlegen der Items
-
-Es wird eine vorgefertigtes "Struct" für alle Geräte mitgeliefert. Es muss lediglich die Miele-"DeviceID" beim jweiligen Gerät
-erfasst werden. Um die Miele-"DeviceID" zu ermitteln kann das Plugin ohne Items eingebunden und gestartet werden. Es werden im Web-IF
-des Plugins alle registrierten Geräte mit der jeweiligen DeviceID angezeigt.
-Führende Nullen der DeviceID sind zu übernehmen
-
-
-
-%YAML 1.1
----
-MieleDevices:
- Freezer:
- type: str
- miele_deviceid: 'XXXXXXXXXXX'
- struct: mieleathome.child
- Dryer:
- type: str
- miele_deviceid: 'YYYYYYYYYYY'
- struct: mieleathome.child
-
-
-
-
-
-
-## Darstellung in der VISU
-
-Es gibt eine vorgefertigte miele.html im Plugin-Ordner. Hier kann man die jeweiligen Optionen herauslesen und nach
-den eigenen Anforderungen anpassen und in den eigenen Seiten verwenden.
-
-## known issues
-### Trockner :
-Ein Trockner kann nur im Modus "SmartStart" gestartet werden.
-Es muss der SmartGrid-Modus aktiv sein und das Gerät auf "SmartStart" eingestellt werden.
-Der Trockner kann dann via API/Plugin gestartet werden bzw. es kann eine Startzeit via API/Plugin gesetzt werden
diff --git a/mieleathome/assets/img.png b/mieleathome/assets/img.png
deleted file mode 100755
index 6f7b59ced..000000000
Binary files a/mieleathome/assets/img.png and /dev/null differ
diff --git a/mieleathome/assets/img_1.png b/mieleathome/assets/img_1.png
deleted file mode 100755
index 6f7b59ced..000000000
Binary files a/mieleathome/assets/img_1.png and /dev/null differ
diff --git a/mieleathome/assets/img_10.png b/mieleathome/assets/img_10.png
deleted file mode 100755
index b4de2ce9c..000000000
Binary files a/mieleathome/assets/img_10.png and /dev/null differ
diff --git a/mieleathome/assets/img_11.png b/mieleathome/assets/img_11.png
deleted file mode 100755
index ce6742316..000000000
Binary files a/mieleathome/assets/img_11.png and /dev/null differ
diff --git a/mieleathome/assets/img_12.png b/mieleathome/assets/img_12.png
deleted file mode 100755
index d7e486224..000000000
Binary files a/mieleathome/assets/img_12.png and /dev/null differ
diff --git a/mieleathome/assets/img_13.png b/mieleathome/assets/img_13.png
deleted file mode 100755
index ec1b29b89..000000000
Binary files a/mieleathome/assets/img_13.png and /dev/null differ
diff --git a/mieleathome/assets/img_14.png b/mieleathome/assets/img_14.png
deleted file mode 100755
index 96cda9d45..000000000
Binary files a/mieleathome/assets/img_14.png and /dev/null differ
diff --git a/mieleathome/assets/img_15.png b/mieleathome/assets/img_15.png
deleted file mode 100755
index d973ec897..000000000
Binary files a/mieleathome/assets/img_15.png and /dev/null differ
diff --git a/mieleathome/assets/img_16.png b/mieleathome/assets/img_16.png
deleted file mode 100755
index efdffa81a..000000000
Binary files a/mieleathome/assets/img_16.png and /dev/null differ
diff --git a/mieleathome/assets/img_17.png b/mieleathome/assets/img_17.png
deleted file mode 100755
index efdffa81a..000000000
Binary files a/mieleathome/assets/img_17.png and /dev/null differ
diff --git a/mieleathome/assets/img_18.png b/mieleathome/assets/img_18.png
deleted file mode 100755
index 351d09a45..000000000
Binary files a/mieleathome/assets/img_18.png and /dev/null differ
diff --git a/mieleathome/assets/img_5.png b/mieleathome/assets/img_5.png
deleted file mode 100755
index f2aeba29f..000000000
Binary files a/mieleathome/assets/img_5.png and /dev/null differ
diff --git a/mieleathome/assets/img_6.png b/mieleathome/assets/img_6.png
deleted file mode 100755
index 6e0c424c4..000000000
Binary files a/mieleathome/assets/img_6.png and /dev/null differ
diff --git a/mieleathome/assets/img_7.png b/mieleathome/assets/img_7.png
deleted file mode 100755
index 48e6b09a7..000000000
Binary files a/mieleathome/assets/img_7.png and /dev/null differ
diff --git a/mieleathome/assets/img_8.png b/mieleathome/assets/img_8.png
deleted file mode 100755
index 22e8e80f0..000000000
Binary files a/mieleathome/assets/img_8.png and /dev/null differ
diff --git a/mieleathome/assets/img_9.png b/mieleathome/assets/img_9.png
deleted file mode 100755
index d9baf2fed..000000000
Binary files a/mieleathome/assets/img_9.png and /dev/null differ
diff --git a/mieleathome/assets/smartvisu.png b/mieleathome/assets/smartvisu.png
new file mode 100644
index 000000000..563da9f45
Binary files /dev/null and b/mieleathome/assets/smartvisu.png differ
diff --git a/mieleathome/assets/img_3.png b/mieleathome/assets/webif_devices.png
similarity index 100%
rename from mieleathome/assets/img_3.png
rename to mieleathome/assets/webif_devices.png
diff --git a/mieleathome/assets/img_4.png b/mieleathome/assets/webif_events.png
similarity index 100%
rename from mieleathome/assets/img_4.png
rename to mieleathome/assets/webif_events.png
diff --git a/mieleathome/assets/img_2.png b/mieleathome/assets/webif_items.png
similarity index 100%
rename from mieleathome/assets/img_2.png
rename to mieleathome/assets/webif_items.png
diff --git a/mieleathome/user_doc.rst b/mieleathome/user_doc.rst
index fcb29e588..8400f3b8a 100755
--- a/mieleathome/user_doc.rst
+++ b/mieleathome/user_doc.rst
@@ -1,6 +1,141 @@
-Sample Plugin <- hier den Namen des Plugins einsetzen
-=====================================================
+.. index:: Plugins; mieleathome
+.. index:: mieleathome
-Anforderungen
--------------
-Wird nachgereicht
+===========
+mieleathome
+===========
+
+.. image:: webif/static/img/plugin_logo.svg
+ :alt: plugin logo
+ :width: 300px
+ :height: 300px
+ :scale: 50 %
+ :align: left
+
+Das Plugin ermöglicht den Zugriff auf die `Miele@Home API`. Es werden Stati abgefragt und
+im Rahmen der Möglichkeiten der API können Geräte gesteuert werden.
+Es wird das Pollen von Informationen sowie das Event-gestütze Empfangen von Daten unterstützt.
+Für das Event-Listening wird ein Stream-request zum Miele-Server aufgebaut. Falls durch den Trennung der
+Internet-Verbindung der Stream abreisst wird dies durch das Plugin erkannt und eine neuer Stream
+aufgebaut.
+
+Folgende Funktionen zur Überwachung und Steuerung von Trocknern und Gefrierschränken sind implementiert:
+
+- Status
+- programPhase
+- programType
+- remainingTime
+- targetTemperature
+- temperature
+- signalInfo
+- signalFailure
+- signalDoor
+- dryingStep
+- elapsedTime
+- ecoFeedback
+- batteryLevel
+- processAction ( start / stop / pause / start_superfreezing / stop_superfreezing / start_supercooling / stop_supercooling / PowerOn / PowerOff)
+
+Bei einem Trockner muss der SmartGrid-Modus aktiv sein und das Gerät auf "SmartStart" eingestellt werden.
+Der Trockner kann dann via API/Plugin gestartet werden bzw. es kann eine Startzeit via API/Plugin gesetzt werden.
+
+Konfiguration
+=============
+
+.. important::
+
+ Detaillierte Informationen zur Konfiguration des Plugins sind unter :doc:`/plugins_doc/config/mieleathome` zu finden.
+
+plugin.yaml
+-----------
+
+Um Zugang zu einem Mielegerät zu erhalten, ist es notwendig, zuerst eine App (smarthomeNG) unter
+`Miele `_ zu registrieren. Nach Erhalt der Freischalt-Mail
+die Seite aufrufen und das Client-Secret und die Client-ID kopieren/merken/speichern.
+
+Dann einmalig über das `Swagger-UI der API `_ mittels
+Client-ID und Client-Secret über den Button "Authorize" (in grün, auf der rechten Seite) Zugriff erteilen.
+Wenn man Client-Id und Client-Secret eingetragen hat, wird man einmalig aufgefordert mittels Mail-Adresse, Passwort und Land der App-Zugriff zu erteilen.
+
+Die erhaltenen Daten für Client-ID und Client-Secret in der ./etc/plugin.yaml wie unten beschrieben eintragen.
+
+.. code-block:: yaml
+
+ # etc/plugin.yaml
+ mieleathome:
+ plugin_name: mieleathome
+ miele_cycle: 120
+ miele_client_id: ''
+ miele_client_secret: ''
+ miele_client_country: 'de-DE'
+ miele_user: '' # email-address
+ miele_pwd: '' # Miele-PWD
+
+Items
+-----
+
+Es wird eine vorgefertigtes "Struct" für alle Geräte mitgeliefert. Es muss lediglich die Miele-"DeviceID" beim jeweiligen Gerät
+definiert werden. Um die Miele-"DeviceID" zu ermitteln kann das Plugin ohne Items eingebunden und gestartet werden. Es werden im Web-IF
+des Plugins alle registrierten Geräte mit der jeweiligen DeviceID angezeigt. Führende Nullen der DeviceID sind zu übernehmen.
+
+
+.. code-block:: yaml
+
+ # items/item.yaml
+ MieleDevices:
+ Freezer:
+ type: str
+ miele_deviceid: 'XXXXXXXXXXX'
+ struct: mieleathome.child
+ Dryer:
+ type: str
+ miele_deviceid: 'YYYYYYYYYYY'
+ struct: mieleathome.child
+
+smartVISU
+---------
+
+Es gibt eine vorgefertigte miele.html im Plugin-Ordner. Hier kann man die jeweiligen Optionen herauslesen und nach
+den eigenen Anforderungen anpassen und in den eigenen Seiten verwenden.
+
+.. image:: assets/smartvisu.png
+ :height: 939px
+ :width: 945px
+ :scale: 50%
+ :alt: Smartvisu
+ :align: center
+
+Web Interface
+=============
+
+Das Web Interface listet sämtliche mit dem Plugin verbundene Items, deren Typ und Wert.
+
+.. image:: assets/webif_items.png
+ :height: 550px
+ :width: 942px
+ :scale: 100%
+ :alt: Webif1
+ :align: center
+
+
+Im zweiten Tab sind die Device ID, das verknüpfte Item, Gerätetyp und -modell zu sehen.
+Dieser Tab kann auch dazu genutzt werden, die DeviceID auszulesen, die in weiterer Folge
+in der items.yaml Datei eingetragen werden muss - siehe Konfiguration.
+
+.. image:: assets/webif_devices.png
+ :height: 264px
+ :width: 944px
+ :scale: 100%
+ :alt: Webif2
+ :align: center
+
+
+Der dritte Tab gibt Einblick in Device und Action Events, hier können z.B. Aktionen zur Temperatureinstellung eines Kühlschranks
+angelegt oder geändert werden.
+
+.. image:: assets/webif_events.png
+ :height: 541px
+ :width: 944px
+ :scale: 100%
+ :alt: Webif3
+ :align: center
diff --git a/modbus_tcp/__init__.py b/modbus_tcp/__init__.py
index b4b32e836..2c79e019a 100755
--- a/modbus_tcp/__init__.py
+++ b/modbus_tcp/__init__.py
@@ -35,17 +35,7 @@
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
-# pymodbus library from https://github.com/riptideio/pymodbus
-from pymodbus.version import version
-
-pymodbus_baseversion = int(version.short().split('.')[0])
-
-if pymodbus_baseversion > 2:
- # for newer versions of pymodbus
- from pymodbus.client.tcp import ModbusTcpClient
-else:
- # for older versions of pymodbus
- from pymodbus.client.sync import ModbusTcpClient
+from pymodbus.client.tcp import ModbusTcpClient
AttrAddress = 'modBusAddress'
AttrType = 'modBusDataType'
@@ -63,7 +53,7 @@ class modbus_tcp(SmartPlugin):
devices.
"""
- PLUGIN_VERSION = '1.0.9'
+ PLUGIN_VERSION = '1.0.10'
def __init__(self, sh, *args, **kwargs):
"""
@@ -172,18 +162,18 @@ def parse_item(self, item):
if self.has_iattr(item.conf, AttrWordOrder):
wordOrder = self.get_iattr_value(item.conf, AttrWordOrder)
if byteOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
- byteOrder = Endian.Big
+ byteOrder = Endian.BIG
elif byteOrder == 'Endian.Little':
- byteOrder = Endian.Little
+ byteOrder = Endian.LITTLE
else:
- byteOrder = Endian.Big
+ byteOrder = Endian.BIG
self.logger.warning("Invalid byte order -> default(Endian.Big) is used")
if wordOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
- wordOrder = Endian.Big
+ wordOrder = Endian.BIG
elif wordOrder == 'Endian.Little':
- wordOrder = Endian.Little
+ wordOrder = Endian.LITTLE
else:
- wordOrder = Endian.Big
+ wordOrder = Endian.BIG
self.logger.warning("Invalid byte order -> default(Endian.Big) is used")
regPara = {'regAddr': regAddr, 'slaveUnit': slaveUnit, 'dataType': dataType, 'factor': factor,
@@ -459,25 +449,13 @@ def __read_Registers(self, regPara):
# self.logger.debug(f"read {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount}")
if objectType == 'Coil':
- if pymodbus_baseversion > 2:
- result = self._Mclient.read_coils(address, registerCount, slave=slaveUnit)
- else:
- result = self._Mclient.read_coils(address, registerCount, unit=slaveUnit)
+ result = self._Mclient.read_coils(address, registerCount, slave=slaveUnit)
elif objectType == 'DiscreteInput':
- if pymodbus_baseversion > 2:
- result = self._Mclient.read_discrete_inputs(address, registerCount, slave=slaveUnit)
- else:
- result = self._Mclient.read_discrete_inputs(address, registerCount, unit=slaveUnit)
+ result = self._Mclient.read_discrete_inputs(address, registerCount, slave=slaveUnit)
elif objectType == 'InputRegister':
- if pymodbus_baseversion > 2:
- result = self._Mclient.read_input_registers(address, registerCount, slave=slaveUnit)
- else:
- result = self._Mclient.read_input_registers(address, registerCount, unit=slaveUnit)
+ result = self._Mclient.read_input_registers(address, registerCount, slave=slaveUnit)
elif objectType == 'HoldingRegister':
- if pymodbus_baseversion > 2:
- result = self._Mclient.read_holding_registers(address, registerCount, slave=slaveUnit)
- else:
- result = self._Mclient.read_holding_registers(address, registerCount, unit=slaveUnit)
+ result = self._Mclient.read_holding_registers(address, registerCount, slave=slaveUnit)
else:
self.logger.error(f"{AttrObjectType} not supported: {objectType}")
return None
diff --git a/modbus_tcp/_pv_1_0_9/__init__.py b/modbus_tcp/_pv_1_0_9/__init__.py
new file mode 100644
index 000000000..b4b32e836
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/__init__.py
@@ -0,0 +1,538 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+# Copyright 2022 De Filippis Ivan
+# Copyright 2022 Ronny Schulz
+# Copyright 2023 Bernd Meiners
+#########################################################################
+# This file is part of SmartHomeNG.
+#
+# Modbus_TCP plugin for SmartHomeNG
+#
+# SmartHomeNG is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SmartHomeNG is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SmartHomeNG. If not, see .
+#
+#########################################################################
+
+from lib.model.smartplugin import *
+from lib.item import Items
+from datetime import datetime
+import threading
+
+from .webif import WebInterface
+
+from pymodbus.constants import Endian
+from pymodbus.payload import BinaryPayloadDecoder
+from pymodbus.payload import BinaryPayloadBuilder
+
+# pymodbus library from https://github.com/riptideio/pymodbus
+from pymodbus.version import version
+
+pymodbus_baseversion = int(version.short().split('.')[0])
+
+if pymodbus_baseversion > 2:
+ # for newer versions of pymodbus
+ from pymodbus.client.tcp import ModbusTcpClient
+else:
+ # for older versions of pymodbus
+ from pymodbus.client.sync import ModbusTcpClient
+
+AttrAddress = 'modBusAddress'
+AttrType = 'modBusDataType'
+AttrFactor = 'modBusFactor'
+AttrByteOrder = 'modBusByteOrder'
+AttrWordOrder = 'modBusWordOrder'
+AttrSlaveUnit = 'modBusUnit'
+AttrObjectType = 'modBusObjectType'
+AttrDirection = 'modBusDirection'
+
+
+class modbus_tcp(SmartPlugin):
+ """
+ This class provides a Plugin for SmarthomeNG to read and or write to modbus
+ devices.
+ """
+
+ PLUGIN_VERSION = '1.0.9'
+
+ def __init__(self, sh, *args, **kwargs):
+ """
+ Initializes the Modbus TCP plugin.
+ The parameters are retrieved from get_parameter_value(parameter_name)
+ """
+
+ self.logger.info('Init modbus_tcp plugin')
+
+ # Call init code of parent class (SmartPlugin)
+ super().__init__()
+
+ self._host = self.get_parameter_value('host')
+ self._port = self.get_parameter_value('port')
+
+ self._cycle = self.get_parameter_value('cycle') # the frequency in seconds how often the device should be accessed
+ if self._cycle == 0:
+ self._cycle = None
+ self._crontab = self.get_parameter_value('crontab') # the more complex way to specify the device query frequency
+ if self._crontab == '':
+ self._crontab = None
+ if not (self._cycle or self._crontab):
+ self.logger.error(f"{self.get_fullname()}: no update cycle or crontab set. Modbus will not be queried automatically")
+
+ self._slaveUnit = int(self.get_parameter_value('slaveUnit'))
+ self._slaveUnitRegisterDependend = False
+
+ self._sh = sh
+ self._regToRead = {}
+ self._regToWrite = {}
+ self._pollStatus = {}
+ self.connected = False
+
+ self._Mclient = ModbusTcpClient(self._host, port=self._port)
+ self.lock = threading.Lock()
+
+ self.init_webinterface(WebInterface)
+
+ return
+
+ def run(self):
+ """
+ Run method for the plugin
+ """
+ self.logger.debug(f"Plugin '{self.get_fullname()}': run method called")
+ self.alive = True
+ if self._cycle or self._crontab:
+ # self.get_shortname()
+ self.scheduler_add('poll_device_' + self._host, self.poll_device, cycle=self._cycle, cron=self._crontab, prio=5)
+ #self.scheduler_add(self.get_shortname(), self._update_values_callback, prio=5, cycle=self._update_cycle, cron=self._update_crontab, next=shtime.now())
+ self.logger.debug(f"Plugin '{self.get_fullname()}': run method finished")
+
+ def stop(self):
+ """
+ Stop method for the plugin
+ """
+ self.alive = False
+ self.logger.debug(f"Plugin '{self.get_fullname()}': stop method called")
+ self.scheduler_remove('poll_device_' + self._host)
+ self._Mclient.close()
+ self.connected = False
+ self.logger.debug(f"Plugin '{self.get_fullname()}': stop method finished")
+
+ def parse_item(self, item):
+ """
+ Default plugin parse_item method. Is called when the plugin is initialized.
+ The plugin can, corresponding to its attribute keywords, decide what to do with
+ the item in future, like adding it to an internal array for future reference
+
+ :param item: The item to process.
+ """
+ if self.has_iattr(item.conf, AttrAddress):
+ self.logger.debug(f"parse item: {item}")
+ regAddr = int(self.get_iattr_value(item.conf, AttrAddress))
+
+ objectType = 'HoldingRegister'
+ value = item()
+ dataType = 'uint16'
+ factor = 1
+ byteOrder = 'Endian.Big'
+ wordOrder = 'Endian.Big'
+ slaveUnit = self._slaveUnit
+ dataDirection = 'read'
+
+ if self.has_iattr(item.conf, AttrType):
+ dataType = self.get_iattr_value(item.conf, AttrType)
+ if self.has_iattr(item.conf, AttrSlaveUnit):
+ slaveUnit = int(self.get_iattr_value(item.conf, AttrSlaveUnit))
+ if (slaveUnit) != self._slaveUnit:
+ self._slaveUnitRegisterDependend = True
+ if self.has_iattr(item.conf, AttrObjectType):
+ objectType = self.get_iattr_value(item.conf, AttrObjectType)
+
+ reg = str(objectType) # dictionary key: objectType.regAddr.slaveUnit // HoldingRegister.528.1
+ reg += '.'
+ reg += str(regAddr)
+ reg += '.'
+ reg += str(slaveUnit)
+
+ if self.has_iattr(item.conf, AttrDirection):
+ dataDirection = self.get_iattr_value(item.conf, AttrDirection)
+ if self.has_iattr(item.conf, AttrFactor):
+ factor = float(self.get_iattr_value(item.conf, AttrFactor))
+ if self.has_iattr(item.conf, AttrByteOrder):
+ byteOrder = self.get_iattr_value(item.conf, AttrByteOrder)
+ if self.has_iattr(item.conf, AttrWordOrder):
+ wordOrder = self.get_iattr_value(item.conf, AttrWordOrder)
+ if byteOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
+ byteOrder = Endian.Big
+ elif byteOrder == 'Endian.Little':
+ byteOrder = Endian.Little
+ else:
+ byteOrder = Endian.Big
+ self.logger.warning("Invalid byte order -> default(Endian.Big) is used")
+ if wordOrder == 'Endian.Big': # Von String in Endian-Konstante "umwandeln"
+ wordOrder = Endian.Big
+ elif wordOrder == 'Endian.Little':
+ wordOrder = Endian.Little
+ else:
+ wordOrder = Endian.Big
+ self.logger.warning("Invalid byte order -> default(Endian.Big) is used")
+
+ regPara = {'regAddr': regAddr, 'slaveUnit': slaveUnit, 'dataType': dataType, 'factor': factor,
+ 'byteOrder': byteOrder,
+ 'wordOrder': wordOrder, 'item': item, 'value': value, 'objectType': objectType,
+ 'dataDir': dataDirection}
+ if dataDirection == 'read':
+ self._regToRead.update({reg: regPara})
+ self.logger.info(f"parse item: {item} Attributes {regPara}")
+ elif dataDirection == 'read_write':
+ self._regToRead.update({reg: regPara})
+ self._regToWrite.update({reg: regPara})
+ self.logger.info(f"parse item: {item} Attributes {regPara}")
+ return self.update_item
+ else:
+ self.logger.warning("Invalid data direction -> default(read) is used")
+ self._regToRead.update({reg: regPara})
+
+ def poll_device(self):
+ """
+ Polls for updates of the device
+
+ This method is only needed, if the device (hardware/interface) does not propagate
+ changes on it's own, but has to be polled to get the actual status.
+ It is called by the scheduler which is set within run() method.
+ """
+
+ with self.lock:
+ try:
+ if self._Mclient.connect():
+ self.logger.info(f"connected to {str(self._Mclient)}")
+ self.connected = True
+ else:
+ self.logger.error(f"could not connect to {self._host}:{self._port}")
+ self.connected = False
+ return
+
+ except Exception as e:
+ self.logger.error(f"connection exception: {str(self._Mclient)} {e}")
+ self.connected = False
+ return
+
+ startTime = datetime.now()
+ regCount = 0
+ try:
+ for reg, regPara in self._regToRead.items():
+ with self.lock:
+ regAddr = regPara['regAddr']
+ value = self.__read_Registers(regPara)
+ # self.logger.debug(f"value read: {value} type: {type(value)}")
+ if value is not None:
+ item = regPara['item']
+ if regPara['factor'] != 1:
+ value = value * regPara['factor']
+ # self.logger.debug(f"value {value} multiply by: {regPara['factor']}")
+ item(value, self.get_fullname())
+ regCount += 1
+
+ if 'read_dt' in regPara:
+ regPara['last_read_dt'] = regPara['read_dt']
+
+ if 'value' in regPara:
+ regPara['last_value'] = regPara['value']
+
+ regPara['read_dt'] = datetime.now()
+ regPara['value'] = value
+ endTime = datetime.now()
+ duration = endTime - startTime
+ if regCount > 0:
+ self._pollStatus['last_dt'] = datetime.now()
+ self._pollStatus['regCount'] = regCount
+ self.logger.debug(f"poll_device: {regCount} register read required {duration} seconds")
+ except Exception as e:
+ self.logger.error(f"something went wrong in the poll_device function: {e}")
+
+ # called each time an item changes.
+ def update_item(self, item, caller=None, source=None, dest=None):
+ """
+ Item has been updated
+
+ This method is called, if the value of an item has been updated by SmartHomeNG.
+ It should write the changed value out to the device (hardware/interface) that
+ is managed by this plugin.
+
+ :param item: item to be updated towards the plugin
+ :param caller: if given it represents the callers name
+ :param source: if given it represents the source
+ :param dest: if given it represents the dest
+ """
+ objectType = 'HoldingRegister'
+ slaveUnit = self._slaveUnit
+ dataDirection = 'read'
+
+ if caller == self.get_fullname():
+ # self.logger.debug(f'item was changed by the plugin itself - caller:{caller} source:{source} dest:{dest}')
+ return
+
+ if self.has_iattr(item.conf, AttrDirection):
+ dataDirection = self.get_iattr_value(item.conf, AttrDirection)
+ if not dataDirection == 'read_write':
+ self.logger.debug(f'update_item: {item} Writing is not allowed - selected dataDirection:{dataDirection}')
+ return
+ # else:
+ # self.logger.debug(f'update_item:{item} dataDirection: {dataDirection}')
+ if self.has_iattr(item.conf, AttrAddress):
+ regAddr = int(self.get_iattr_value(item.conf, AttrAddress))
+ else:
+ self.logger.warning(f'update_item:{item} Item has no register address')
+ return
+ if self.has_iattr(item.conf, AttrSlaveUnit):
+ slaveUnit = int(self.get_iattr_value(item.conf, AttrSlaveUnit))
+ if (slaveUnit) != self._slaveUnit:
+ self._slaveUnitRegisterDependend = True
+ if self.has_iattr(item.conf, AttrObjectType):
+ objectType = self.get_iattr_value(item.conf, AttrObjectType)
+ else:
+ return
+
+ reg = str(objectType) # Dict-key: HoldingRegister.528.1 *** objectType.regAddr.slaveUnit ***
+ reg += '.'
+ reg += str(regAddr)
+ reg += '.'
+ reg += str(slaveUnit)
+ if reg in self._regToWrite:
+ with self.lock:
+ regPara = self._regToWrite[reg]
+ self.logger.debug(f'update_item:{item} value:{item()} regToWrite: {reg}')
+ try:
+ if self._Mclient.connect():
+ self.logger.info(f"connected to {str(self._Mclient)}")
+ self.connected = True
+ else:
+ self.logger.error(f"could not connect to {self._host}:{self._port}")
+ self.connected = False
+ return
+
+ except Exception as e:
+ self.logger.error(f"connection exception: {str(self._Mclient)} {e}")
+ self.connected = False
+ return
+
+ startTime = datetime.now()
+ regCount = 0
+ try:
+ self.__write_Registers(regPara, item())
+ except Exception as e:
+ self.logger.error(f"something went wrong in the __write_Registers function: {e}")
+
+ def __write_Registers(self, regPara, value):
+ objectType = regPara['objectType']
+ address = regPara['regAddr']
+ slaveUnit = regPara['slaveUnit']
+ bo = regPara['byteOrder']
+ wo = regPara['wordOrder']
+ dataTypeStr = regPara['dataType']
+ dataType = ''.join(filter(str.isalpha, dataTypeStr)) # vom dataType die Ziffen entfernen z.B. uint16 = uint
+ registerCount = 0 # Anzahl der zu schreibenden Register (Words)
+
+ try:
+ bits = int(''.join(filter(str.isdigit, dataTypeStr))) # bit-Zahl aus aus dataType z.B. uint16 = 16
+ except:
+ bits = 16
+
+ if dataType.lower() == 'string':
+ registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
+ else:
+ registerCount = int(bits / 16)
+
+ if regPara['factor'] != 1:
+ # self.logger.debug(f"value {value} divided by: {regPara['factor']}")
+ value = value * (1 / regPara['factor'])
+
+ self.logger.debug(f"write {value} to {objectType}.{address}.{address} (address.slaveUnit) dataType:{dataTypeStr}")
+ builder = BinaryPayloadBuilder(byteorder=bo, wordorder=wo)
+
+ if dataType.lower() == 'uint':
+ if bits == 16:
+ builder.add_16bit_uint(int(value))
+ elif bits == 32:
+ builder.add_32bit_uint(int(value))
+ elif bits == 64:
+ builder.add_64bit_uint(int(value))
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'int':
+ if bits == 16:
+ builder.add_16bit_int(int(value))
+ elif bits == 32:
+ builder.add_32bit_int(int(value))
+ elif bits == 64:
+ builder.add_64bit_int(int(value))
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'float':
+ if bits == 32:
+ builder.add_32bit_float(value)
+ elif bits == 64:
+ builder.add_64bit_float(value)
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'string':
+ builder.add_string(value)
+ elif dataType.lower() == 'bit':
+ if objectType == 'Coil' or objectType == 'DiscreteInput':
+ if not isinstance(value, bool): # test is boolean
+ self.logger.error(f"Value is not boolean: {value}")
+ return
+ else:
+ if set(value).issubset({'0', '1'}) and bool(value): # test is bit-string '00110101'
+ builder.add_bits(value)
+ else:
+ self.logger.error(f"Value is not a bitstring: {value}")
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ return None
+
+ if objectType == 'Coil':
+ result = self._Mclient.write_coil(address, value, unit=slaveUnit)
+ elif objectType == 'HoldingRegister':
+ registers = builder.to_registers()
+ result = self._Mclient.write_registers(address, registers, unit=slaveUnit)
+ elif objectType == 'DiscreteInput':
+ self.logger.warning(f"this object type cannot be written {objectType}:{address} slaveUnit:{slaveUnit}")
+ return
+ elif objectType == 'InputRegister':
+ self.logger.warning(f"this object type cannot be written {objectType}:{address} slaveUnit:{slaveUnit}")
+ return
+ else:
+ return
+ if result.isError():
+ self.logger.error(f"write error: {result} {objectType}.{address}.{slaveUnit} (address.slaveUnit)")
+ return None
+
+ if 'write_dt' in regPara:
+ regPara['last_write_dt'] = regPara['write_dt']
+ regPara['write_dt'] = datetime.now()
+ else:
+ regPara.update({'write_dt': datetime.now()})
+
+ if 'write_value' in regPara:
+ regPara['last_write_value'] = regPara['write_value']
+ regPara['write_value'] = value
+ else:
+ regPara.update({'write_value': value})
+
+ # regPara['write_dt'] = datetime.now()
+ # regPara['write_value'] = value
+
+ def __read_Registers(self, regPara):
+ objectType = regPara['objectType']
+ dataTypeStr = regPara['dataType']
+ dataType = ''.join(filter(str.isalpha, dataTypeStr))
+ bo = regPara['byteOrder']
+ wo = regPara['wordOrder']
+ slaveUnit = regPara['slaveUnit']
+ registerCount = 0
+ address = regPara['regAddr']
+ value = None
+
+ try:
+ bits = int(''.join(filter(str.isdigit, dataTypeStr)))
+ except:
+ bits = 16
+
+ if dataType.lower() == 'string':
+ registerCount = int(bits / 2) # bei string: bits = bytes !! string16 -> 16Byte - 8 registerCount
+ else:
+ registerCount = int(bits / 16)
+
+ if self.connected == False:
+ self.logger.error(f"not connected to {self._host}:{self._port}")
+ return None
+
+ # self.logger.debug(f"read {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount}")
+ if objectType == 'Coil':
+ if pymodbus_baseversion > 2:
+ result = self._Mclient.read_coils(address, registerCount, slave=slaveUnit)
+ else:
+ result = self._Mclient.read_coils(address, registerCount, unit=slaveUnit)
+ elif objectType == 'DiscreteInput':
+ if pymodbus_baseversion > 2:
+ result = self._Mclient.read_discrete_inputs(address, registerCount, slave=slaveUnit)
+ else:
+ result = self._Mclient.read_discrete_inputs(address, registerCount, unit=slaveUnit)
+ elif objectType == 'InputRegister':
+ if pymodbus_baseversion > 2:
+ result = self._Mclient.read_input_registers(address, registerCount, slave=slaveUnit)
+ else:
+ result = self._Mclient.read_input_registers(address, registerCount, unit=slaveUnit)
+ elif objectType == 'HoldingRegister':
+ if pymodbus_baseversion > 2:
+ result = self._Mclient.read_holding_registers(address, registerCount, slave=slaveUnit)
+ else:
+ result = self._Mclient.read_holding_registers(address, registerCount, unit=slaveUnit)
+ else:
+ self.logger.error(f"{AttrObjectType} not supported: {objectType}")
+ return None
+
+ if result.isError():
+ self.logger.error(f"read error: {result} {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount}")
+ return None
+
+ if objectType == 'Coil':
+ value = result.bits[0]
+ elif objectType == 'DiscreteInput':
+ value = result.bits[0]
+ elif objectType == 'InputRegister':
+ decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo)
+ else:
+ decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=bo, wordorder=wo)
+
+ self.logger.debug(f"read {objectType}.{address}.{slaveUnit} (address.slaveUnit) regCount:{registerCount} result:{result}")
+
+ if dataType.lower() == 'uint':
+ if bits == 16:
+ return decoder.decode_16bit_uint()
+ elif bits == 32:
+ return decoder.decode_32bit_uint()
+ elif bits == 64:
+ return decoder.decode_64bit_uint()
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'int':
+ if bits == 16:
+ return decoder.decode_16bit_int()
+ elif bits == 32:
+ return decoder.decode_32bit_int()
+ elif bits == 64:
+ return decoder.decode_64bit_int()
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'float':
+ if bits == 32:
+ return decoder.decode_32bit_float()
+ elif bits == 64:
+ return decoder.decode_64bit_float()
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ elif dataType.lower() == 'string':
+ # bei string: bits = bytes !! string16 -> 16Byte
+ ret = decoder.decode_string(bits)
+ return str(ret, 'ASCII')
+ elif dataType.lower() == 'bit':
+ if objectType == 'Coil' or objectType == 'DiscreteInput':
+ # self.logger.debug(f"read bit value: {value}")
+ return value
+ else:
+ self.logger.debug(f"read bits values: {value.decode_bits()}")
+ return decoder.decode_bits()
+ else:
+ self.logger.error(f"Number of bits or datatype not supported : {dataTypeStr}")
+ return None
diff --git a/modbus_tcp/_pv_1_0_9/assets/tab1_readed.png b/modbus_tcp/_pv_1_0_9/assets/tab1_readed.png
new file mode 100644
index 000000000..293a50df9
Binary files /dev/null and b/modbus_tcp/_pv_1_0_9/assets/tab1_readed.png differ
diff --git a/modbus_tcp/_pv_1_0_9/example.yaml b/modbus_tcp/_pv_1_0_9/example.yaml
new file mode 100644
index 000000000..4a85a6ff1
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/example.yaml
@@ -0,0 +1,211 @@
+paradigma:
+ TestInputRegister:
+ type: num
+ name: TestIR
+ enforce_updates: True
+ modBusObjectType: InputRegister
+ modBusAddress: 9997
+ modBusFactor: 0.1
+ modBusDataType: int16
+ TestHoldingRegister:
+ type: num
+ name: TestHR
+ enforce_updates: True
+ # modBusObjectType: HoldingRegister (default)
+ modBusAddress: 9998
+ modBusFactor: 0.1
+ modBusDataType: int16
+ TestCoil0:
+ type: bool
+ name: TestC0
+ enforce_updates: True
+ modBusObjectType: Coil
+ modBusAddress: 9996 #+1
+ modBusDataType: bit
+ TestCoil1:
+ type: bool
+ name: TestC1
+ enforce_updates: True
+ modBusObjectType: Coil
+ modBusAddress: 9997 #+1
+ modBusDataType: bit
+
+#solaredge_SE6000:
+Photovoltaik:
+ C_Version:
+ type: str
+ name: C_Version
+ modBusAddress: 40044
+ modBusDataType: string16
+ AC_Leistung_sf:
+ type: num
+ name: I_AC_Power_SF # SF- Skalierungsfaktor sunspec (z.B. -3)
+ enforce_updates: True
+ modBusAddress: 40084
+ modBusDataType: int16
+ AC_Leistung:
+ type: num
+ name: I_AC_Power # Die Leistung ergit sich aus dem erhaltenen Registerwert und dem Skalierungsfaktor (AC_Leistung_sf)
+ enforce_updates: True
+ eval: value*10**sh.Photovoltaik.AC_Leistung_sf() # Berechnung der Leistung (erhaltene Registerwert * 10^Skalierungsfaktor)
+ modBusAddress: 40083
+ modBusFactor: 0.001
+ DC_Leistung_sf:
+ type: num
+ name: I_DC_Power_SF
+ enforce_updates: True
+ modBusAddress: 40101
+ modBusDataType: int16
+ DC_Leistung:
+ type: num
+ name: I_DC_Power
+ eval: value*10**sh.Photovoltaik.DC_Leistung_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ enforce_updates: True
+ modBusAddress: 40100
+ modBusFactor: 0.001
+ Inverter_Temperatur_sf:
+ type: num
+ name: I_Temp_Sink_SF
+ enforce_updates: True
+ modBusAddress: 40106
+ modBusDataType: int16
+ Inverter_Temperatur:
+ type: num
+ name: I_Temp_Sink
+ database: yes
+ database_maxage: 365
+ eval: value*10**sh.Photovoltaik.Inverter_Temperatur_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ modBusAddress: 40103
+ modBusDataType: int16
+ Zaehler_Strom_sf:
+ type: num
+ name: M_AC_Current_SF
+ enforce_updates: True
+ modBusAddress: 40194
+ modBusDataType: int16
+ Zaehler_Strom:
+ type: num
+ name: M_AC_Current
+ enforce_updates: True
+ database: yes
+ database_maxage: 365
+ eval: value*10**sh.Photovoltaik.Zaehler_Strom_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ modBusAddress: 40190
+ Zaehler_Spannung_sf:
+ type: num
+ name: M_AC_Voltage_SF
+ enforce_updates: True
+ modBusAddress: 40203
+ modBusDataType: int16
+ Zaehler_Spannung:
+ type: num
+ name: M_AC_Voltage_L_N
+ enforce_updates: True
+ database: yes
+ database_maxage: 365
+ eval: value*10**sh.Photovoltaik.Zaehler_Spannung_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ modBusAddress: 40195
+ Zaehler_Frequenz_sf:
+ type: num
+ name: M_AC_Freq_SF
+ enforce_updates: True
+ modBusAddress: 40205
+ modBusDataType: int16
+ Zaehler_Frequenz:
+ type: num
+ name: M_AC_Freq
+ enforce_updates: True
+ database: yes
+ database_maxage: 365
+ eval: value*10**sh.Photovoltaik.Zaehler_Frequenz_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ modBusAddress: 40204
+ Zaehler_Leistung_sf:
+ type: num
+ name: M_AC_Power_SF
+ enforce_updates: True
+ modBusAddress: 40210
+ modBusDataType: int16
+ Zaehler_Leistung:
+ type: num
+ name: M_AC_Power
+ enforce_updates: True
+ eval: value*10**sh.Photovoltaik.Zaehler_Leistung_sf() # erhaltene Registerwert * 10^Skalierungsfaktor
+ modBusAddress: 40206
+ modBusDataType: int16
+ modBusFactor: 0.001
+ Status: # 0 Autark, 1 Export, 2 Import,
+ type: num
+ eval_trigger: Photovoltaik.Zaehler_Leistung
+ eval: 1 if sh.Photovoltaik.Zaehler_Leistung() > 0.025 else 2 if sh.Photovoltaik.Zaehler_Leistung() < -0.025 else 0
+ StatusText:
+ type: str
+ eval_trigger: Photovoltaik.Zaehler_Leistung
+ eval: "'Exportieren' if sh.Photovoltaik.Zaehler_Leistung() > 0.025 else 'Importieren' if sh.Photovoltaik.Zaehler_Leistung() < -0.025 else ''"
+ Speicher_Leistung:
+ type: num
+ name: S_Power
+ enforce_updates: True
+ modBusAddress: 59764
+ modBusDataType: float32
+ modBusFactor: 0.001
+ Speicher_Energie:
+ type: num
+ name: S_Available_Energy
+ modBusAddress: 59776
+ modBusDataType: float32
+ modBusFactor: 0.001
+ Speicher_SOE:
+ type: num
+ name: S_SOE
+ modBusAddress: 59780
+ modBusDataType: float32
+ Speicher_Status:
+ type: num
+ name: S_Status
+ modBusAddress: 59782
+ modBusDataType: uint32
+ Text:
+ type: str
+ eval_trigger: Photovoltaik.Speicher_Status
+ eval: sh..lookup()[value]
+ lookup:
+ type: dict
+ initial_value: "{0: 'Aus', 1: 'Standby', 2: 'Init', 3: 'Laden', 4: 'Entladen', 5: 'Fehler', 6: 'Leerlauf'}"
+
+Siemens_LOGO:
+ AI1:
+ type: num
+ name: AI1
+ modBusObjectType: InputRegister
+ modBusAddress: 0
+ #modBusFactor: 0.1
+ #modBusDataType: int16
+ AM1:
+ type: num
+ name: AM1
+ modBusObjectType: HoldingRegister
+ modBusAddress: 528
+ modBusDirection: read_write
+ #modBusFactor: 1
+ #modBusDataType: int16
+ M1:
+ type: bool
+ name: M1
+ modBusObjectType: Coil
+ modBusAddress: 8256
+ modBusDataType: bit
+ modBusDirection: read_write
+ I1:
+ type: bool
+ name: I1
+ modBusObjectType: DiscreteInput
+ modBusAddress: 0
+ modBusDataType: bit
+ VM0:
+ type: num
+ name: VM0
+ modBusObjectType: HoldingRegister
+ modBusAddress: 0
+ modBusDirection: read_write
+ modBusFactor: 0.01
+ #modBusDataType: int16
diff --git a/modbus_tcp/_pv_1_0_9/plugin.yaml b/modbus_tcp/_pv_1_0_9/plugin.yaml
new file mode 100644
index 000000000..83bdf2cd2
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/plugin.yaml
@@ -0,0 +1,134 @@
+# Metadata for the Smart-Plugin
+plugin:
+ # Global plugin attributes
+ type: gateway
+ description:
+ de: 'Plugin zur Geräte-Anbindung über ModBus an SmartHomeNG'
+ en: 'Plugin to connect via modbus with SmartHomeNG'
+ maintainer: ivande
+ tester: NONE # Who tests this plugin?
+ state: ready
+ keywords: modbus_tcp modbus smartmeter inverter heatpump
+ #documentation: http://smarthomeng.de/user/plugins/modbus_tcp/user_doc.html
+ support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1154368-einbindung-von-modbus-tcp
+ version: 1.0.9 # Plugin version
+ sh_minversion: 1.8 # minimum shNG version to use this plugin
+ #sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
+ py_minversion: 3.6
+ # py_maxversion: # maximum Python version to use for this plugin (leave empty if latest)
+ multi_instance: True # plugin supports multi instance
+ restartable: unknown
+ classname: modbus_tcp # class containing the plugin
+
+parameters:
+ host:
+ type: ipv4
+ description:
+ de: 'IP Adresse des Modbus-Geraetes'
+ en: 'IP address from the modbus-device'
+
+ port:
+ type: int
+ valid_min: 0
+ valid_max: 65535
+ description:
+ de: 'modbus Port'
+ en: 'modbus port'
+
+ cycle:
+ type: int
+ default: 300
+ valid_min: 0
+ description:
+ de: 'Update Zyklus in Sekunden. Wenn der Wert 0 ist, wird keine Abfrage über cycle ausgeführt'
+ en: 'Update cycle in seconds. If value is 0 then noch query will be made by means of cycle'
+
+ crontab:
+ type: str
+ description:
+ de: 'Update mit Festlegung via Crontab'
+ en: 'Update by means of a crontab'
+
+ slaveUnit:
+ type: num
+ default: 1
+ description:
+ de: 'Slave-Addresse der zu lesenden Modbus-Einheit'
+ en: 'slave-address of the Modbus-Unit to read'
+
+item_attributes:
+ modBusObjectType:
+ type: str
+ default: 'HoldingRegister'
+ description:
+ de: 'Auswahl welcher Objekt-Type gelesen werden soll'
+ en: 'Selection of which object type should be read '
+ valid_list:
+ - 'Coil'
+ - 'DiscreteInput'
+ - 'InputRegister'
+ - 'HoldingRegister'
+
+ modBusAddress:
+ type: num
+ description:
+ de: 'Register Adresse welche gelesen werden soll'
+ en: 'register address to read'
+
+ modBusUnit:
+ type: num
+ default: 1 # als default wird die slaveUnit aus der plugin-Konfig verwendet
+ description:
+ de: 'Slave-Addresse der zu lesenden Modbus-Einheit (Unit aus der plugin-Konfig wird überschrieben)'
+ en: 'slave-address of the Modbus-Unit to read - (Value from plugin config will be overwritten)'
+
+ modBusDataType:
+ type: str
+ default: 'uint16'
+ description:
+ de: 'Datentyp vom zu lesenden Register (bit, int16 uint16 int32 uint32 float32 string16 stringNN)'
+ en: 'datatype from register to read (bit, int16 uint16 int32 uint32 float32 string16 stringNN)'
+
+ modBusDirection:
+ type: str
+ default: 'read'
+ description:
+ de: 'Datenrichtung'
+ en: 'data direction'
+ valid_list:
+ - 'read'
+ - 'read_write'
+ - 'write'
+
+ modBusByteOrder:
+ type: str
+ default: 'Endian.Big'
+ description:
+ de: 'Endian.Big oder Endian.Little'
+ en: 'Endian.Big or Endian.Little'
+ valid_list:
+ - 'Endian.Big'
+ - 'Endian.Little'
+
+ modBusWordOrder:
+ type: str
+ default: 'Endian.Big'
+ description:
+ de: 'Endian.Big oder Endian.Little'
+ en: 'Endian.Big or Endian.Little'
+ valid_list:
+ - 'Endian.Big'
+ - 'Endian.Little'
+
+ modBusFactor:
+ type: num
+ default: 1
+ description:
+ de: 'Faktor mit dem der gelesene Register-Wert multipliziert wird'
+ en: 'Factor by which the read register value is multiplied'
+
+item_structs: NONE
+
+plugin_functions: NONE
+
+logic_parameters: NONE
diff --git a/modbus_tcp/_pv_1_0_9/requirements.txt b/modbus_tcp/_pv_1_0_9/requirements.txt
new file mode 100644
index 000000000..04c18ec78
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/requirements.txt
@@ -0,0 +1,2 @@
+pymodbus>=2.3,<3.0;python_version<'3.8'
+pymodbus>=3.0.2;python_version>="3.8"
diff --git a/modbus_tcp/_pv_1_0_9/user_doc.rst b/modbus_tcp/_pv_1_0_9/user_doc.rst
new file mode 100644
index 000000000..22dd37d2e
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/user_doc.rst
@@ -0,0 +1,201 @@
+.. index:: modbus_tcp plugin
+.. index:: Plugins; modbus_tcp
+
+
+==========
+modbus_tcp
+==========
+
+.. image:: webif/static/img/plugin_logo.png
+ :alt: plugin logo
+ :width: 300px
+ :height: 300px
+ :scale: 50 %
+ :align: left
+
+SmarthomeNG plugin, zum Lesen von Register über ModBusTCP
+
+Anforderungen
+=============
+
+* Python > 3.6
+* pymodbus >= 2.5.3
+* SmarthomeNG >= 1.8.0
+
+pymodbus
+--------
+
+das Paket sollte automatisch von SH installiert werden.
+
+pymodbus - manuelle Installation:
+---------------------------------
+
+.. code:: shell-session
+
+ python3 -m pip install pymodbus --user --upgrade
+
+Konfiguration
+=============
+
+plugin.yaml
+-----------
+
+.. code-block:: yaml
+
+ solaredge:
+ plugin_name: modbus_tcp
+ instance: solaredge
+ host: 192.168.0.50
+ port: 502
+ cycle: 60
+ plugin_enabled: true
+
+ logoMB:
+ plugin_name: modbus_tcp
+ instance: logomb
+ host: 192.168.0.80
+ port: 502
+ cycle: 20
+ plugin_enabled: true
+
+* 'instance' = Name der Instanz, sollen mehrer Geräte angesprochen werden (Multiinstanz)
+
+Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
+
+
+items.yaml
+----------
+
+Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
+
+
+logic.yaml
+----------
+
+Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
+
+Beispiel: In der Datei example.yaml sind ein paar Items für einen Solaredge-Wechselrichter SE6000 angelegt.
+
+Funktionen
+~~~~~~~~~~
+
+Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
+
+
+Beispiele
+---------
+Beispiel für SH-Item's
+
+siehe auch example.yaml
+
+.. code-block:: yaml
+
+ mydevice:
+ AI1:
+ type: num
+ name: AI1
+ modBusObjectType: InputRegister #(optional) default: HoldingRegister
+ modBusAddress: 0
+ modBusDataType: int16 #(optional) default: uint16
+ #modBusByteOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ #modBusWordOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ modBusDirection: 'read_write' #(optional) default: 'read'
+ modBusUnit: '71' #(optional) default: slaveUnit aus der Plugin-Konfig
+ #modBusFactor: 0.1 #(optional) default: 1
+ #modBusDirection: read_write #(optional) default: 'read'
+ AM1:
+ type: num
+ name: AM1
+ modBusObjectType: HoldingRegister
+ modBusAddress: 528
+ modBusDirection: read_write
+ #modBusFactor: 1
+ #modBusDataType: int16
+ M1:
+ type: bool
+ name: M1
+ modBusObjectType: Coil
+ modBusAddress: 8256
+ modBusDataType: bit
+ modBusDirection: read_write
+ I1:
+ type: bool
+ name: I1
+ modBusObjectType: DiscreteInput
+ modBusAddress: 0
+ modBusDataType: bit
+ VM0:
+ type: num
+ name: VM0
+ modBusObjectType: HoldingRegister
+ modBusAddress: 0
+ modBusDirection: read_write
+ modBusFactor: 0.01
+
+ geraetename:
+ type: str
+ #modBusObjectType: HoldingRegister #(optional) default: HoldingRegister
+ modBusAddress: 40030
+ modBusDataType: 'string16' #(optional) default: uint16
+ #modBusFactor: '1000' #(optional) default: 1
+ modBusByteOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ modBusWordOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ modBusDirection: 'read_write' #(optional) default: 'read'
+ modBusUnit: '71' #(optional) default: slaveUnit aus der Plugin-Konfig
+ temperatur:
+ type: num
+ modBusAddress: 40052
+ modBusDataType: 'float32 #(optional) default: uint16
+ #modBusFactor: '1' #(optional) default: 1
+ modBusByteOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ modBusWordOrder: 'Endian.Little' #(optional) default: 'Endian.Big'
+ modBusUnit: '71' #(optional) default: slaveUnit aus der Plugin-Konfig
+
+ # Multiinstanz:
+ # Jedes Attribut mit der @ ergänzen. Der Name der Instance muss in der Plugin Konfiguration festgelegt werden.
+ M1:
+ type: bool
+ name: M1
+ modBusObjectType@logomb: Coil
+ modBusAddress@logomb: 8256
+ modBusDataType@logomb: bit
+ modBusDirection@logomb: read_write
+
+
+Changelog
+---------
+V1.0.9
+
+V1.0.8 Neuere Pymodbus Versionen können nun verwendet werden.
+ Di minimale Version für Pymodbus ist jetzt 2.5.3
+
+V1.0.7 Verbindung offen halten und lock nutzen um Thread Sicherheit zu erreichen (CaeruleusAqua and bmxp)
+ Fehler behoben: nicht deklarierte Variable "TypeStr" und "bitstr"
+
+V1.0.6 schreiben von Register (HoldingRegister, Coil)
+
+V1.0.5 kleine Fehler behoben
+
+V1.0.4 ObjectType hinzugefügt (HoldingRegister, InputRegister, DiscreteInput, Coil)
+ Multiinstanz hinzugefügt
+ Verbindung schliessen nach Abruf aller Register
+
+V1.0.3 slaveUnit - Fehler behoben (_regToRead-key (adress.unit))
+ Bug Web Interface (Anzeige der Adresse)
+ example.yaml verbessert
+
+V1.0.2 slaveUnit zu Items hinzugefügt
+
+V1.0.1 slaveUnit zu plugin-Paramter hinzugefügt
+
+V1.0.0 Initial plugin version
+
+
+Web Interface
+=============
+
+Das Plugin kann aus dem Admin Interface aufgerufen werden. Dazu auf der Seite Plugins in der entsprechenden
+Zeile das Icon in der Spalte **Web Interface** anklicken.
+
+.. image:: assets/tab1_readed.png
+ :class: screenshot
diff --git a/modbus_tcp/_pv_1_0_9/webif/__init__.py b/modbus_tcp/_pv_1_0_9/webif/__init__.py
new file mode 100644
index 000000000..486e5cb2a
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/webif/__init__.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+# Copyright 2020-
+#########################################################################
+# This file is part of SmartHomeNG.
+# https://www.smarthomeNG.de
+# https://knx-user-forum.de/forum/supportforen/smarthome-py
+#
+# Sample plugin for new plugins to run with SmartHomeNG version 1.5 and
+# upwards.
+#
+# SmartHomeNG is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SmartHomeNG is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SmartHomeNG. If not, see .
+#
+#########################################################################
+
+import datetime
+import time
+import os
+
+from lib.item import Items
+from lib.model.smartplugin import SmartPluginWebIf
+
+
+# ------------------------------------------
+# Webinterface of the plugin
+# ------------------------------------------
+
+import cherrypy
+import csv
+from jinja2 import Environment, FileSystemLoader
+
+
+class WebInterface(SmartPluginWebIf):
+
+ def __init__(self, webif_dir, plugin):
+ """
+ Initialization of instance of class WebInterface
+
+ :param webif_dir: directory where the webinterface of the plugin resides
+ :param plugin: instance of the plugin
+ :type webif_dir: str
+ :type plugin: object
+ """
+ self.logger = plugin.logger
+ self.webif_dir = webif_dir
+ self.plugin = plugin
+ self.items = Items.get_instance()
+
+ self.tplenv = self.init_template_environment()
+
+
+ @cherrypy.expose
+ def index(self, reload=None):
+ """
+ Build index.html for cherrypy
+
+ Render the template and return the html file to be delivered to the browser
+
+ :return: contents of the template after beeing rendered
+ """
+ tmpl = self.tplenv.get_template('index.html')
+ # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...)
+ return tmpl.render(p=self.plugin,
+ items=sorted(self.items.return_items(), key=lambda k: str.lower(k['_path'])),
+ item_count=0)
+
+ @cherrypy.expose
+ def get_data_html(self, dataSet=None):
+ """
+ Return data to update the webpage
+
+ For the standard update mechanism of the web interface, the dataSet to return the data for is None
+
+ :param dataSet: Dataset for which the data should be returned (standard: None)
+ :return: dict with the data needed to update the web page.
+ """
+ if dataSet is None:
+ # get the new data
+ data = {}
+
+ # data['item'] = {}
+ # for i in self.plugin.items:
+ # data['item'][i]['value'] = self.plugin.getitemvalue(i)
+ #
+ # return it as json the the web page
+ # try:
+ # return json.dumps(data)
+ # except Exception as e:
+ # self.logger.error("get_data_html exception: {}".format(e))
+ return {}
+
diff --git a/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_green.png b/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_green.png
new file mode 100644
index 000000000..fb130568b
Binary files /dev/null and b/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_green.png differ
diff --git a/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_red.png b/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_red.png
new file mode 100644
index 000000000..00fc04c90
Binary files /dev/null and b/modbus_tcp/_pv_1_0_9/webif/static/img/lamp_red.png differ
diff --git a/modbus_tcp/_pv_1_0_9/webif/static/img/plugin_logo.png b/modbus_tcp/_pv_1_0_9/webif/static/img/plugin_logo.png
new file mode 100644
index 000000000..4f849e35c
Binary files /dev/null and b/modbus_tcp/_pv_1_0_9/webif/static/img/plugin_logo.png differ
diff --git a/modbus_tcp/_pv_1_0_9/webif/templates/index.html b/modbus_tcp/_pv_1_0_9/webif/templates/index.html
new file mode 100644
index 000000000..75651666b
--- /dev/null
+++ b/modbus_tcp/_pv_1_0_9/webif/templates/index.html
@@ -0,0 +1,198 @@
+{% extends "base_plugin.html" %}
+{% block pluginscripts %}
+
+{% endblock pluginscripts %}
+{% set logo_frame = false %}
+{% set use_bodytabs = false %}
+{% set tabcount = 1 %}
+
+{% block headtable %}
+
+
+
+
+
+ {% if p.connected %}
+
+ {% else %}
+
+ {% endif %}
+ {{ _('Connected') }}
+ |
+ {{ _('Host : Port') }} |
+ {{ p._host }} : {{ p._port }} |
+
+
+ {{ _('') }} |
+ {{ _('global slave unit') }} |
+ {{ p._slaveUnit }} |
+
+
+ {{ _('') }} |
+ {{ _('pol_device / cycle-time [seconds]') }} |
+ {{ p._cycle }} |
+
+
+ {% if 'last_dt' in p._pollStatus %}
+ {{ _('') }} |
+ {{ _('last_read (readed registers)') }} |
+ {{ p._pollStatus.last_dt.strftime('%d.%m.%Y %H:%M:%S %Z') }} ({{ p._pollStatus.regCount }}) |
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+
+{% block buttons %}
+{% if 1==2 %}
+
+{% endif %}
+{% endblock %}
+
+
+{% set tabcount = 2 %}
+
+
+{% if item_count==0 %}
+ {% set start_tab = 1 %}
+{% endif %}
+
+{% set tab1title = "registers to read (" ~ p._regToRead|length ~ ")" %}
+{% block bodytab1 %}
+
+
+
+
+
+
+ {{ _('ObjectType') }} |
+ {{ _('Address') }} |
+ {% if p._slaveUnitRegisterDependend %}
+ {{ _('Unit') }} |
+ {% endif %}
+ {{ _('Value') }} |
+ {{ _('Item') }} |
+ {{ _('DataType') }} |
+ {{ _('last_read') }} |
+ {{ _('prev_read') }} |
+ {{ _('prev_value') }} |
+
+
+
+
+ {% for item in p._regToRead %}
+
+ {{ p._regToRead[item].objectType }} |
+ {{ p._regToRead[item].regAddr }} |
+ {% if p._slaveUnitRegisterDependend %}
+ {{ p._regToRead[item].slaveUnit }} |
+ {% endif %}
+ {{ p._regToRead[item].value }} |
+ {{ p._regToRead[item].item }} |
+ {{ p._regToRead[item].dataType }} |
+
+ {% if 'read_dt' in p._regToRead[item] %}
+ {{ p._regToRead[item].read_dt.strftime('%d.%m.%Y %H:%M:%S %Z') }} |
+ {% else %}
+ |
+ {% endif %}
+ {% if 'last_read_dt' in p._regToRead[item] %}
+ {{ p._regToRead[item].last_read_dt.strftime('%d.%m.%Y %H:%M:%S %Z') }} |
+ {% else %}
+ |
+ {% endif %}
+ {{ p._regToRead[item].last_value }} |
+
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
+
+{% set tab2title = "registers to write (" ~ p._regToWrite|length ~ ")" %}
+{% block bodytab2 %}
+
+
+
+
+
+
+ {{ _('ObjectType') }} |
+ {{ _('Address') }} |
+ {% if p._slaveUnitRegisterDependend %}
+ {{ _('Unit') }} |
+ {% endif %}
+ {{ _('Value') }} |
+ {{ _('Item') }} |
+ {{ _('DataType') }} |
+ {{ _('last_write') }} |
+ {{ _('prev_write') }} |
+ {{ _('prev_value') }} |
+
+
+
+
+ {% for item in p._regToWrite %}
+
+ {{ p._regToWrite[item].objectType }} |
+ {{ p._regToWrite[item].regAddr }} |
+ {% if p._slaveUnitRegisterDependend %}
+ {{ p._regToWrite[item].slaveUnit }} |
+ {% endif %}
+ {% if 'write_value' in p._regToWrite[item] %}
+ {{ p._regToWrite[item].write_value }} |
+ {% else %}
+ |
+ {% endif %}
+ {{ p._regToWrite[item].item }} |
+ {{ p._regToWrite[item].dataType }} |
+
+ {% if 'write_dt' in p._regToWrite[item] %}
+ {{ p._regToWrite[item].write_dt.strftime('%d.%m.%Y %H:%M:%S %Z') }} |
+ {% else %}
+ |
+ {% endif %}
+ {% if 'last_write_dt' in p._regToWrite[item] %}
+ {{ p._regToWrite[item].last_write_dt.strftime('%d.%m.%Y %H:%M:%S %Z') }} |
+ {% else %}
+ |
+ {% endif %}
+ {{ p._regToWrite[item].last_write_value }} |
+
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
+
+
+
+
diff --git a/modbus_tcp/plugin.yaml b/modbus_tcp/plugin.yaml
index 83bdf2cd2..b2a137869 100755
--- a/modbus_tcp/plugin.yaml
+++ b/modbus_tcp/plugin.yaml
@@ -5,13 +5,13 @@ plugin:
description:
de: 'Plugin zur Geräte-Anbindung über ModBus an SmartHomeNG'
en: 'Plugin to connect via modbus with SmartHomeNG'
- maintainer: ivande
+ maintainer: ivande, Cannon
tester: NONE # Who tests this plugin?
state: ready
keywords: modbus_tcp modbus smartmeter inverter heatpump
#documentation: http://smarthomeng.de/user/plugins/modbus_tcp/user_doc.html
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1154368-einbindung-von-modbus-tcp
- version: 1.0.9 # Plugin version
+ version: 1.0.10 # Plugin version
sh_minversion: 1.8 # minimum shNG version to use this plugin
#sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
py_minversion: 3.6
diff --git a/modbus_tcp/requirements.txt b/modbus_tcp/requirements.txt
index 04c18ec78..df27c8584 100755
--- a/modbus_tcp/requirements.txt
+++ b/modbus_tcp/requirements.txt
@@ -1,2 +1 @@
-pymodbus>=2.3,<3.0;python_version<'3.8'
-pymodbus>=3.0.2;python_version>="3.8"
+pymodbus>=3.5.2;python_version>='3.8'
diff --git a/modbus_tcp/user_doc.rst b/modbus_tcp/user_doc.rst
index 22dd37d2e..5323449c5 100755
--- a/modbus_tcp/user_doc.rst
+++ b/modbus_tcp/user_doc.rst
@@ -164,6 +164,8 @@ siehe auch example.yaml
Changelog
---------
+V1.0.10 Mindestversion für pymodbus ist nun 3.5.2
+
V1.0.9
V1.0.8 Neuere Pymodbus Versionen können nun verwendet werden.
diff --git a/neato/user_doc.rst b/neato/user_doc.rst
index 2871982fb..f557bbc16 100755
--- a/neato/user_doc.rst
+++ b/neato/user_doc.rst
@@ -40,11 +40,12 @@ Vorwerk VR300 ja ja
Authentifizierung
=================
-Das Plugin unterstützt zwei verschiedene Arten der Authentifizierung mit dem Neato oder Vorwerk Backend:
+Das Plugin unterstützt zwei verschiedene Arten der Authentifizierung mit dem Neato oder Vorwerk Backend:
a) Authentifizierung über Emailadresse des Nutzerkontos und zugehöriges Passwort. Nutzbar für Neato und alte Vorwerk API
-.. code-block:: html
+.. code-block:: yaml
+
Neato:
plugin_name: neato
account_email: 'your_neato_account_email'
@@ -53,7 +54,7 @@ a) Authentifizierung über Emailadresse des Nutzerkontos und zugehöriges Passwo
b) Oauth2 Authentifizierung über Emailadresse des Nutzerkontos und Token. Nutzbar nur für Vorwerk mit dem aktuellen MyKobol APP Interface
-.. code-block:: html
+.. code-block:: yaml
Neato:
plugin_name: neato
@@ -64,18 +65,18 @@ b) Oauth2 Authentifizierung über Emailadresse des Nutzerkontos und Token. Nutzb
Der Token kann hier kompfortabel über die Schritt für Schritt Anleitung des Plugin Webinterfaces generiert werden, siehe Vorwerk OAuth2 Tab.
Wenn eine Nutzung des Webinterfaces nicht möglich ist, kann ein Token auch manuell generiert werden. Hierzu:
-
-a) Neato plugin aktivieren und Emailadresse des Vorwerk Nutzerkontos konfigurieren.
+
+a) Neato plugin aktivieren und Emailadresse des Vorwerk Nutzerkontos konfigurieren.
b) Plugin Logging auf Level INFO stellen (in logger.yaml oder via Admin Interface)
-c) Plugin Funktion request_oauth2_code ausführen. Hierbei wird ein Code bei Vorwerk angefragt, welcher an die oben angegebene Emaildresse gesendet wird.
+c) Plugin Funktion request_oauth2_code ausführen. Hierbei wird ein Code bei Vorwerk angefragt, welcher an die oben angegebene Emaildresse gesendet wird.
-d) Nach Erhalt des Codes die Plugin Funktion request_oauth2_token(code) ausführen, wobei als Argument der per Email erhaltene Code übergeben wird.
+d) Nach Erhalt des Codes die Plugin Funktion request_oauth2_token(code) ausführen, wobei als Argument der per Email erhaltene Code übergeben wird.
e) Im Logfile nach dem generierten ASCII Token im Hexadezimalformat suchen
-f) Das Hex ASCII Token in der plugin.yaml angeben.
+f) Das Hex ASCII Token in der plugin.yaml angeben.
@@ -107,13 +108,13 @@ Roboter Status
Das String Item für den Roboterstatus (state) kann folgende Zustände einnehmen:
======================= ====
-Roboterstatus (state)
+Roboterstatus (state)
======================= ====
-invalid
-idle
-busy
-paused
-error
+invalid
+idle
+busy
+paused
+error
======================= ====
@@ -148,7 +149,7 @@ Das Num Item für die Roboterbefehle (command) kann folgende Zustände einnehmen
============================= =========
Befehl (command) dezimal
============================= =========
-Start cleaning 61
+Start cleaning 61
Stop cleaning 62
Pause cleaning 63
Resume cleaning 64
diff --git a/pluggit/README.md b/pluggit/README.md
index faedd299e..4705e5663 100755
--- a/pluggit/README.md
+++ b/pluggit/README.md
@@ -24,6 +24,13 @@ struct: pluggit.pluggit
## Änderungen:
+V2.0.6 - 15.09.2023
+- Anpassung für pymodbus 3.5.2: bytorder und wordorder musste korrrigiert werden, statt Little und Big nun LITTLE und BIG
+- Python muss >= 3.8 sein und pymodbus >= 3.5.2
+
+V2.05 - 11.09.2023
+- unter Python 10 muss mindestens die Version 3.3.2 von pymodbus laufen, da sonst Verbindungsprobleme
+
V2.0.4 - 13.11.2022
- Verbesserungen zur Versionsprüfung "pymodbus"
diff --git a/pluggit/__init__.py b/pluggit/__init__.py
index 514e01872..3c7c34986 100755
--- a/pluggit/__init__.py
+++ b/pluggit/__init__.py
@@ -29,23 +29,15 @@
import logging
from lib.model.smartplugin import SmartPlugin
-# pymodbus library from https://github.com/riptideio/pymodbus
-from pymodbus.version import version
-pymodbus_baseversion = int(version.short().split('.')[0])
-
-if pymodbus_baseversion > 2:
- # for newer versions of pymodbus
- from pymodbus.client.tcp import ModbusTcpClient
-else:
- # for older versions of pymodbus
- from pymodbus.client.sync import ModbusTcpClient
+# pymodbus library from https://github.com/pymodbus-dev/pymodbus
+from pymodbus.client.tcp import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
class Pluggit(SmartPlugin):
- PLUGIN_VERSION="2.0.4"
+ PLUGIN_VERSION="2.0.6"
_itemReadDictionary = {}
_itemWriteDictionary = {}
@@ -319,6 +311,7 @@ def update_item(self, item, caller=None, source=None, dest=None):
if pluggit_paramList[self.DICT_WRITE_ADDRESS] != -1:
# check for conditions
# unit mode = manual?
+ self.logger.debug(f"Pluggit SendKey: {pluggit_sendkey}")
if pluggit_sendkey == 'prmRomIdxSpeedLevel':
self.SetUnitMode('UM_ManualMode', True)
# check for conversion
@@ -378,15 +371,15 @@ def SetUnitMode(self, modekey, enable):
if unitstate != -1:
pluggit_paramList = self._modbusRegisterDictionary['prmRamIdxUnitMode']
registerValue = self._Pluggit.read_holding_registers(pluggit_paramList[self.DICT_READ_ADDRESS], pluggit_paramList[self.DICT_ADDRESS_QUANTITY])
- vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.Big, wordorder=Endian.Little)
+ vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.BIG, wordorder=Endian.LITTLE)
readItemValue = vdecoder.decode_16bit_uint()
#if readItemValue & unitstate != unitstate:
# workaround for manual bypass timeout
- self.logger.info('SetUnitMode(): Mode \"{}\".'.format(modekey))
#if modekey == 'UM_ManualBypass' & bool(enable):
#self._Pluggit.write_registers(pluggit_paramList[self.DICT_WRITE_ADDRESS], unitmode[self.UM_DICT_VALUE_DISABLE])
#time.sleep(0.5)
# write value to registers
+ self.logger.debug('SetUnitMode(): Mode \"{}\".'.format(modekey))
self._Pluggit.write_registers(pluggit_paramList[self.DICT_WRITE_ADDRESS], unitstate)
time.sleep(0.5)
else:
@@ -472,7 +465,7 @@ def _refresh(self):
registerValue = self._Pluggit.read_holding_registers(pluggit_paramList[self.DICT_READ_ADDRESS], pluggit_paramList[self.DICT_ADDRESS_QUANTITY])
# TODO: auswerten, wenn Reigister nicht auslesbar
readCacheDictionary[pluggit_key] = registerValue
- vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.Big, wordorder=Endian.Little)
+ vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.BIG, wordorder=Endian.LITTLE)
readItemValue = None
diff --git a/pluggit/_pv_1_2_2/README.md b/pluggit/_pv_1_2_2/README.md
deleted file mode 100755
index 0d4b43ca1..000000000
--- a/pluggit/_pv_1_2_2/README.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# PLUGGIT
-
-## Requirements
-
-This plugin is based on the lib pymodbus (branch python3):
-https://github.com/bashwork/pymodbus
-https://github.com/bashwork/pymodbus/tree/python3
-
-## Supported Hardware
-
-It is currently working and tested with:
-
- * Pluggit AP310
-
-## Configuration
-
-### plugin.yaml
-
-The plugin can be configured like this:
-
-```yaml
-pluggit:
- class_name: Pluggit
- class_path: plugins.pluggit
- host: 192.168.0.222
- # cycle: 300
-```
-
-This plugin retrieves data from the KWL Pluggit AP310 based on the modbus register description from the official pluggit homepage ( http://www.pluggit.com/portal/de/faq/bms-building-management-system/verbindung-mit-building-management-system-9737 )
-
-The data retrieval is done by establishing a modbus tcp network connection via modbus port 502.
-You need to configure the host (or IP) address of your pluggit KWL.
-
-The cycle parameter defines the update interval and defaults to 300 seconds.
-
-### items.yaml
-
-#### pluggit
-
-This attribute references the information to retrieve by the plugin.
-The following list of information can be specified:
-
- * prmRamIdxUnitMode: Active Unit mode> 0x0004 Manual Mode; 0x0008 WeekProgram
- * prmNumOfWeekProgram: Number of the Active Week Program (for Week Program mode)
- * prmRomIdxSpeedLevel: Speed level of Fans in Manual mode; shows a current speed level [4-0]; used for changing of the fan speed level
- * prmFilterRemainingTime: Remaining time of the Filter Lifetime (Days)
- * prmRamIdxT1: Frischluft temperature in degree
- * prmRamIdxT2: Zuluft temperature in degree
- * prmRamIdxT3: Abluft temperature in degree
- * prmRamIdxT4: Fortluft temperature in degree
- * prmRamIdxBypassActualState: Bypass state> Closed 0x0000; In process 0x0001; Closing 0x0020; Opening 0x0040; Opened 0x00FF
- * activatePowerBoost: bool variable that changes the Unit Mode to manual mode and sets the fan speed level to the highest level (4)
-
-#### Example
-
-Example configuration which shows the current unit mode, the actual week program, the fan speed, the remaining filter lifetime and the bypass state.
-
-```yaml
-pluggit:
- type: foo
-
- unitMode:
- type: str
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxUnitMode
-
- weekProgram:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmNumOfWeekProgram
-
- fanSpeed:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRomIdxSpeedLevel
-
- remainingFilterLifetime:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmFilterRemainingTime
-
- frischluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT1
-
- zuluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT2
-
- abluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT3
-
- fortluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT4
-
- bypassState:
- type: str
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxBypassActualState
-
- activatePowerBoost:
- type: bool
- visu_acl: rw
- enforce_updates: 'true'
- pluggit_send: activatePowerBoost
-```
-
-## logic.yaml
-
-No logic related stuff implemented.
-
-## Methods
-
-No methods provided currently.
diff --git a/pluggit/_pv_1_2_2/__init__.py b/pluggit/_pv_1_2_2/__init__.py
deleted file mode 100755
index 96403b4bd..000000000
--- a/pluggit/_pv_1_2_2/__init__.py
+++ /dev/null
@@ -1,331 +0,0 @@
-#!/usr/bin/env python3
-# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
-#########################################################################
-# Copyright 2017 Henning Behrend; Version 2.0
-#########################################################################
-# This file is part of SmartHomeNG.
-# https://github.com/smarthomeNG/smarthome
-# http://knx-user-forum.de/
-#
-# SmartHomeNG is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SmartHomeNG is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SmartHomeNG. If not, see .
-#########################################################################
-
-import time
-import threading
-import logging
-from lib.model.smartplugin import SmartPlugin
-
-# import pydevd
-
-# pymodbus library from https://code.google.com/p/pymodbus/
-from pymodbus.client.sync import ModbusTcpClient
-from pymodbus.constants import Endian
-from pymodbus.payload import BinaryPayloadDecoder
-from pymodbus.payload import BinaryPayloadBuilder
-
-class PluggitException(Exception):
- pass
-
-class Pluggit(SmartPlugin):
- ALLOW_MULTIINSTANCE = False
- PLUGIN_VERSION="1.2.2"
-
- _myTempReadDict = {}
- _myTempWriteDict = {}
- #============================================================================#
- # define variables for most important modbus registers of KWL "Pluggit AP310"
- #
- # Important: In the PDU Registers are addressed starting at zero.
- # Therefore registers numbered 1-16 are addressed as 0-15.
- # that means e.g. holding register "40169" is "40168" and so on
- #============================================================================#
-
- # dictionary for modbus registers
- _modbusRegisterDic = {
- # 'prmDateTime': 108, # 40109: Current Date/time in Unix time (amount of seconds from 1.1.1970)
- 'prmRamIdxT1': 133, # 40133: T1, °C
- 'prmRamIdxT2': 135, # 40135: T2, °C
- 'prmRamIdxT3': 137, # 40137: T3, °C
- 'prmRamIdxT4': 139, # 40139: T4, °C
- # 'prmRamIdxT5': 140, # 40141: T5, °C
- 'prmRamIdxUnitMode': 168, # 40169: Active Unit mode> 0x0004 Manual Mode; 0x0008 WeekProgram
- 'prmRamIdxBypassActualState': 198, # 40199: Bypass state> Closed 0x0000; In process 0x0001; Closing 0x0020; Opening 0x0040; Opened 0x00FF
- 'prmRomIdxSpeedLevel': 324, # 40325: Speed level of Fans in Manual mode; shows a current speed level [4-0]; used for changing of the fan speed level
- # 'prmVOC': 430, # 40431: VOC sensor value (read from VOC); ppm. If VOC is not installed, then 0.
- # 'prmBypassTmin': 444, # 40445: Minimum temperature of Bypass openning (°C), if T1 < Tmin then bypass should be closed
- # 'prmBypassTmax': 446, # 40447: Maximum temperature of Bypass openning (°C), if T1 > Tmax or Tmax is 0 then bypass should be closed
- # 'prmWorkTime': 624 # 40625: Work time of system, in hour (UNIX)
- 'prmNumOfWeekProgram': 466, # 40467: Number of the Active Week Program (for Week Program mode)
- 'prmFilterRemainingTime': 554 # 40555: Remaining time of the Filter Lifetime (Days)
- }
-
-
- # Initialize connection
- def __init__(self, smarthome, host, port=502, cycle=50):
- self.logger = logging.getLogger(__name__)
- self._host = host
- self._port = int(port)
- self._sh = smarthome
- self._cycle = int(cycle)
- self._lock = threading.Lock()
- self._is_connected = False
- self._items = {}
- self.connect()
- # pydevd.settrace("10.20.0.125")
-
- def connect(self):
- start_time = time.time()
- if self._is_connected:
- return True
- self._lock.acquire()
- try:
- self.logger.info("Pluggit: connecting to {0}:{1}".format(self._host, self._port))
- self._Pluggit = ModbusTcpClient(self._host, self._port)
- except Exception as e:
- self.logger.error("Pluggit: could not connect to {0}:{1}: {2}".format(self._host, self._port, e))
- return
- finally:
- self._lock.release()
- self.logger.info("Pluggit: connected to {0}:{1}".format(self._host, self._port))
- self._is_connected = True
- end_time = time.time()
- self.logger.debug("Pluggit: connection took {0} seconds".format(end_time - start_time))
-
- def disconnect(self):
- start_time = time.time()
- if self._is_connected:
- try:
- self._Pluggit.close()
- except:
- pass
- self._is_connected = False
- end_time = time.time()
- self.logger.debug("Pluggit: disconnect took {0} seconds".format(end_time - start_time))
-
- def run(self):
- self.alive = True
- self._sh.scheduler.add('Pluggit', self._refresh, cycle=self._cycle)
-
- def stop(self):
- self.alive = False
-
- # parse items in pluggit.conf
- def parse_item(self, item):
- # check for smarthome.py attribute 'pluggit_listen' in pluggit.conf
- if self.has_iattr(item.conf, 'pluggit_listen'):
- self.logger.debug("Pluggit: parse read item: {0}".format(item))
- pluggit_key = self.get_iattr_value(item.conf, 'pluggit_listen')
- if pluggit_key in self._modbusRegisterDic:
- self._myTempReadDict[pluggit_key] = item
- self.logger.debug("Pluggit: Inhalt des dicts _myTempReadDict nach Zuweisung zu item: '{0}'".format(self._myTempReadDict))
- else:
- self.logger.warn("Pluggit: invalid key {0} configured".format(pluggit_key))
- elif self.has_iattr(item.conf, 'pluggit_send'):
- self.logger.debug("Pluggit: parse send item: {0}".format(item))
- pluggit_sendKey = self.get_iattr_value(item.conf, 'pluggit_send')
- if pluggit_sendKey is None:
- return None
- else:
- self._myTempWriteDict[pluggit_sendKey] = item
- self.logger.debug("Pluggit: Inhalt des dicts _myTempWriteDict nach Zuweisung zu send item: '{0}'".format(self._myTempWriteDict))
- return self.update_item
- else:
- return None
-
- def parse_logic(self, logic):
- pass
-
- def update_item(self, item, caller=None, source=None, dest=None):
- if caller != 'Pluggit':
- if self.has_iattr(item.conf, 'pluggit_send'):
- command = self.get_iattr_value(item.conf, 'pluggit_send')
- value = item()
- self.logger.info("Pluggit: {0} set {1} to {2} for {3}".format(caller, command, value, item.id()))
- if(command == 'activatePowerBoost') and (isinstance(value, bool)):
- if value:
- self._activatePowerBoost()
- else:
- self._activateWeekProgram()
- pass
-
- def _activatePowerBoost(self):
-
- active_unit_mode_value = 4, 0
- fan_speed_level_value = 4, 0
-
- # Change Unit Mode to manual
- self.logger.debug("Pluggit: Start => Change Unit mode to manual: {0}".format(active_unit_mode_value))
- self._Pluggit.write_registers(self._modbusRegisterDic['prmRamIdxUnitMode'], active_unit_mode_value)
- self.logger.debug("Pluggit: Finished => Change Unit mode to manual: {0}".format(active_unit_mode_value))
-
- # wait 500ms before changing fan speed
- self.logger.debug("Pluggit: Wait 500ms before changing fan speed")
- time.sleep(0.5)
-
- # Change Fan Speed to highest speed
- self.logger.debug("Pluggit: Start => Change Fan Speed to Level 4")
- self._Pluggit.write_registers(self._modbusRegisterDic['prmRomIdxSpeedLevel'], fan_speed_level_value)
- self.logger.debug("Pluggit: Finished => Change Fan Speed to Level 4")
-
- # self._refresh()
- # check new active unit mode
- active_unit_mode = self._Pluggit.read_holding_registers(self._modbusRegisterDic['prmRamIdxUnitMode'], read_qty = 1).getRegister(0)
-
- if active_unit_mode == 8:
- self.logger.debug("Pluggit: Active Unit Mode: Week program")
- elif active_unit_mode == 4:
- self.logger.debug("Pluggit: Active Unit Mode: Manual")
-
- # check new fan speed
- fan_speed_level = self._Pluggit.read_holding_registers(self._modbusRegisterDic['prmRomIdxSpeedLevel'], read_qty = 1).getRegister(0)
- self.logger.debug("Pluggit: Fan Speed: {0}".format(fan_speed_level))
-
- def _activateWeekProgram(self):
-
- active_unit_mode_value = 8, 0
-
- # Change Unit Mode to "Week Program"
- self.logger.debug("Pluggit: Start => Change Unit mode to 'Week Program': {0}".format(active_unit_mode_value))
- self._Pluggit.write_registers(self._modbusRegisterDic['prmRamIdxUnitMode'], active_unit_mode_value)
- self.logger.debug("Pluggit: Finished => Change Unit mode to 'Week Program': {0}".format(active_unit_mode_value))
-
- # self._refresh()
-
- # check new active unit mode
- active_unit_mode = self._Pluggit.read_holding_registers(self._modbusRegisterDic['prmRamIdxUnitMode'], read_qty = 1).getRegister(0)
-
- if active_unit_mode == 8:
- self.logger.debug("Pluggit: Active Unit Mode: Week program")
- elif active_unit_mode == 4:
- self.logger.debug("Pluggit: Active Unit Mode: Manual")
-
- # wait 100ms before checking fan speed
- time.sleep(0.1)
-
- # check new fan speed
- fan_speed_level = self._Pluggit.read_holding_registers(self._modbusRegisterDic['prmRomIdxSpeedLevel'], read_qty = 1).getRegister(0)
- self.logger.debug("Pluggit: Fan Speed: {0}".format(fan_speed_level))
-
- def _refresh(self):
- start_time = time.time()
- try:
- myCounter = 1
- for pluggit_key in self._myTempReadDict:
- self.logger.debug("Pluggit: ---------------------------------> Wir sind in der Refresh Schleife")
- values = self._modbusRegisterDic[pluggit_key]
- self.logger.debug("Pluggit: Refresh Schleife: Inhalt von values ist {0}".format(values))
- # 2015-01-07 23:53:08,296 DEBUG Pluggit Pluggit: Refresh Schleife: Inhalt von values ist 168 -- __init__.py:_refresh:158
- item = self._myTempReadDict[pluggit_key]
- self.logger.debug("Pluggit: Refresh Schleife: Inhalt von item ist {0}".format(item))
- # 2015-01-07 23:53:08,316 DEBUG Pluggit Pluggit: Refresh Schleife: Inhalt von item ist pluggit.unitMode -- __init__.py:_refresh:160
-
- #=======================================================#
- # read values from pluggit via modbus client registers
- #=======================================================#
-
- self.logger.debug("Pluggit: ------------------------------------------> Wir sind vor dem Auslesen der Werte")
- registerValue = None
- registerValue = self._Pluggit.read_holding_registers(values, read_qty = 1).getRegister(0)
- self.logger.debug("Pluggit: Read parameter '{0}' with register '{1}': Value is '{2}'".format(pluggit_key, values, registerValue))
-
- # week program: possible values 0-10
- if values == self._modbusRegisterDic['prmNumOfWeekProgram']:
- registerValue += 1
- item(registerValue, 'Pluggit')
- # 2015-01-07 23:53:08,435 DEBUG Pluggit Item pluggit.unitMode = 8 via Pluggit None None -- item.py:__update:363
- self.logger.debug("Pluggit: Week Program Number: {0}".format(registerValue))
- # 2015-01-07 23:53:08,422 DEBUG Pluggit Pluggit: Active Unit Mode: Week program -- __init__.py:_refresh:177
-
- # active unit mode
- if values == self._modbusRegisterDic['prmRamIdxUnitMode'] and registerValue == 8:
- self.logger.debug("Pluggit: Active Unit Mode: Week program")
- item('Woche', 'Pluggit')
- if values == self._modbusRegisterDic['prmRamIdxUnitMode'] and registerValue == 4:
- self.logger.debug("Pluggit: Active Unit Mode: Manual")
- item('Manuell', 'Pluggit')
-
- # fan speed
- if values == self._modbusRegisterDic['prmRomIdxSpeedLevel']:
- self.logger.debug("Pluggit: Fan Speed: {0}".format(registerValue))
- item(registerValue, 'Pluggit')
-
- # remaining filter lifetime
- if values == self._modbusRegisterDic['prmFilterRemainingTime']:
- self.logger.debug("Pluggit: Remaining filter lifetime: {0}".format(registerValue))
- item(registerValue, 'Pluggit')
-
- # bypass state
- if values == self._modbusRegisterDic['prmRamIdxBypassActualState'] and registerValue == 255:
- self.logger.debug("Pluggit: Bypass state: opened")
- item('geöffnet', 'Pluggit')
- if values == self._modbusRegisterDic['prmRamIdxBypassActualState'] and registerValue == 0:
- self.logger.debug("Pluggit: Bypass state: closed")
- item('geschlossen', 'Pluggit')
-
- # Temperatures
- # Frischluft außen
- if values == self._modbusRegisterDic['prmRamIdxT1']:
- t1 = self._Pluggit.read_holding_registers(values, 2, unit=22)
- decodert1 = BinaryPayloadDecoder.fromRegisters(t1.registers, endian=Endian.Big)
- t1 = decodert1.decode_32bit_float()
- t1 = round(t1, 2)
- self.logger.debug("Pluggit: Frischluft außen: {0:4.1f}".format(t1))
- self.logger.debug("Pluggit: Frischluft außen: {0}".format(t1))
- item(t1, 'Pluggit')
-
- # Zuluft innen
- if values == self._modbusRegisterDic['prmRamIdxT2']:
- t2 = self._Pluggit.read_holding_registers(values, 2, unit=22)
- decodert2 = BinaryPayloadDecoder.fromRegisters(t2.registers, endian=Endian.Big)
- t2 = decodert2.decode_32bit_float()
- t2 = round(t2, 2)
- self.logger.debug("Pluggit: Zuluft innen: {0:4.1f}".format(t2))
- self.logger.debug("Pluggit: Zuluft innen: {0}".format(t2))
- item(t2, 'Pluggit')
-
- # Abluft innen
- if values == self._modbusRegisterDic['prmRamIdxT3']:
- t3 = self._Pluggit.read_holding_registers(values, 2, unit=22)
- decodert3 = BinaryPayloadDecoder.fromRegisters(t3.registers, endian=Endian.Big)
- t3 = decodert3.decode_32bit_float()
- t3 = round(t3, 2)
- self.logger.debug("Pluggit: Abluft innen: {0:4.1f}".format(t3))
- self.logger.debug("Pluggit: Abluft innen: {0}".format(t3))
- item(t3, 'Pluggit')
-
- # Fortluft außen
- if values == self._modbusRegisterDic['prmRamIdxT4']:
- t4 = self._Pluggit.read_holding_registers(values, 2, unit=22)
- decodert4 = BinaryPayloadDecoder.fromRegisters(t4.registers, endian=Endian.Big)
- t4 = decodert4.decode_32bit_float()
- t4 = round(t4, 2)
- self.logger.debug("Pluggit: Fortluft außen: {0:4.1f}".format(t4))
- self.logger.debug("Pluggit: Fortluft außen: {0}".format(t4))
- item(t4, 'Pluggit')
-
- self.logger.debug("Pluggit: ------------------------------------------> Ende der Schleife vor sleep, Durchlauf Nr. {0}".format(myCounter))
- time.sleep(0.1)
- myCounter += 1
-
- except Exception as e:
- self.logger.error("Pluggit: something went wrong in the refresh function: {0}".format(e))
- return
- end_time = time.time()
- cycletime = end_time - start_time
- self.logger.debug("Pluggit: cycle took {0} seconds".format(cycletime))
-
-if __name__ == '__main__':
- logging.basicConfig(level=logging.INFO)
- myplugin = Plugin('Pluggit')
- myplugin.run()
diff --git a/pluggit/_pv_1_2_2/pluggit.yaml b/pluggit/_pv_1_2_2/pluggit.yaml
deleted file mode 100755
index 968a0d026..000000000
--- a/pluggit/_pv_1_2_2/pluggit.yaml
+++ /dev/null
@@ -1,62 +0,0 @@
-pluggit:
- type: foo
-
- unitMode:
- type: str
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxUnitMode
-
- weekProgram:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmNumOfWeekProgram
-
- fanSpeed:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRomIdxSpeedLevel
-
- remainingFilterLifetime:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmFilterRemainingTime
-
- frischluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT1
-
- zuluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT2
-
- abluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT3
-
- fortluft:
- type: num
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxT4
-
- bypassState:
- type: str
- visu_acl: ro
- enforce_updates: 'true'
- pluggit_listen: prmRamIdxBypassActualState
-
- activatePowerBoost:
- type: bool
- visu_acl: rw
- enforce_updates: 'true'
- pluggit_send: activatePowerBoost
diff --git a/pluggit/_pv_1_2_2/plugin.yaml b/pluggit/_pv_1_2_2/plugin.yaml
deleted file mode 100755
index f0c368dc3..000000000
--- a/pluggit/_pv_1_2_2/plugin.yaml
+++ /dev/null
@@ -1,105 +0,0 @@
-# Metadata for the Smart-Plugin
-plugin:
- # Global plugin attributes
- type: interface # plugin type (gateway, interface, protocol, system, web)
- description:
- de: 'Anbindung einer KWL Pluggit AP310 über das Modbus Protokoll'
- en: 'Connection of a Pluggit AP310 unit using Modbus protocol'
- maintainer: '? (Henning Behrend / ratzi82)'
- tester: '?' # Who tests this plugin?
- state: ready
- keywords: modbus
-# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page
-# support: https://knx-user-forum.de/forum/supportforen/smarthome-py
-
- version: 1.2.2 # Plugin version
- sh_minversion: 1.2 # minimum shNG version to use this plugin
-# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
- multi_instance: False # plugin supports multi instance
- restartable: unknown
- classname: Pluggit # class containing the plugin
-
-parameters:
- # Definition of parameters to be configured in etc/plugin.yaml
- host:
- type: str
- description:
- de: "Hostname oder IP Adresse des Pluggit Dienstes"
- en: "Hostname or IP address of the Pluggit service"
-
- port:
- type: int
- default: 502
- valid_min: 0
- valid_max: 65535
- description:
- de: "Port Nummer des Pluggit Dienstes"
- en: "Port number of Pluggit service"
-
- cycle:
- type: int
- default: 50
- description:
- de: "Zykluszeit"
- en: "Cycle time"
-
-
-item_attributes:
- # Definition of item attributes defined by this plugin
-
- pluggit_listen:
- type: str
- valid_list:
- - prmRamIdxUnitMode
- - prmNumOfWeekProgram
- - prmRomIdxSpeedLevel
- - prmFilterRemainingTime
- - prmRamIdxT1
- - prmRamIdxT2
- - prmRamIdxT3
- - prmRamIdxT4
- valid_list_description:
- de:
- - Aktiver Modus der Einheit> 0x0004 Manueller Modus; 0x0008 Wochenprogramm
- - Nummer des Wochenprogrammes (für Week Programmier-Modus)
- - Geschwindigkeit des Lüfters im manuellen Modus; zeigt den aktuellen Gschwindigkeits-Level [4-0]
- - Verbleibende Zeit der Lebensdauer des Filters (in Tagen)
- - Frischluft Temperatur in °C
- - Zuluft Temperatur in °C
- - Abluft Temperatur in °C
- - Fortluft Temperatur in °C
- en:
- - Active Unit mode> 0x0004 Manual Mode; 0x0008 WeekProgram
- - Number of the Active Week Program (for Week Program mode)
- - Speed level of Fans in Manual mode; shows a current speed level [4-0]; used for changing of the fan speed level
- - Remaining time of the Filter Lifetime (Days)
- - Frischluft temperature in degree
- - Zuluft temperature in degree
- - Abluft temperature in degree
- - Fortluft temperature in degree
- description:
- de: "In das Item zu lesende Daten"
- en: "Data to be read into the item"
-
- pluggit_send:
- type: str
- valid_list:
- - activatePowerBoost
- valid_list_description:
- de:
- - Bool Variable welche den Unit Mode auf manuellen Modus ändert und den Lüfter Gschwindikeits-Level auf den höchsten Wert (4) setzt.
- en:
- - bool variable that changes the Unit Mode to manual mode and sets the fan speed level to the highest level (4)
- description:
- de: "In die Pluggit Einheit zu schreibender Wert"
- en: "Value to be written to Plugitt unit"
-
-item_structs: NONE
- # Definition of item-structure templates for this plugin
-
-plugin_functions: NONE
- # Definition of plugin functions defined by this plugin
-
-logic_parameters: NONE
- # Definition of logic parameters defined by this plugin
-
diff --git a/pluggit/_pv_2_0_4/README.md b/pluggit/_pv_2_0_4/README.md
new file mode 100644
index 000000000..faedd299e
--- /dev/null
+++ b/pluggit/_pv_2_0_4/README.md
@@ -0,0 +1,58 @@
+# pluggit
+SmartHomeNG plugin pluggit
+
+Das SmartHomeNG plugin für eine Pluggit AP310 KWL
+
+## Einbindung in der plugin.yaml mit:
+
+```python
+pluggit:
+ class_name: Pluggit
+ class_path: plugins.pluggit
+ host: 192.168.1.4
+ #cycle: 300
+```
+
+host = IP-Adresse der pluggit
+cycle = Update-Interfall der abzufragenden Werte (optional)
+
+## Einbindung in den Items per Item-Struct:
+
+```python
+struct: pluggit.pluggit
+```
+
+## Änderungen:
+
+V2.0.4 - 13.11.2022
+- Verbesserungen zur Versionsprüfung "pymodbus"
+
+V2.0.3 - 25.10.2022
+- Support für pymodbus 3.0
+
+22.05.2022:
+- Fehler mit manuellem Bypass behoben
+
+16.02.2022:
+- CurentUnitMode.ManualBypass dem Item-struct zugefügt
+- Log-Level für verschiedene Ausgaben angepasst
+- CurrentUnitMode.AwayMode repariert
+
+24.02.2021:
+- Item-struct um Zugriffe für SmartVISU erweitert
+- item_attribut um pluggit_convert erweitert
+- scheduler.remove eingebaut
+
+29.08.2020:
+ - bool-Werte konnten nicht geschrieben werden
+
+## Folgende Vorteile ergeben sich zu dem Plugin 1.x
+
+- wesentlich mehr Parameter der pluggit können abgefragt werden
+- einige Parameter lassen sich auch schreiben
+- die Werte können intern auch konvertiert werden, sodass man eine vernünftige Ausgabe erhält
+
+## Es fehlen auch noch ein paar Dinge:
+
+- die Programmierung des Auto-Wochenprogramms ist noch nicht implementiert
+- eine Dokumentation der Parameter
diff --git a/pluggit/_pv_2_0_4/__init__.py b/pluggit/_pv_2_0_4/__init__.py
new file mode 100644
index 000000000..514e01872
--- /dev/null
+++ b/pluggit/_pv_2_0_4/__init__.py
@@ -0,0 +1,542 @@
+#!/usr/bin/env python3
+#
+#########################################################################
+# Copyright 2017 Henning Behrend
+# Copyright 2020-2022 Ronny Schulz
+#########################################################################
+#
+# This file is part of SmartHomeNG.
+#
+# SmartHomeNG is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+#
+# SmartHomeNG is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SmartHomeNG. If not, see .
+#
+#########################################################################
+
+from datetime import datetime
+import time
+import threading
+import logging
+from lib.model.smartplugin import SmartPlugin
+
+# pymodbus library from https://github.com/riptideio/pymodbus
+from pymodbus.version import version
+pymodbus_baseversion = int(version.short().split('.')[0])
+
+if pymodbus_baseversion > 2:
+ # for newer versions of pymodbus
+ from pymodbus.client.tcp import ModbusTcpClient
+else:
+ # for older versions of pymodbus
+ from pymodbus.client.sync import ModbusTcpClient
+
+from pymodbus.constants import Endian
+from pymodbus.payload import BinaryPayloadDecoder
+from pymodbus.payload import BinaryPayloadBuilder
+
+class Pluggit(SmartPlugin):
+ PLUGIN_VERSION="2.0.4"
+
+ _itemReadDictionary = {}
+ _itemWriteDictionary = {}
+
+ DICT_READ_ADDRESS = 0
+ DICT_WRITE_ADDRESS = 1
+ DICT_ADDRESS_QUANTITY = 2
+ DICT_VALUE_TYPE = 3
+ DICT_ALLOWED_CONV_LIST = 4
+ DICT_ROUND_VALUE = 5
+ DICT_MIN_VALUE = 6
+ DICT_MAX_VALUE = 7
+
+ #============================================================================#
+ # define variables for most important modbus registers of KWL "Pluggit AP310"
+ #
+ # Important: In the PDU Registers are addressed starting at zero.
+ # Therefore registers numbered 1-16 are addressed as 0-15.
+ # that means e.g. holding register "40169" is "40168" and so on
+ #============================================================================#
+
+ # 1: Bezeichnung des Registers (key)
+ # 2: Lese-Adresse des Registers (0)
+ # 3: Schreib-Adresse des Registers (1)
+ # 4: Anzahl der zu lesenden/schreibenden Register (2)
+ # 5: Typ des Wertes (3)
+ # 6: erlaubte Umwandlungen des Types (4)
+ # 7: Anzahl Kommastellen bei Rundungen oder -1 für keine (5)
+ # 8: minimaler Wert oder -1 für nicht verwendet (6)
+ # 9: maximaler Wert oder -1 für nicht verwendet (7)
+ _modbusRegisterDictionary = {
+ 'prmSystemID': [2, -1, 2, 'uint', ['SID_FP1', 'SID_Week', 'SID_Bypass', 'SID_LRSwitch', 'SID_InternalPreheater', 'SID_ServoFlow', 'SID_RHSensor', 'SID_VOCSensor', 'SID_ExtOverride', 'SID_HAC1', 'SID_HRC2', 'SID_PCTool', 'SID_Apps', 'SID_ZeegBee', 'SID_DI1Override', 'SID_DI2Override'], -1, -1, -1],
+ 'prmSystemSerialNum': [4, -1, 4, 'uint', [], -1, -1, -1],
+ 'prmSystemName': [8, 8, 16, 'str', [], -1, -1, -1],
+ 'prmFWVersion': [24, -1, 1, 'version', [], -1, -1, -1],
+ 'prmDHCPEN': [26, -1, 1, 'bool', [], -1, 0, 1],
+ 'prmCurrentIPAddress': [28, -1, 2, 'ip', [], -1, -1, -1],
+ 'prmCurrentIPMask': [32, -1, 2, 'ip', [], -1, -1, -1],
+ 'prmCurrentIPGateway': [36, -1, 2, 'ip', [], -1, -1, -1],
+ 'prmMACAddr': [40, -1, 4, 'mac', [], -1, -1, -1],
+ 'prmHALLeft': [84, -1, 1, 'bool', [], -1, 0, 1],
+ 'prmHALRight': [86, -1, 1, 'bool', [], -1, 0, 1],
+ 'prmHALTaho1': [100, -1, 2, 'float', [], 0, 0, 5000],
+ 'prmHALTaho2': [102, -1, 2, 'float', [], 0, 0, 5000],
+ 'prmDateTime': [108, 110, 2, 'timestamp', ['ToDateTime', 'ToDate', 'ToTime'], -1, -1, -1],
+ 'prmDateTimeSet': [108, 110, 2, 'timestamp', ['ToDateTime', 'ToDate', 'ToTime'], -1, -1, -1],
+ 'prmRamIdxT1': [132, -1, 2, 'float', [], 2, -327.67, 327.67],
+ 'prmRamIdxT2': [134, -1, 2, 'float', [], 2, -327.67, 327.67],
+ 'prmRamIdxT3': [136, -1, 2, 'float', [], 2, -327.67, 327.67],
+ 'prmRamIdxT4': [138, -1, 2, 'float', [], 2, -327.67, 327.67],
+ 'prmRamIdxT5': [140, -1, 2, 'float', [], 2, -327.67, 327.67],
+ 'prmPreheaterDutyCycle': [160, -1, 1, 'uint', [], -1, 0, 100],
+ 'prmRamIdxUnitMode': [168, 168, 1, 'uint', ['UM_DemandMode', 'UM_ManualMode', 'UM_WeekProgramMode', 'UM_AwayMode', 'UM_FireplaceMode', 'UM_SummerMode', 'UM_NightMode', 'UM_ManualBypass'], -1, 0, 65535],
+ 'prmRamIdxHac1FirmwareVersion': [192, -1, 2, 'version_bcd', [], -1, -1, -1],
+ 'prmRamIdxRh3Corrected': [196, -1, 1, 'uint', [], -1, 0, 100],
+ 'prmRamIdxBypassActualState': [198, -1, 1, 'uint', ['ToBool', 'ToBool_inverted', 'ToString'], -1, 0, 255],
+ 'prmRamIdxHac1Components': [244, -1, 1, 'uint', ['ToString'], -1, 0, 255],
+ 'prmRamIdxBypassManualTimeout': [264, -1, 1, 'uint', [], -1, 60, 480],
+ 'prmRomIdxSpeedLevel': [324, 324, 1, 'uint', [], -1, 0, 4],
+ 'prmRomIdxNightModeStartHour': [332, 332, 1, 'uint', [], -1, 0, 23],
+ 'prmRomIdxNightModeStartMin': [334, 334, 1, 'uint', [], -1, 0, 59],
+ 'prmRomIdxNightModeEndHour': [336, 336, 1, 'uint', [], -1, 0, 23],
+ 'prmRomIdxNightModeEndMin': [338, 338, 1, 'uint', [], -1, 0, 59],
+ 'prmRomIdxRhSetPoint': [340, -1, 1, 'uint', [], -1, 35, 65],
+ 'prmRomIdxAfterHeaterT2SetPoint': [344, 344, 1, [], 'uint', -1, 0, 30],
+ 'prmRomIdxAfterHeaterT3SetPoint': [346, 346, 1, [], 'uint', -1, 0, 30],
+ 'prmRomIdxAfterHeaterT5SetPoint': [348, 348, 1, [], 'uint', -1, 0, 30],
+ 'prmVOC': [430, -1, 1, 'uint', [], -1, 0, 65536],
+ 'prmBypassTmin': [444, -1, 2, 'float', [], 1, 12.0, 15.0],
+ 'prmBypassTmax': [446, -1, 2, 'float', [], 1, 21.0, 27.0],
+ 'prmNumOfWeekProgram': [466, 467, 1, 'uint', [], -1, 0, 10],
+ 'prmCurrentBLState': [472, -1, 1, 'uint', ['ToString'], -1, -1, -1],
+ 'prmSetAlarmNum': [514, 514, 1, 'uint', [], -1, 0, 15],
+ 'prmLastActiveAlarm': [516, 516, 1, 'uint', ['ToString'], -1, -1, -1],
+ 'prmRefValEx': [518, -1, 1, 'uint', [], -1, 0, 65535],
+ 'prmRefValSupl': [520, -1, 1, 'uint', [], -1, 0, 65535],
+ 'prmFireplacePreset': [540, -1, 1, 'bool', [], -1, 0, 1],
+ 'prmFilterRemainingTime': [554, -1, 1, 'uint', [], -1, 0, 360],
+ 'prmFilterDefaultTime': [556, 556, 1, 'uint', [], -1, 0, 360],
+ 'prmFilterReset': [558, 558, 1, 'bool', [], -1, 0, 1],
+ 'prmPPM1Unit': [562, 562, 2, 'uint', [], -1, 0, 65535],
+ 'prmPPM2Unit': [564, 564, 2, 'uint', [], -1, 0, 65535],
+ 'prmPPM3Unit': [566, 566, 2, 'uint', [], -1, 0, 65535],
+ 'prmPPM1External': [568, 568, 1, 'uint', [], -1, 0, 65535],
+ 'prmPPM2External': [570, 570, 1, 'uint', [], -1, 0, 65535],
+ 'prmPPM3External': [572, 572, 1, 'uint', [], -1, 0, 65535],
+ 'prmHACCO2Val': [574, -1, 1, 'uint', [], -1, 0, 65535],
+ 'prmSystemIDComponents': [611, -1, 2, 'uint', [], -1, -1, -1],
+ 'prmWorkTime': [624, -1, 2, 'uint', [], -1, -1, -1],
+ 'PrmWeekMon': [626, 626, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekTue': [632, 632, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekWed': [638, 638, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekThu': [644, 644, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekFri': [650, 650, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekSat': [656, 656, 6, 'weekprogram', [], -1, -1, -1],
+ 'PrmWeekSun': [662, 662, 6, 'weekprogram', [], -1, -1, -1],
+ 'prmStartExploitationDateStamp': [668, -1, 2, 'timestamp', ['ToDateTime', 'ToDate'], -1, -1, -1]
+ }
+
+ SID_DICT_VALUE_ENABLE = 0
+ SID_DICT_TEXT = 1
+
+ _SystemIDDict = {
+ 'SID_FP1': [0x0001, 'FP1'],
+ 'SID_Week': [0x0002, 'Week'],
+ 'SID_Bypass': [0x0004, 'Bypass'],
+ 'SID_LRSwitch': [0x0008, 'LRSwitch'],
+ 'SID_InternalPreheater': [0x0010, 'Internal preheater'],
+ 'SID_ServoFlow': [0x0020, 'Servo flow'],
+ 'SID_RHSensor': [0x0040, 'RH sensor'],
+ 'SID_VOCSensor': [0x0080, 'VOC sensor'],
+ 'SID_ExtOverride': [0x0100, 'Ext Override'],
+ 'SID_HAC1': [0x0200, 'HAC1'],
+ 'SID_HRC2': [0x0400, 'HRC2'],
+ 'SID_PCTool': [0x0800, 'PC Tool'],
+ 'SID_Apps': [0x1000, 'Apps'],
+ 'SID_ZeegBee': [0x2000, 'ZeegBee'],
+ 'SID_DI1Override': [0x4000, 'DI1 Override'],
+ 'SID_DI2Override': [0x8000, 'DI2 Override']
+ }
+
+ _CurrentBLStateDic = {
+ 0: 'Standby',
+ 1: 'Manual',
+ 2: 'Demand',
+ 3: 'Week program',
+ 4: 'Servo-flow',
+ 5: 'Away',
+ 6: 'Summer',
+ 7: 'DI Override',
+ 8: 'Hygrostat override',
+ 9: 'Fireplace',
+ 10: 'Installer',
+ 11: 'Fail Safe 1',
+ 12: 'Fail Safe 2',
+ 13: 'Fail Off',
+ 14: 'Defrost Off',
+ 15: 'Defrost',
+ 16: 'Night'
+ }
+
+ UM_DICT_VALUE_ENABLE = 0
+ UM_DICT_VALUE_DISABLE = 1
+ UM_DICT_TEXT = 2
+
+ _RamIdxUnitModeDic = {
+ 'UM_DemandMode': [0x0002, -1, 'Demand Mode'],
+ 'UM_ManualMode': [0x0004, -1, 'Manual Mode'],
+ 'UM_WeekProgramMode': [0x0008, -1, 'Week Program Mode',],
+ 'UM_AwayMode': [0x0010, 0x8010, 'Away Mode',],
+ 'UM_NightMode': [0x0020, 0x8020, 'Night Mode'],
+ 'UM_FireplaceMode': [0x0040, 0x8040, 'Fireplace Mode',],
+ 'UM_ManualBypass': [0x0080, 0x8080, 'Manual Bypass'],
+ 'UM_SummerMode': [0x0800, 0x8800, 'Summer Mode']
+ }
+
+ _AlarmDic = {
+ 0: 'No Alarm',
+ 1: 'Exhaust FAN Alarm',
+ 2: 'Supply FAN Alarm',
+ 3: 'Bypass Alarm',
+ 4: 'T1 Alarm',
+ 5: 'T2 Alarm',
+ 6: 'T3 Alarm',
+ 7: 'T4 Alarm',
+ 8: 'T5 Alarm',
+ 9: 'RH Alarm',
+ 10: 'Outdoor13 Alarm',
+ 11: 'Supply5 Alarm',
+ 12: 'Fire Alarm',
+ 13: 'Communication Alarm',
+ 14: 'FireTermostat Alarm',
+ 15: 'High waterlevel Alarm'
+ }
+
+ _RamIdxBypassActualStateDic = {
+ 0: 'Closed',
+ 1: 'In process',
+ 32: 'Closing',
+ 64: 'Opening',
+ 255: 'Opened'
+ }
+
+ _RamIdxHac1ComponentsDic = {
+ 0x0001: 'CO2 Sensor',
+ 0x0004: 'PreHeater',
+ 0x0008: 'PreCooler',
+ 0x0010: 'AfterHeater',
+ 0x0020: 'AfterCooler',
+ 0x0040: 'Hygrostat'
+ }
+
+ # Initialize connection
+ def __init__(self, sh, *args, **kwargs):
+ self.logger = logging.getLogger(__name__)
+ self._host = self.get_parameter_value('host')
+ self._port = int(self.get_parameter_value('port'))
+ self._cycle = int(self.get_parameter_value('cycle'))
+ self._lock = threading.Lock()
+ self._is_connected = False
+ self.connect()
+
+ def connect(self):
+ start_time = time.time()
+ if self._is_connected:
+ return True
+ self._lock.acquire()
+ try:
+ self.logger.info("Pluggit: connecting to {0}:{1}".format(self._host, self._port))
+ self._Pluggit = ModbusTcpClient(self._host, self._port)
+ except Exception as e:
+ self.logger.error("Pluggit: could not connect to {0}:{1}: {2}".format(self._host, self._port, e))
+ return
+ finally:
+ self._lock.release()
+ self.logger.info("Pluggit: connected to {0}:{1}".format(self._host, self._port))
+ self._is_connected = True
+ end_time = time.time()
+ self.logger.debug("Pluggit: connection took {0} seconds".format(end_time - start_time))
+
+ def disconnect(self):
+ start_time = time.time()
+ if self._is_connected:
+ try:
+ self._Pluggit.close()
+ except:
+ pass
+ self._is_connected = False
+ end_time = time.time()
+ self.logger.debug("Pluggit: disconnect took {0} seconds".format(end_time - start_time))
+
+ def run(self):
+ self.scheduler_add(__name__, self._refresh, cycle=self._cycle)
+ self.alive = True
+
+ def stop(self):
+ self.scheduler_remove(__name__)
+ self.alive = False
+
+ def parse_item(self, item):
+ # check for smarthome.py attribute 'pluggit_read' in pluggit.conf
+ if self.has_iattr(item.conf, 'pluggit_read'):
+ self.logger.debug("Pluggit: parse read item: {0}".format(item))
+ pluggit_key = self.get_iattr_value(item.conf, 'pluggit_read')
+ if pluggit_key in self._modbusRegisterDictionary:
+ self._itemReadDictionary[item] = pluggit_key
+ self.logger.debug("Pluggit: Inhalt des dicts _itemReadDictionary nach Zuweisung zu item: '{0}'".format(self._itemReadDictionary))
+ else:
+ self.logger.warning("Pluggit: invalid key {0} configured".format(pluggit_key))
+ # check for smarthome.py attribute 'pluggit_write' in pluggit.conf
+ if self.has_iattr(item.conf, 'pluggit_write'):
+ self.logger.debug("Pluggit: parse write item: {0}".format(item))
+ pluggit_sendKey = self.get_iattr_value(item.conf, 'pluggit_write')
+ if pluggit_sendKey in self._modbusRegisterDictionary:
+ self._itemWriteDictionary[item] = pluggit_sendKey
+ self.logger.debug("Pluggit: Inhalt des dicts _itemWriteDictionary nach Zuweisung zu write item: '{0}'".format(self._itemWriteDictionary))
+ return self.update_item
+ else:
+ self.logger.warning("Pluggit: invalid key {0} configured".format(pluggit_key))
+ else:
+ return None
+
+ def update_item(self, item, caller=None, source=None, dest=None):
+ if caller != 'Pluggit':
+ pluggit_sendkey = self._itemWriteDictionary[item]
+ pluggit_paramList = self._modbusRegisterDictionary[pluggit_sendkey]
+ value = item()
+ writeItemValue = None
+
+ if value is not None:
+ if pluggit_paramList[self.DICT_WRITE_ADDRESS] != -1:
+ # check for conditions
+ # unit mode = manual?
+ if pluggit_sendkey == 'prmRomIdxSpeedLevel':
+ self.SetUnitMode('UM_ManualMode', True)
+ # check for conversion
+ if self.has_iattr(item.conf, 'pluggit_convert'):
+ # with conversion
+ convType = self.get_iattr_value(item.conf, 'pluggit_convert')
+ if convType in pluggit_paramList[self.DICT_ALLOWED_CONV_LIST]:
+ value = self.ConvertValueFromItem(value, convType, pluggit_sendkey)
+ else:
+ self.logger.warning('Umwandlung von {} zu {} bei Item {} nicht zulässig.'.format(pluggit_paramList[self.DICT_VALUE_TYPE], convType, item))
+ if value is not None:
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'uint' or pluggit_paramList[self.DICT_VALUE_TYPE] == 'bool' or pluggit_paramList[self.DICT_VALUE_TYPE] == 'timestamp':
+ writeItemValue = value & 0xFFFF, value >> 16 & 0xFFFF
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'str':
+ writeItemValue = self.StringToBinWord(value, pluggit_paramList[self.DICT_ADDRESS_QUANTITY])
+ if writeItemValue is not None:
+ # write values to pluggit via modbus client registers
+ self.logger.debug('VALUE: {}'.format(writeItemValue))
+ self._Pluggit.write_registers(pluggit_paramList[self.DICT_WRITE_ADDRESS], writeItemValue)
+ else:
+ self.logger.warning("Parameter {} bei Item {} kann nur gelesen werden.".format(pluggit_sendkey, item))
+
+ def BinWordToString(self, binWord):
+ result = ''
+ for word in binWord:
+ char = word & 0xFF
+ if char == 0:
+ break
+ result += chr(char)
+ char = word >> 8
+ if char == 0:
+ break
+ result += chr(char)
+ return result
+
+ def StringToBinWord(self, bwString, wordCount):
+ result = []
+ vlen = len(bwString)
+ for i in range (0, wordCount):
+ binval1 = 0
+ binval2 = 0
+ if vlen > i*2:
+ binval1 = ord(bwString[i*2])
+ if vlen > i*2+1:
+ binval2 = ord(bwString[i*2+1])
+ result.append(binval1 | binval2 << 8)
+ return result
+
+ def SetUnitMode(self, modekey, enable):
+ if 'prmRamIdxUnitMode' in self._modbusRegisterDictionary:
+ if modekey in self._RamIdxUnitModeDic:
+ unitmode = self._RamIdxUnitModeDic[modekey]
+ if bool(enable):
+ unitstate = unitmode[self.UM_DICT_VALUE_ENABLE]
+ else:
+ unitstate = unitmode[self.UM_DICT_VALUE_DISABLE]
+ if unitstate != -1:
+ pluggit_paramList = self._modbusRegisterDictionary['prmRamIdxUnitMode']
+ registerValue = self._Pluggit.read_holding_registers(pluggit_paramList[self.DICT_READ_ADDRESS], pluggit_paramList[self.DICT_ADDRESS_QUANTITY])
+ vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.Big, wordorder=Endian.Little)
+ readItemValue = vdecoder.decode_16bit_uint()
+ #if readItemValue & unitstate != unitstate:
+ # workaround for manual bypass timeout
+ self.logger.info('SetUnitMode(): Mode \"{}\".'.format(modekey))
+ #if modekey == 'UM_ManualBypass' & bool(enable):
+ #self._Pluggit.write_registers(pluggit_paramList[self.DICT_WRITE_ADDRESS], unitmode[self.UM_DICT_VALUE_DISABLE])
+ #time.sleep(0.5)
+ # write value to registers
+ self._Pluggit.write_registers(pluggit_paramList[self.DICT_WRITE_ADDRESS], unitstate)
+ time.sleep(0.5)
+ else:
+ self.logger.warning('SetUnitMode(): Illegal mode \"{}\".'.format(modekey))
+ else:
+ self.logger.warning('SetUnitMode(): Parameter \"prmRamIdxUnitMode\" for UnitMode not found in dictionary.')
+
+ def ConvertValueFromItem(self, value, conversionType, pluggitKey):
+ conversionValue = None
+
+ pluggit_paramList = self._modbusRegisterDictionary[pluggitKey]
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'uint':
+ if conversionType == 'ToBool':
+ conversionValue = int(value)
+ if conversionType == 'ToBool_inverted':
+ conversionValue = int(not value)
+
+ if pluggit_paramList [self.DICT_VALUE_TYPE] == 'timestamp':
+ if conversionType == 'ToDateTime':
+ conversionValue = datetime(value).strftime("%s")
+ # TODO: ToDate
+ # TODO: ToTime
+
+ # pluggit_key = prmRamIdxUnitMode
+ if conversionType in self._RamIdxUnitModeDic:
+ if bool(value):
+ conversionValue = self._RamIdxUnitModeDic[conversionType][self.UM_DICT_VALUE_ENABLE]
+ else:
+ conversionValue = self._RamIdxUnitModeDic[conversionType][self.UM_DICT_VALUE_DISABLE]
+
+ self.logger.debug("conVersion: {}".format(conversionValue))
+ return conversionValue
+
+ def ConvertValueToItem(self, value, conversionType, pluggitKey):
+ conversionValue = None
+
+ if conversionType == 'ToBool':
+ conversionValue = bool(value)
+
+ if conversionType == 'ToBool_inverted':
+ conversionValue = not bool(value)
+
+ if conversionType == 'ToDateTime':
+ conversionValue = datetime.utcfromtimestamp(value)
+
+ if conversionType == 'ToDate':
+ conversionValue = datetime.utcfromtimestamp(value).date()
+
+ if conversionType == 'ToTime':
+ conversionValue = datetime.utcfromtimestamp(value).time()
+
+ if conversionType == 'ToString':
+ if pluggitKey == 'prmCurrentBLState':
+ conversionValue = self._CurrentBLStateDic[value]
+ if pluggitKey == 'prmLastActiveAlarm':
+ conversionValue = self._AlarmDic[value]
+ if pluggitKey == 'prmRamIdxBypassActualState':
+ conversionValue = self._RamIdxBypassActualStateDic[value]
+
+ # pluggit_key = prmSystemID
+ if conversionType in self._SystemIDDict:
+ conversionValue = bool(value & self._SystemIDDict[conversionType][self.SID_DICT_VALUE_ENABLE])
+
+ # pluggit_key = prmRamIdxUnitMode
+ if conversionType in self._RamIdxUnitModeDic:
+ conversionValue = bool(value & self._RamIdxUnitModeDic[conversionType][self.UM_DICT_VALUE_ENABLE])
+
+ return conversionValue
+
+ def _refresh(self):
+ readCacheDictionary = {}
+
+ for item in self._itemReadDictionary:
+ pluggit_key = self._itemReadDictionary[item]
+ pluggit_paramList = self._modbusRegisterDictionary[pluggit_key]
+
+ # read values from pluggit via modbus client registers, if not in cache
+ if pluggit_paramList[self.DICT_READ_ADDRESS] != -1:
+ if pluggit_key in readCacheDictionary:
+ registerValue = readCacheDictionary[pluggit_key]
+ else:
+ registerValue = self._Pluggit.read_holding_registers(pluggit_paramList[self.DICT_READ_ADDRESS], pluggit_paramList[self.DICT_ADDRESS_QUANTITY])
+ # TODO: auswerten, wenn Reigister nicht auslesbar
+ readCacheDictionary[pluggit_key] = registerValue
+ vdecoder = BinaryPayloadDecoder.fromRegisters(registerValue.registers, byteorder=Endian.Big, wordorder=Endian.Little)
+
+ readItemValue = None
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'uint':
+ if pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 1 or pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 2:
+ readItemValue = vdecoder.decode_16bit_uint()
+ if pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 4:
+ readItemValue = vdecoder.decode_64bit_uint()
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'float':
+ if pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 2:
+ readItemValue = vdecoder.decode_32bit_float()
+ if pluggit_paramList[self.DICT_ROUND_VALUE] != -1:
+ readItemValue = round(readItemValue, pluggit_paramList[self.DICT_ROUND_VALUE])
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'bool':
+ if pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 1:
+ readItemValue = bool(vdecoder.decode_16bit_uint())
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'timestamp':
+ if pluggit_paramList[self.DICT_ADDRESS_QUANTITY] == 2:
+ readItemValue = vdecoder.decode_32bit_uint()
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'str':
+ readItemValue = self.BinWordToString(registerValue.registers)
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'ip':
+ readItemValue = '{}.{}.{}.{}'.format(registerValue.registers[1] >> 8, registerValue.registers[1] & 0xFF, registerValue.registers[0] >> 8, registerValue.registers[0] & 0xFF)
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'mac':
+ readItemValue = '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'.format(registerValue.registers[0] >> 8, registerValue.registers[0] & 0xFF, registerValue.registers[3] >> 8, registerValue.registers[3] & 0xFF, registerValue.registers[2] >> 8, registerValue.registers[2] & 0xFF)
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'version':
+ vresult = vdecoder.decode_16bit_uint()
+ readItemValue = '{}.{}'.format(vresult >> 8 & 0xFF, vresult & 0xFF)
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'version_bcd':
+ vresult = vdecoder.decode_16bit_uint()
+ readItemValue = '{}.{}{}'.format(vresult >> 12 & 0x0F, vresult >> 8 & 0x0F, vresult >> 4 & 0x0F, vresult & 0x0F)
+
+ if pluggit_paramList[self.DICT_VALUE_TYPE] == 'weekprogram':
+ # 6 Register, 1 h = 4 Bit, 2 h = 8 bit, 4 h = 16 bit = 1 Register
+ #readItemValue = registerValue.registers
+ #readItemValue = str(registerValue.registers[0] & 0x0F)
+ #self.logger.info('{}'.format(readItemValue))
+ pass
+
+ # check for conversion
+ convItemValue = None
+ if readItemValue is not None:
+ if self.has_iattr(item.conf, 'pluggit_convert'):
+ convType = self.get_iattr_value(item.conf, 'pluggit_convert')
+ if convType in pluggit_paramList[self.DICT_ALLOWED_CONV_LIST]:
+ convItemValue = self.ConvertValueToItem(readItemValue, convType, pluggit_key)
+ if convItemValue is not None:
+ item(convItemValue, 'Pluggit')
+ else:
+ self.logger.warning("Fehler bei der Umwandlung von Item {}.".format(item))
+ else:
+ self.logger.warn("Umwandlung von {} zu {} bei Item {} nicht zulässig.".format(pluggit_paramList[self.DICT_VALUE_TYPE], convType, item))
+ else:
+ if readItemValue is not None:
+ item(readItemValue, 'Pluggit')
+ else:
+ self.logger.warning("Unbekannter Wert-Typ: {} bei Item {}.".format(pluggit_paramList[self.DICT_VALUE_TYPE]), item)
+
+ time.sleep(0.1)
\ No newline at end of file
diff --git a/pluggit/_pv_2_0_4/pluggit.yaml b/pluggit/_pv_2_0_4/pluggit.yaml
new file mode 100644
index 000000000..35928cf16
--- /dev/null
+++ b/pluggit/_pv_2_0_4/pluggit.yaml
@@ -0,0 +1,3 @@
+Zentral:
+ pluggit:
+ struct: pluggit.pluggit
\ No newline at end of file
diff --git a/pluggit/_pv_2_0_4/plugin.yaml b/pluggit/_pv_2_0_4/plugin.yaml
new file mode 100644
index 000000000..8cd06782a
--- /dev/null
+++ b/pluggit/_pv_2_0_4/plugin.yaml
@@ -0,0 +1,640 @@
+# Metadata for the Smart-Plugin
+plugin:
+ # Global plugin attributes
+ type: interface # plugin type (gateway, interface, protocol, system, web)
+ description:
+ de: 'Anbindung einer KWL Pluggit AP310 über das Modbus-Protokoll.'
+ en: ''
+ maintainer: 'Cannon'
+# tester: # Who tests this plugin?
+# state:
+ keywords: modbus
+# documentation:
+# support:
+ version: 2.0.4 # Plugin version
+ sh_minversion: 1.8 # minimum shNG version to use this plugin
+# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
+ multi_instance: False # plugin supports multi instance
+ classname: Pluggit # class containing the plugin
+
+parameters:
+ # Definition of parameters to be configured in etc/plugin.yaml
+ host:
+ type: str
+ mandatory: True
+ description:
+ de: 'Hostname oder IP Adresse des Pluggit Dienstes.'
+ en: 'Hostname or IP address of the Pluggit service.'
+
+ port:
+ type: int
+ mandatory: False
+ default: 502
+ valid_min: 0
+ valid_max: 65535
+ description:
+ de: 'Port Nummer des Pluggit Dienstes.'
+ en: 'Port number of Pluggit service.'
+
+ cycle:
+ type: int
+ mandatory: False
+ default: 50
+ description:
+ de: 'Zykluszeit.'
+ en: 'Cycle time.'
+
+item_attributes:
+ # Definition of item attributes defined by this plugin
+ pluggit_read:
+ type: str
+ description:
+ de: 'Aus der Pluggit-Einheit zu lesende Daten.'
+ en: 'Data to be read from the Pluggit unit.'
+
+ pluggit_write:
+ type: str
+ description:
+ de: 'In die Pluggit-Einheit zu schreibender Wert.'
+ en: 'Value to be written to the Pluggit unit.'
+
+ pluggit_convert:
+ type: str
+ description:
+ de: 'Wandelt den gelesenen oder den zu schreibenden Wert in bzw. von ein anderes Format um.'
+ en: 'Convert the read or write to value to or from another format.'
+
+item_structs:
+ pluggit:
+ DHCPEnabled:
+ type: bool
+ pluggit_read: prmDHCPEN
+ visu_acl: ro
+
+ IPAdress:
+ type: str
+ pluggit_read: prmCurrentIPAddress
+ visu_acl: ro
+
+ IPMask:
+ type: str
+ pluggit_read: prmCurrentIPMask
+ visu_acl: ro
+
+ IPGateway:
+ type: str
+ pluggit_read: prmCurrentIPGateway
+ visu_acl: ro
+
+ MACAdress:
+ type: str
+ pluggit_read: prmMACAddr
+ visu_acl: ro
+
+ SystemComponents:
+ type: num
+ pluggit_read: prmSystemID
+ visu_acl: ro
+
+ FP1:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_FP1
+ visu_acl: ro
+
+ Week:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_Week
+ visu_acl: ro
+
+ Bypass:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_Bypass
+ visu_acl: ro
+
+ LRSwitch:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_LRSwitch
+ visu_acl: ro
+
+ InternalPreheater:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_InternalPreheater
+ visu_acl: ro
+
+ ServoFlow:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_ServoFlow
+ visu_acl: ro
+
+ RHSensor:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_RHSensor
+ visu_acl: ro
+
+ VOCSensor:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_VOCSensor
+ visu_acl: ro
+
+ ExtOverride:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_ExtOverride
+ visu_acl: ro
+
+ HAC1:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_HAC1
+ visu_acl: ro
+
+ HRC2:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_HRC2
+ visu_acl: ro
+
+ PCTool:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_PCTool
+ visu_acl: ro
+
+ Apps:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_Apps
+ visu_acl: ro
+
+ ZeegBee:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_ZeegBee
+ visu_acl: ro
+
+ DI1Override:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_DI1Override
+ visu_acl: ro
+
+ DI2Override:
+ type: bool
+ pluggit_read: prmSystemID
+ pluggit_convert: SID_DI2Override
+ visu_acl: ro
+
+ SystemSerialNum:
+ type: num
+ pluggit_read: prmSystemSerialNum
+ visu_acl: ro
+
+ SystemName:
+ type: str
+ pluggit_read: prmSystemName
+ pluggit_write: prmSystemName
+ visu_acl: rw
+
+ SwitchInBPosition:
+ type: bool
+ pluggit_read: prmHALLeft
+ visu_acl: ro
+
+ SwitchInAPosition:
+ type: bool
+ pluggit_read: prmHALRight
+ visu_acl: ro
+
+ FirmwareVersion:
+ type: num
+ pluggit_read: prmFWVersion
+ visu_acl: ro
+
+ HACFirmwareVersion:
+ type: num
+ pluggit_read: prmRamIdxHac1FirmwareVersion
+ visu_acl: ro
+
+ SystemTime:
+ type: num
+ pluggit_read: prmDateTime
+ pluggit_write: prmDateTimeSet
+ visu_acl: rw
+
+ DateTime:
+ type: foo
+ pluggit_read: prmDateTime
+ pluggit_write: prmDateTimeSet
+ pluggit_convert: ToDateTime
+ visu_acl: rw
+
+ Date:
+ type: foo
+ pluggit_read: prmDateTime
+ pluggit_convert: ToDate
+ visu_acl: ro
+
+ Time:
+ type: foo
+ pluggit_read: prmDateTime
+ pluggit_convert: ToTime
+ visu_acl: ro
+
+ WorkTime:
+ type: num
+ pluggit_read: prmWorkTime
+ visu_acl: ro
+
+ ExploitationDate:
+ type: foo
+ pluggit_read: prmStartExploitationDateStamp
+ visu_acl: ro
+
+ Date:
+ type: foo
+ pluggit_read: prmStartExploitationDateStamp
+ pluggit_convert: ToDate
+ visu_acl: ro
+
+ BLState:
+ type: num
+ pluggit_read: prmCurrentBLState
+ visu_acl: ro
+
+ Text:
+ type: str
+ pluggit_read: prmCurrentBLState
+ pluggit_convert: ToString
+ visu_acl: ro
+
+ CurrentUnitMode:
+ type: num
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ visu_acl: rw
+
+ DemandMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_DemandMode
+ visu_acl: rw
+
+ ManualMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_ManualMode
+ visu_acl: rw
+
+ WeekProgramMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_WeekProgramMode
+ visu_acl: rw
+
+ AwayMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_AwayMode
+ visu_acl: rw
+
+ FireplaceMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_FireplaceMode
+ visu_acl: rw
+
+ SummerMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_SummerMode
+ visu_acl: rw
+
+ NightMode:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_NightMode
+ visu_acl: rw
+
+ NightMode_StartHour:
+ type: num
+ pluggit_read: prmRomIdxNightModeStartHour
+ pluggit_write: prmRomIdxNightModeStartHour
+ visu_acl: rw
+
+ NightMode_StartMin:
+ type: num
+ pluggit_read: prmRomIdxNightModeStartMin
+ pluggit_write: prmRomIdxNightModeStartMin
+ visu_acl: rw
+
+ NightMode_EndHour:
+ type: num
+ pluggit_read: prmRomIdxNightModeEndHour
+ pluggit_write: prmRomIdxNightModeEndHour
+ visu_acl: rw
+
+ NightMode_EndMin:
+ type: num
+ pluggit_read: prmRomIdxNightModeEndMin
+ pluggit_write: prmRomIdxNightModeEndMin
+ visu_acl: rw
+
+ ManualBypass:
+ type: bool
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_ManualBypass
+ visu_acl: rw
+
+ FanSpeedLevel:
+ type: num
+ pluggit_read: prmRomIdxSpeedLevel
+ pluggit_write: prmRomIdxSpeedLevel
+ visu_acl: rw
+
+ Fan1rpm:
+ type: num
+ pluggit_read: prmHALTaho1
+ visu_acl: ro
+
+ Fan2rpm:
+ type: num
+ pluggit_read: prmHALTaho2
+ visu_acl: ro
+
+ # Frischluft
+ OutdoorTemperature:
+ type: num
+ pluggit_read: prmRamIdxT1
+ visu_acl: ro
+
+ # Zuluft
+ SupplyTemperature:
+ type: num
+ pluggit_read: prmRamIdxT2
+ visu_acl: ro
+
+ # Abluft
+ ExtractTemperature:
+ type: num
+ pluggit_read: prmRamIdxT3
+ visu_acl: ro
+
+ # Fortluft
+ ExhaustTemperature:
+ type: num
+ pluggit_read: prmRamIdxT4
+ visu_acl: ro
+
+ # Raumtemperatur, kabellos
+ RoomTemperature:
+ type: num
+ pluggit_read: prmRamIdxT5
+ visu_acl: ro
+
+ FilterRemainingLifetime:
+ type: num
+ pluggit_read: prmFilterRemainingTime
+ visu_acl: ro
+
+ FilterDefaultTime:
+ type: num
+ pluggit_read: prmFilterDefaultTime
+ pluggit_write: prmFilterDefaultTime
+ visu_acl: rw
+
+ FilterReset:
+ type: bool
+ pluggit_read: prmFilterReset
+ pluggit_write: prmFilterReset
+ visu_acl: rw
+
+ Alarm:
+ type: num
+ pluggit_read: prmLastActiveAlarm
+ visu_acl: ro
+ Text:
+ type: str
+ pluggit_read: prmLastActiveAlarm
+ pluggit_convert: ToString
+ visu_acl: ro
+ ClearAlarm:
+ type: num
+ pluggit_write: prmSetAlarmNum
+ visu_acl: rw
+
+ WeekProgram:
+ type: num
+ pluggit_read: prmNumOfWeekProgram
+ pluggit_write: prmNumOfWeekProgram
+ visu_acl: rw
+
+ Monday:
+ type: list
+ pluggit_read: PrmWeekMon
+ pluggit_write: PrmWeekMon
+ visu_acl: rw
+
+ Tuesday:
+ type: list
+ pluggit_read: PrmWeekTue
+ pluggit_write: PrmWeekTue
+ visu_acl: rw
+
+ Wednesday:
+ type: list
+ pluggit_read: PrmWeekWed
+ pluggit_write: PrmWeekWed
+ visu_acl: rw
+
+ Thursday:
+ type: list
+ pluggit_read: PrmWeekThu
+ pluggit_write: PrmWeekThu
+ visu_acl: rw
+
+ Friday:
+ type: list
+ pluggit_read: PrmWeekFri
+ pluggit_write: PrmWeekFri
+ visu_acl: rw
+
+ Saturday:
+ type: list
+ pluggit_read: PrmWeekSat
+ pluggit_write: PrmWeekSat
+ visu_acl: rw
+
+ Sunday:
+ type: list
+ pluggit_read: PrmWeekSun
+ pluggit_write: PrmWeekSun
+ visu_acl: rw
+
+ Bypass:
+ type: num
+ pluggit_read: prmRamIdxBypassActualState
+ visu_acl: ro
+
+ Text:
+ type: str
+ pluggit_read: prmRamIdxBypassActualState
+ pluggit_convert: ToString
+ visu_acl: ro
+
+ Open:
+ type: bool
+ pluggit_read: prmRamIdxBypassActualState
+ pluggit_convert: ToBool
+ visu_acl: ro
+
+ Tmin:
+ type: num
+ pluggit_read: prmBypassTmin
+ visu_acl: ro
+
+ Tmax:
+ type: num
+ pluggit_read: prmBypassTmax
+ visu_acl: ro
+
+ ManualMode:
+ type: bool
+ enforce_updates: yes
+ pluggit_read: prmRamIdxUnitMode
+ pluggit_write: prmRamIdxUnitMode
+ pluggit_convert: UM_ManualBypass
+ visu_acl: rw
+
+ ManualTimeout:
+ type: num
+ pluggit_read: prmRamIdxBypassManualTimeout
+ visu_acl: ro
+
+ PreheaterPower:
+ type: num
+ pluggit_read: prmPreheaterDutyCycle
+ visu_acl: ro
+
+ ReferenceExtractFanSpeed:
+ type: num
+ pluggit_read: prmRefValEx
+ visu_acl: ro
+
+ ReferenceSupplyFanSpeed:
+ type: num
+ pluggit_read: prmRefValSupl
+ visu_acl: ro
+
+ FireplacePresent:
+ type: bool
+ pluggit_read: prmFireplacePreset
+ visu_acl: ro
+
+ VOC_Sensor:
+ SensorValue:
+ type: num
+ pluggit_read: prmVOC
+ visu_acl: ro
+
+ LowTreshold:
+ type: num
+ pluggit_read: prmPPM1Unit
+ pluggit_write: prmPPM1Unit
+ visu_acl: rw
+
+ MiddleTreshold:
+ type: num
+ pluggit_read: prmPPM2Unit
+ pluggit_write: prmPPM2Unit
+ visu_acl: rw
+
+ HighTreshold:
+ type: num
+ pluggit_read: prmPPM3Unit
+ pluggit_write: prmPPM3Unit
+ visu_acl: rw
+
+ RH_Sensor:
+ SensorValue:
+ type: num
+ pluggit_read: prmRamIdxRh3Corrected
+ visu_acl: ro
+
+ SetPoint:
+ type: num
+ pluggit_read: prmRomIdxRhSetPoint
+ visu_acl: ro
+
+ HAC_Parts:
+ SystemIDComponents:
+ type: num
+ pluggit_read: prmSystemIDComponents
+ visu_acl: ro
+
+ CO2_Level:
+ type: num
+ pluggit_read: prmHACCO2Val
+ visu_acl: ro
+
+ CO2_LowTreshold:
+ type: num
+ pluggit_read: prmPPM1External
+ pluggit_write: prmPPM1External
+ visu_acl: rw
+
+ CO2_MiddleTreshold:
+ type: num
+ pluggit_read: prmPPM2External
+ pluggit_write: prmPPM2External
+ visu_acl: rw
+
+ CO2_HighTreshold:
+ type: num
+ pluggit_read: prmPPM3External
+ pluggit_write: prmPPM3External
+ visu_acl: rw
+
+ IdxHac1Components:
+ type: num
+ pluggit_read: prmRamIdxHac1Components
+ visu_acl: ro
+
+ SetpointT2:
+ type: num
+ pluggit_read: prmRomIdxAfterHeaterT2SetPoint
+ pluggit_write: prmRomIdxAfterHeaterT2SetPoint
+ visu_acl: rw
+
+ SetpointT3:
+ type: num
+ pluggit_read: prmRomIdxAfterHeaterT3SetPoint
+ pluggit_write: prmRomIdxAfterHeaterT3SetPoint
+ visu_acl: rw
+
+ SetpointT5:
+ type: num
+ pluggit_read: prmRomIdxAfterHeaterT5SetPoint
+ pluggit_write: prmRomIdxAfterHeaterT5SetPoint
+ visu_acl: rw
+
+logic_parameters: NONE
+ # Definition of logic parameters defined by this plugin
+
+plugin_functions:
+ # Definition of function interface of the plugin
\ No newline at end of file
diff --git a/pluggit/_pv_2_0_4/requirements.txt b/pluggit/_pv_2_0_4/requirements.txt
new file mode 100644
index 000000000..04c18ec78
--- /dev/null
+++ b/pluggit/_pv_2_0_4/requirements.txt
@@ -0,0 +1,2 @@
+pymodbus>=2.3,<3.0;python_version<'3.8'
+pymodbus>=3.0.2;python_version>="3.8"
diff --git a/pluggit/_pv_2_0_4/user_doc.rst b/pluggit/_pv_2_0_4/user_doc.rst
new file mode 100644
index 000000000..269f16377
--- /dev/null
+++ b/pluggit/_pv_2_0_4/user_doc.rst
@@ -0,0 +1,128 @@
+
+.. index:: Plugins; pluggit
+.. index:: pluggit Plugin
+
+=======
+pluggit
+=======
+
+.. image:: webif/static/img/plugin_logo.svg
+ :alt: plugin logo
+ :width: 300px
+ :height: 300px
+ :scale: 50 %
+ :align: left
+
+Das pluggit plugin dient zur Ansteuerung einer Pluggit AP310 KWL.
+
+
+Einführung
+==========
+
+Die aktuelle Version 2.x des Plugins ist eine Neuentwicklung auf Basis der Version v1.2.3 des pluggit Plugins.
+Die Version 2.x ist nicht konfigurations-kompatibel zur Version 1.x. Die Konfiguration der Plugin Parameter und der
+Item Attribute ist daher neu entsprechend der aktuellen Dokumentation vorzunehmen.
+
+Es ist bei Bedarf jedoch möglich erstmal die alte Version v1.2.3 des pluggit Plugins weiter zu nutzen.
+Dazu muss nur ein Eintrag in der Plugin Konfiguration in der etc/plugin.yaml angepasst/eingefügt werden.
+
+Statt
+
+.. code:: yaml
+
+ pluggit:
+ plugin_name: pluggit
+
+ ...
+
+
+muss die zu verwendende Version des Plugins zusätzlich angegeben werden:
+
+.. code:: yaml
+
+ pluggit:
+ plugin_name: pluggit
+ plugin_version: 1.2.3
+
+ ...
+
+
+Vorteile gegenüber der Plugin Version v1.2.3
+============================================
+
+- wesentlich mehr Parameter der pluggit können abgefragt werden
+- einige Parameter lassen sich auch schreiben
+- die Werte können intern auch konvertiert werden, sodass man eine vernünftige Ausgabe erhält
+
+
+Es fehlen auch noch ein paar Dinge
+==================================
+
+- die Programmierung des Auto-Wochenprogramms ist noch nicht implementiert
+- eine Dokumentation der Parameter
+
+
+.. Anforderungen
+.. =============
+
+.. Anforderungen des Plugins auflisten. Werden spezielle Soft- oder Hardwarekomponenten benötigt?
+
+.. Um das Plugin zu nutzen, muss ...
+
+
+.. Installation benötigter Software
+.. ================================
+
+Konfiguration
+=============
+
+Die Plugin Parameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind
+unter :doc:`/plugins_doc/config/pluggit` nachzulesen.
+
+
+Struct
+======
+
+Die zur Nutzung des Plugins benötigten Items können durch die Einbindung der struct **pluggit.pluggit** angelegt
+werden.
+
+
+.. Beispiele
+.. ---------
+
+.. Hier können ausführlichere Beispiele und Anwendungsfälle beschrieben werden.
+
+
+.. Web Interface
+.. =============
+
+.. ...
+
+
+Version History
+===============
+
+V2.0.3 - 25.10.2022
+
+- Support für pymodbus 3.0
+
+22.05.2022
+
+- Fehler mit manuellem Bypass behoben
+
+16.02.2022
+
+- CurentUnitMode.ManualBypass dem Item-struct zugefügt
+- Log-Level für verschiedene Ausgaben angepasst
+- CurrentUnitMode.AwayMode repariert
+
+24.02.2021
+
+- Item-struct um Zugriffe für SmartVISU erweitert
+- item_attribut um pluggit_convert erweitert
+- scheduler.remove eingebaut
+
+29.08.2020
+
+ - bool-Werte konnten nicht geschrieben werden
+
diff --git a/pluggit/changes.txt b/pluggit/changes.txt
new file mode 100644
index 000000000..ab6bf7143
--- /dev/null
+++ b/pluggit/changes.txt
@@ -0,0 +1,8 @@
+15.09.2023:
+- Anpassung für pymodbus 3.5.2: bytorder und wordorder musste korrrigiert werden, statt Little und Big nun LITTLE und BIG
+
+11.09.2023:
+- unter Python 10 muss mindestens die Version 3.3.2 von pymodbus laufen, da sonst Verbindungsprobleme
+
+23.07.2023:
+- pymodbus mindestens Version 3.3.0, da Versionsmanagement geändert wurde, Version 2 wird nicht mehr unterstützt
diff --git a/pluggit/plugin.yaml b/pluggit/plugin.yaml
index 8cd06782a..a98bb2411 100755
--- a/pluggit/plugin.yaml
+++ b/pluggit/plugin.yaml
@@ -11,7 +11,7 @@ plugin:
keywords: modbus
# documentation:
# support:
- version: 2.0.4 # Plugin version
+ version: 2.0.6 # Plugin version
sh_minversion: 1.8 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
multi_instance: False # plugin supports multi instance
diff --git a/pluggit/requirements.txt b/pluggit/requirements.txt
index 04c18ec78..df27c8584 100755
--- a/pluggit/requirements.txt
+++ b/pluggit/requirements.txt
@@ -1,2 +1 @@
-pymodbus>=2.3,<3.0;python_version<'3.8'
-pymodbus>=3.0.2;python_version>="3.8"
+pymodbus>=3.5.2;python_version>='3.8'
diff --git a/pluggit/user_doc.rst b/pluggit/user_doc.rst
index 269f16377..93d5139ad 100755
--- a/pluggit/user_doc.rst
+++ b/pluggit/user_doc.rst
@@ -102,6 +102,19 @@ werden.
Version History
===============
+V2.0.6 - 15.09.2023
+
+- Anpassung für pymodbus 3.5.2: bytorder und wordorder musste korrrigiert werden, statt Little und Big nun LITTLE und BIG
+- Python muss >= 3.8 sein und pymodbus >= 3.5.2
+
+V2.0.5 - 11.09.2023
+
+- unter Python 10 muss mindestens die Version 3.3.2 von pymodbus laufen, da sonst Verbindungsprobleme
+
+V2.0.4 - 13.11.2022
+
+- Verbesserungen zur Versionsprüfung "pymodbus"
+
V2.0.3 - 25.10.2022
- Support für pymodbus 3.0
@@ -124,5 +137,5 @@ V2.0.3 - 25.10.2022
29.08.2020
- - bool-Werte konnten nicht geschrieben werden
+- bool-Werte konnten nicht geschrieben werden
diff --git a/roombapysh/user_doc.rst b/roombapysh/user_doc.rst
index fa27a88d2..dc86b9961 100644
--- a/roombapysh/user_doc.rst
+++ b/roombapysh/user_doc.rst
@@ -1,9 +1,9 @@
.. index:: Plugins; roombapysh
.. index:: roombapysh
-======
+======================
Roomba für SmartHomeNG
-======
+======================
Dieses Plugin ermöglicht die Einbindung von iRobot Roomba Staubsaugrobotern.
Das Plugin basiert auf https://github.com/pschmitt/roombapy (ein Fork von https://github.com/NickWaterton/Roomba980-Python) für die Kommunikation.
@@ -53,20 +53,18 @@ Installation
Vor der Inbetriebnahme des Plugins bitte paho-mqtt und orjson installieren.
-.. code-block::
+.. code-block:: bash
+
pip3 install paho-mqtt
pip3 install orjson
Konfiguration
=============
-Die Plugin Parameter und die Informationen zur Item-spezifischen Konfiguration des Plugins sind
-unter :doc:`/plugins_doc/config/sample` beschrieben.
-
-plugin.yaml
------------
+Die Plugin Parameter, die Informationen zur Item-spezifischen Konfiguration des Plugins und zur Logik-spezifischen
+Konfiguration sind unter :doc:`/plugins_doc/config/roombapysh` beschrieben.
-Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
+Dort findet sich auch die Dokumentation zu Funktionen, die das Plugin evtl. bereit stellt.
items.yaml
@@ -76,6 +74,7 @@ Hier ein Beispiel einer vollständigen Konfiguration von Items.
Die Datei liegt auch im Plugin-Verzeichnis (Harry.yaml).
.. code-block:: yaml
+
# Harry.yaml
Harry:
connect:
@@ -225,27 +224,14 @@ Die Datei liegt auch im Plugin-Verzeichnis (Harry.yaml).
roombapysh: 'lastCommand_initiator'
-Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
-
-
-logic.yaml
-----------
-
-Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
-
-
-Funktionen
-----------
-
-Bitte die Dokumentation lesen, die aus den Metadaten der plugin.yaml erzeugt wurde.
-
Beispiele
=========
Beispiel, wie in der smartVisu der Status des Roomba als Icon angezeigt werden kann:
-.. code-block:: yaml
+.. code-block:: jinja
+
{{ basic.symbol('', ['Harry.Status'], '', ['scene_robo_vac_cleaner.svg','text_question_mark.svg','scene_robo_vac_cleaner_charging.svg','scene_robo_vac_cleaner_charging.svg','scene_robo_vac_cleaner_active.svg','scene_robo_vac_cleaner_paused.svg','scene_robo_vac_cleaner_dock.svg','scene_robo_vac_cleaner_attention.svg'], [0,1,2,3,4,5,6,7], '', ['#766','#f00','#ff0','#0f0','#0f0','#ff0','#0f0','#f00'],'','','midi') }}
Ein Beispiel, wie eine komplette smartVisu Seite aussehen könnte liegt als roomba.html im Plugin-Verzeichnis
diff --git a/smartvisu/tplNG/navi_svg.html b/smartvisu/tplNG/navi_svg.html
index a4d3d2bbc..3b8011f61 100755
--- a/smartvisu/tplNG/navi_svg.html
+++ b/smartvisu/tplNG/navi_svg.html
@@ -1,6 +1,6 @@
- {{ lib.svgimg ('', '{{ visu_img }}', '{{ icon0 }}', '') }}
+ {{ lib.svgimg ('', '{{ visu_img }}', 'icon0', '') }}
{{ visu_name }}
{{ visu_aside }}
{{ visu_aside2 }}
diff --git a/sonos/__init__.py b/sonos/__init__.py
index 1a04ce5d3..458954be8 100755
--- a/sonos/__init__.py
+++ b/sonos/__init__.py
@@ -184,7 +184,7 @@ def renew_error_callback(exception): # events_twisted: failure
# Redundant, as the exception will be logged by the events module
self.logger.error(msg)
- # ToDo possible improvement: Do not do periodic renew but do prober disposal on renew failure here instead. sub.renew(requested_timeout=10)
+ # ToDo possible improvement: Do not do periodic renew but do propper disposal on renew failure here instead. sub.renew(requested_timeout=10)
class SubscriptionHandler(object):
@@ -266,31 +266,34 @@ def unsubscribe(self):
# try to unsubscribe first
try:
self._event.unsubscribe()
+ self.logger.info(f"Event {self._endpoint} unsubscribed")
except Exception as e:
self.logger.warning(f"Exception in unsubscribe(): {e}")
- self._signal.set()
- if self._thread:
- self.logger.dbglow("Preparing to terminate thread")
- if debug:
- self.logger.dbghigh(f"unsubscribe(): Preparing to terminate thread for endpoint {self._endpoint}")
- self._thread.join(timeout=4)
- if debug:
- self.logger.dbghigh(f"unsubscribe(): Thread joined for endpoint {self._endpoint}")
+ else:
+ if debug:
+ self.logger.warning(f"unsubscribe(): Endpoint: {self._endpoint}, Thread: {self._threadName}, self._event not valid")
+
+ self._signal.set()
+ if self._thread:
+ self.logger.dbglow("Preparing to terminate thread")
+ if debug:
+ self.logger.dbghigh(f"unsubscribe(): Preparing to terminate thread for endpoint {self._endpoint}")
+ self._thread.join(timeout=4)
+ if debug:
+ self.logger.dbghigh(f"unsubscribe(): Thread joined for endpoint {self._endpoint}")
- if not self._thread.is_alive():
- self.logger.dbglow("Thread killed for enpoint {self._endpoint}")
- if debug:
- self.logger.dbghigh(f"Thread killed for endpoint {self._endpoint}")
+ if not self._thread.is_alive():
+ self.logger.dbglow("Thread killed for enpoint {self._endpoint}")
+ if debug:
+ self.logger.dbghigh(f"Thread killed for endpoint {self._endpoint}")
+ else:
+ self.logger.warning("unsubscibe(): Error, thread is still alive after termination (join timed-out)")
+ self._thread = None
+ self.logger.info(f"Event {self._endpoint} thread terminated")
- else:
- self.logger.warning("unsubscibe(): Error, thread is still alive after termination (join timed-out)")
- self._thread = None
- self.logger.info(f"Event {self._endpoint} unsubscribed and thread terminated")
if debug:
self.logger.dbghigh(f"unsubscribe(): Event {self._endpoint} unsubscribed and thread terminated")
- else:
- if debug:
- self.logger.warning(f"unsubscribe(): {self._endpoint}: self._event not valid")
+
if debug:
self.logger.dbghigh(f"unsubscribe(): {self._endpoint}: lock released")
@@ -704,7 +707,7 @@ def _av_transport_event(self, sub_handler: SubscriptionHandler) -> None:
self.logger.dbghigh(f"_av_transport_event: {self.uid}: av transport event handler active.")
while not sub_handler.signal.wait(1):
- self.logger.dbgmed(f"_av_transport_event: {self.uid}: start try")
+# self.logger.dbglow(f"_av_transport_event: {self.uid}: start try")
try:
event = sub_handler.event.events.get(timeout=0.5)
@@ -857,7 +860,7 @@ def _av_transport_event(self, sub_handler: SubscriptionHandler) -> None:
def _check_property(self):
if not self.is_initialized:
- self.logger.warning(f"Speaker '{self.uid}' is not initialized.")
+ self.logger.warning(f"Checkproperty: Speaker '{self.uid}' is not initialized.")
return False
if not self.coordinator:
self.logger.warning(f"Speaker '{self.uid}': coordinator is empty")
@@ -2674,7 +2677,7 @@ def _play_snippet(self, file_path: str, webservice_url: str, volume: int = -1, d
if not tag.duration:
self.logger.error("TinyTag duration is none.")
else:
- duration = round(tag.duration) + duration_offset
+ duration = tag.duration + duration_offset
self.logger.debug(f"TTS track duration: {duration}s, TTS track duration offset: {duration_offset}s")
file_name = quote(os.path.split(file_path)[1])
snippet_url = f"{webservice_url}/{file_name}"
@@ -2994,7 +2997,7 @@ class Sonos(SmartPlugin):
"""
Main class of the Plugin. Does all plugin specific stuff
"""
- PLUGIN_VERSION = "1.8.4"
+ PLUGIN_VERSION = "1.8.5"
def __init__(self, sh):
"""Initializes the plugin."""
diff --git a/sonos/plugin.yaml b/sonos/plugin.yaml
index 0e7feeb79..283feeefc 100755
--- a/sonos/plugin.yaml
+++ b/sonos/plugin.yaml
@@ -12,7 +12,7 @@ plugin:
documentation: https://github.com/smarthomeNG/plugins/blob/master/sonos/README.md
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/25151-sonos-anbindung
- version: 1.8.4 # Plugin version
+ version: 1.8.5 # Plugin version
sh_minversion: 1.5.1 # minimum shNG version to use this plugin
py_minversion: 3.8 # minimum Python version to use for this plugin
multi_instance: False # plugin supports multi instance
diff --git a/sonos/soco/__init__.py b/sonos/soco/__init__.py
index 573076afc..b965ab2ca 100755
--- a/sonos/soco/__init__.py
+++ b/sonos/soco/__init__.py
@@ -17,7 +17,7 @@
__author__ = "The SoCo-Team "
# Please increment the version number and add the suffix "-dev" after
# a release, to make it possible to identify in-development code
-__version__ = "0.29.1"
+__version__ = "0.30.2"
__website__ = "https://github.com/SoCo/SoCo"
__license__ = "MIT License"
diff --git a/sonos/soco/alarms.py b/sonos/soco/alarms.py
index 076dc44d6..0b5dc8dc1 100755
--- a/sonos/soco/alarms.py
+++ b/sonos/soco/alarms.py
@@ -1,7 +1,7 @@
"""This module contains classes relating to Sonos Alarms."""
import logging
import re
-from datetime import datetime
+from datetime import datetime, timedelta
from . import discovery
from .core import _SocoSingletonBase, PLAY_MODES
@@ -10,6 +10,12 @@
log = logging.getLogger(__name__)
TIME_FORMAT = "%H:%M:%S"
+RECURRENCE_KEYWORD_EQUIVALENT = {
+ "DAILY": "ON_0123456",
+ "ONCE": "ON_", # Never reoccurs
+ "WEEKDAYS": "ON_12345",
+ "WEEKENDS": "ON_06",
+}
def is_valid_recurrence(text):
@@ -179,6 +185,44 @@ def update(self, zone=None):
if not new_alarms.get(alarm_id):
self.alarms.pop(alarm_id)
+ def get_next_alarm_datetime(
+ self, from_datetime=None, include_disabled=False, zone_uid=None
+ ):
+ """Get the next alarm trigger datetime.
+
+ Args:
+ from_datetime (datetime, optional): a datetime to reference next
+ alarms from. This argument filters by alarms on or after this
+ exact time. Since alarms do not store timezone information,
+ the output timezone will match this input argument. Defaults
+ to `datetime.now()`.
+ include_disabled (bool, optional): If `True` then disabled alarms
+ will be included in searching for the next alarm. Defaults to
+ `False`.
+ zone_uid (str, optional): If set the alarms will be filtered by
+ zone with this UID. Defaults to `None`.
+
+ Returns:
+ datetime: The next alarm trigger datetime or None if disabled
+ """
+ if from_datetime is None:
+ from_datetime = datetime.now()
+
+ next_alarm_datetime = None
+ for alarm_id in self.alarms:
+ this_alarm = self.alarms.get(alarm_id)
+ if zone_uid is not None and this_alarm.zone.uid != zone_uid:
+ continue
+ this_next_datetime = this_alarm.get_next_alarm_datetime(
+ from_datetime, include_disabled
+ )
+ if (next_alarm_datetime is None) or (
+ this_next_datetime is not None
+ and this_next_datetime < next_alarm_datetime
+ ):
+ next_alarm_datetime = this_next_datetime
+ return next_alarm_datetime
+
class Alarm:
@@ -372,6 +416,66 @@ def alarm_id(self):
"""`str`: The ID of the alarm, or `None`."""
return self._alarm_id
+ def get_next_alarm_datetime(self, from_datetime=None, include_disabled=False):
+ """Get the next alarm trigger datetime.
+
+ Args:
+ from_datetime (datetime, optional): a datetime to reference next
+ alarms from. This argument filters by alarms on or after this
+ exact time. Since alarms do not store timezone information,
+ the output timezone will match this input argument. Defaults
+ to `datetime.now()`.
+ include_disabled (bool, optional): If `True` then the next datetime
+ will be computed even if the alarm is disabled. Defaults to
+ `False`.
+
+ Returns:
+ datetime: The next alarm trigger datetime or None if disabled
+ """
+ if not self.enabled and not include_disabled:
+ return None
+
+ if from_datetime is None:
+ from_datetime = datetime.now()
+
+ # Convert helper words to number recurrences
+ recurrence_on_str = RECURRENCE_KEYWORD_EQUIVALENT.get(
+ self.recurrence, self.recurrence
+ )
+
+ # For the purpose of finding the next alarm a "once" trigger that has
+ # yet to trigger is everyday (the next possible day)
+ if recurrence_on_str == RECURRENCE_KEYWORD_EQUIVALENT["ONCE"]:
+ recurrence_on_str = RECURRENCE_KEYWORD_EQUIVALENT["DAILY"]
+
+ # Trim the 'ON_' prefix, convert to int, remove duplicates
+ recurrence_set = set(map(int, recurrence_on_str[3:]))
+
+ # Convert Sonos weekdays to Python weekdays
+ # Sonos starts on Sunday, Python starts on Monday
+ if 0 in recurrence_set:
+ recurrence_set.remove(0)
+ recurrence_set.add(7)
+ recurrence_set = {x - 1 for x in recurrence_set}
+
+ # Begin search from next day if it would have already triggered today
+ offset = 0
+ if self.start_time <= from_datetime.time():
+ offset += 1
+
+ # Find first day
+ from_datetime_day = from_datetime.weekday()
+ offset_weekday = (from_datetime_day + offset) % 7
+ while offset_weekday not in recurrence_set:
+ offset += 1
+ offset_weekday = (from_datetime_day + offset) % 7
+
+ return datetime.combine(
+ from_datetime.date() + timedelta(days=offset),
+ self.start_time,
+ tzinfo=from_datetime.tzinfo,
+ )
+
def get_alarms(zone=None):
"""Get a set of all alarms known to the Sonos system.
diff --git a/sonos/soco/core.py b/sonos/soco/core.py
index ff67db3ae..1fb2c1e2f 100755
--- a/sonos/soco/core.py
+++ b/sonos/soco/core.py
@@ -236,6 +236,7 @@ class SoCo(_SocoSingletonBase):
is_soundbar
is_satellite
has_satellites
+ sub_crossover
sub_enabled
sub_gain
is_subwoofer
@@ -510,7 +511,14 @@ def has_subwoofer(self):
Only provides reliable results when called on the soundbar
or subwoofer devices if configured in a home theater setup.
+
+ Sonos Amp devices support a directly-connected 3rd party subwoofer
+ connected over RCA. This property is always enabled for those devices.
"""
+ model_name = self.speaker_info["model_name"].lower()
+ if model_name.endswith("sonos amp"):
+ return True
+
self.zone_group_state.poll(self)
channel_map = self._channel_map or self._ht_sat_chan_map
if not channel_map:
@@ -1074,6 +1082,46 @@ def surround_enabled(self, enable):
]
)
+ @property
+ def sub_crossover(self):
+ """int: Reports the current subwoofer crossover frequency in Hz.
+
+ Only supported on Amp devices.
+ """
+ model_name = self.speaker_info["model_name"].lower()
+ if not model_name.endswith("sonos amp"):
+ return None
+
+ response = self.renderingControl.GetEQ(
+ [("InstanceID", 0), ("EQType", "SubCrossover")]
+ )
+ return int(response["CurrentValue"])
+
+ @sub_crossover.setter
+ def sub_crossover(self, frequency):
+ """Set the subwoofer crossover frequency. Only supported on Amp devices.
+
+ :param frequency: Desired subwoofer crossover frequency in Hz
+ :type frequency: int
+ """
+ model_name = self.speaker_info["model_name"].lower()
+ if not model_name.endswith("sonos amp"):
+ message = "Subwoofer crossover not supported on this device."
+ raise NotSupportedException(message)
+
+ if not 50 <= frequency <= 110:
+ raise ValueError(
+ "Invalid value, must be integer between 50 and 110 inclusive"
+ )
+
+ self.renderingControl.SetEQ(
+ [
+ ("InstanceID", 0),
+ ("EQType", "SubCrossover"),
+ ("DesiredValue", int(frequency)),
+ ]
+ )
+
@property
def sub_enabled(self):
"""bool: Reports if the subwoofer is enabled.
diff --git a/sonos/soco/data_structures.py b/sonos/soco/data_structures.py
index 40a0bb44e..6a4861dc9 100755
--- a/sonos/soco/data_structures.py
+++ b/sonos/soco/data_structures.py
@@ -458,7 +458,7 @@ def __init__(
restricted=True,
resources=None,
desc="RINCON_AssociatedZPUDN",
- **kwargs
+ **kwargs,
):
"""
Args:
@@ -629,7 +629,7 @@ def from_element(cls, element): # pylint: disable=R0914
restricted=restricted,
resources=resources,
desc=desc,
- **content
+ **content,
)
@classmethod
diff --git a/sonos/soco/discovery.py b/sonos/soco/discovery.py
index e81991005..c84685e9a 100755
--- a/sonos/soco/discovery.py
+++ b/sonos/soco/discovery.py
@@ -24,7 +24,7 @@ def discover(
interface_addr=None,
household_id="Sonos",
allow_network_scan=False,
- **network_scan_kwargs
+ **network_scan_kwargs,
):
"""Discover Sonos zones on the local network.
diff --git a/sonos/soco/events_base.py b/sonos/soco/events_base.py
index f53e8e20f..8c7e6a6a0 100755
--- a/sonos/soco/events_base.py
+++ b/sonos/soco/events_base.py
@@ -438,6 +438,7 @@ def success(headers):
self.timeout = int(timeout.lstrip("Second-"))
self._timestamp = time.time()
self.is_subscribed = True
+ service.soco.zone_group_state.add_subscription(self)
log.debug(
"Subscribed to %s, sid: %s",
service.base_url + service.event_subscription_url,
@@ -653,6 +654,7 @@ def _cancel_subscription(self, msg=None):
# an attempt to unsubscribe fails
self._has_been_unsubscribed = True
self._timestamp = None
+ self.service.soco.zone_group_state.remove_subscription(self)
# Cancel any auto renew
self._auto_renew_cancel()
if msg:
diff --git a/sonos/soco/events_twisted.py b/sonos/soco/events_twisted.py
index d5a5749ba..f1dac229e 100755
--- a/sonos/soco/events_twisted.py
+++ b/sonos/soco/events_twisted.py
@@ -178,7 +178,7 @@ def listen(self, ip_address):
port_number, factory, interface=ip_address
)
break
- # pylint: disable=invalid-name
+ # pylint: disable=invalid-name,used-before-assignment
except twisted.internet.error.CannotListenError as e:
log.warning(e)
continue
diff --git a/sonos/soco/plugins/plex.py b/sonos/soco/plugins/plex.py
index 5406ead53..df44ac033 100755
--- a/sonos/soco/plugins/plex.py
+++ b/sonos/soco/plugins/plex.py
@@ -203,7 +203,7 @@ def add_to_queue(self, plex_media, position=0, as_next=False, **kwargs):
("DesiredFirstTrackNumberEnqueued", position),
("EnqueueAsNext", int(as_next)),
],
- **kwargs
+ **kwargs,
)
qnumber = response["FirstTrackNumberEnqueued"]
return int(qnumber)
diff --git a/sonos/soco/plugins/sharelink.py b/sonos/soco/plugins/sharelink.py
index b6f866b34..0919d77f7 100755
--- a/sonos/soco/plugins/sharelink.py
+++ b/sonos/soco/plugins/sharelink.py
@@ -266,7 +266,7 @@ def add_share_link_to_queue(self, uri, position=0, as_next=False, **kwargs):
("DesiredFirstTrackNumberEnqueued", position),
("EnqueueAsNext", int(as_next)),
],
- **kwargs
+ **kwargs,
)
qnumber = response["FirstTrackNumberEnqueued"]
diff --git a/sonos/soco/services.py b/sonos/soco/services.py
index 98209d0c1..16ee964ad 100755
--- a/sonos/soco/services.py
+++ b/sonos/soco/services.py
@@ -36,6 +36,7 @@
from collections import namedtuple
from xml.sax.saxutils import escape
+import xml.etree.ElementTree as ET
import requests
from .cache import Cache
@@ -558,24 +559,30 @@ def handle_upnp_error(self, xml_error):
# errorDescription is not required, and Sonos does not seem to use it.
# NB need to encode unicode strings before passing to ElementTree
- xml_error = xml_error.encode("utf-8")
- error = XML.fromstring(xml_error)
- log.debug("Error %s", xml_error)
- error_code = error.findtext(".//{urn:schemas-upnp-org:control-1-0}errorCode")
- if error_code is not None:
- description = self.UPNP_ERRORS.get(int(error_code), "")
- raise SoCoUPnPException(
- message="UPnP Error {} received: {} from {}".format(
- error_code, description, self.soco.ip_address
- ),
- error_code=error_code,
- error_description=description,
- error_xml=xml_error,
+ try:
+ xml_error = xml_error.encode("utf-8")
+ error = ET.fromstring(xml_error)
+ log.debug("Error %s", xml_error)
+ error_code = error.findtext(
+ ".//{urn:schemas-upnp-org:control-1-0}errorCode"
)
+ if error_code is not None:
+ description = self.UPNP_ERRORS.get(int(error_code), "")
+ raise SoCoUPnPException(
+ message="UPnP Error {} received: {} from {}".format(
+ error_code, description, self.soco.ip_address
+ ),
+ error_code=error_code,
+ error_description=description,
+ error_xml=xml_error,
+ )
- # Unknown error, so just return the entire response
- log.error("Unknown error received from %s", self.soco.ip_address)
- raise UnknownSoCoException(xml_error)
+ raise ValueError("No error code found in UPnP error response")
+ except (ET.ParseError, ValueError) as e:
+ # Unknown error, so just return the entire response
+ error = "Error parsing UPnP error response from %s: %s"
+ log.error(error, self.soco.ip_address, xml_error)
+ raise UnknownSoCoException(xml_error) from e
def subscribe(
self, requested_timeout=None, auto_renew=False, event_queue=None, strict=True
diff --git a/sonos/soco/soap.py b/sonos/soco/soap.py
index 93348ecbc..d89f53e75 100755
--- a/sonos/soco/soap.py
+++ b/sonos/soco/soap.py
@@ -112,7 +112,7 @@ def __init__(
soap_action=None,
soap_header=None,
namespace=None,
- **request_args
+ **request_args,
):
"""
Args:
@@ -295,7 +295,7 @@ def call(self):
headers=headers,
data=data.encode("utf-8"),
timeout=timeout,
- **self.request_args
+ **self.request_args,
)
_LOG.debug("Received %s, %s", response.headers, response.text)
status = response.status_code
diff --git a/sonos/soco/zonegroupstate.py b/sonos/soco/zonegroupstate.py
index 1f89952f2..1b5526771 100755
--- a/sonos/soco/zonegroupstate.py
+++ b/sonos/soco/zonegroupstate.py
@@ -63,14 +63,15 @@
import asyncio
import logging
import time
+from weakref import WeakSet
from lxml import etree as LXML
from . import config
+from .events_base import SubscriptionBase
from .exceptions import NotSupportedException, SoCoException, SoCoUPnPException
from .groups import ZoneGroup
-EVENT_CACHE_TIMEOUT = 60
POLLING_CACHE_TIMEOUT = 5
NEVER_TIME = -1200.0
@@ -115,6 +116,7 @@ def __init__(self):
self._cache_until = NEVER_TIME
self._last_zgs = None
+ self._subscriptions = WeakSet()
# Statistics
self.total_requests = 0
@@ -124,6 +126,39 @@ def clear_cache(self):
"""Clear the cache timestamp."""
self._cache_until = NEVER_TIME
+ def add_subscription(self, subscription: SubscriptionBase):
+ """Start tracking a ZoneGroupTopology subscription."""
+ if (
+ subscription.service.service_type == "ZoneGroupTopology"
+ and subscription not in self._subscriptions
+ ):
+ self._subscriptions.add(subscription)
+ _LOG.debug(
+ "Monitoring ZoneGroupTopology subscription %s on %s",
+ subscription.sid,
+ subscription.service.soco,
+ )
+
+ def remove_subscription(self, subscription: SubscriptionBase):
+ """Stop tracking a ZoneGroupTopology subscription."""
+ if subscription in self._subscriptions:
+ self._subscriptions.remove(subscription)
+ _LOG.debug(
+ "Discarded unsubscribed subscription %s from %s, %d remaining",
+ subscription.sid,
+ subscription.service.soco,
+ len(self._subscriptions),
+ )
+
+ @property
+ def has_subscriptions(self):
+ """Return True if active subscriptions are updating this ZoneGroupState."""
+ stale_subscriptions = [sub for sub in self._subscriptions if not sub.time_left]
+ for sub in stale_subscriptions:
+ _LOG.debug("Discarding stale subscription: %s", sub.sid)
+ self.remove_subscription(sub)
+ return bool(self._subscriptions)
+
def clear_zone_groups(self):
"""Clear all known group sets."""
self.groups.clear()
@@ -133,6 +168,15 @@ def clear_zone_groups(self):
def poll(self, soco):
"""Poll using the provided SoCo instance and process the payload."""
# pylint: disable=protected-access
+ if self.has_subscriptions:
+ self.total_requests += 1
+ _LOG.debug(
+ "Subscriptions (%s) still active during poll for %s, using cache",
+ len(self._subscriptions),
+ soco.ip_address,
+ )
+ return
+
if time.monotonic() < self._cache_until:
self.total_requests += 1
_LOG.debug(
@@ -156,6 +200,8 @@ def poll(self, soco):
try:
zgs = soco.zoneGroupTopology.GetZoneGroupState()["ZoneGroupState"]
self.process_payload(payload=zgs, source="poll", source_ip=soco.ip_address)
+ self._cache_until = time.monotonic() + POLLING_CACHE_TIMEOUT
+ _LOG.debug("Extending ZGS cache by %ss", POLLING_CACHE_TIMEOUT)
# In the event of failure, we fall back to using a ZGT event to
# determine the ZGS. Fallback behaviour can be disabled by setting the
@@ -244,22 +290,12 @@ async def update_zgs_by_event_asyncio(speaker):
def process_payload(self, payload, source, source_ip):
"""Update using the provided XML payload."""
self.total_requests += 1
-
- def update_cache():
- if source == "event":
- timeout = EVENT_CACHE_TIMEOUT
- else:
- timeout = POLLING_CACHE_TIMEOUT
- self._cache_until = time.monotonic() + timeout
- _LOG.debug("Setting ZGS cache to %ss", timeout)
-
tree = normalize_zgs_xml(payload)
normalized_zgs = str(tree)
if normalized_zgs == self._last_zgs:
_LOG.debug(
"Duplicate ZGS received from %s (%s), ignoring", source_ip, source
)
- update_cache()
return
self.processed_count += 1
@@ -272,7 +308,6 @@ def update_cache():
)
self.update_soco_instances(tree)
- update_cache()
self._last_zgs = normalized_zgs
def parse_zone_group_member(self, member_element):