@@ -57,20 +66,48 @@ 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
-###17.02.2019
+
+## Change Log
+
+### 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
+### 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
+### 19.01.2019
- Version auf 1.0.0.2 erhöht
- ContactSensor Interface eingebaut
- Doku für ContactSensor Interface ergänzt
@@ -79,7 +116,7 @@ Die Actions unterscheiden sich zwischen Payload V2 und V3 oft nur durch Gross/Kl
- ReportLockState eingebaut
- Doku für die Erstellung des Alexa-Skill´s auf Amazon als PDF erstellt
-###31.12.2018
+### 31.12.2018
- Version auf 1.0.0.1 erhöht
- CameraStreamController eingebaut
- Dokumentation für CameraStreamController ergänzt
@@ -87,116 +124,177 @@ Die Actions unterscheiden sich zwischen Payload V2 und V3 oft nur durch Gross/Kl
- Dokumentation für PowerLevelController ergänzt
- Debugs und Testfunktionen kontrolliert und für Upload entfernt
-###24.12.2018
+### 24.12.2018
- Doku für PercentageController erstellt
- Bug Fix für fehlerhafte Testfunktionen aus der Lambda
-###12.12.2018
+### 12.12.2018
- Scene Controller eingebaut
- Doku für Scene Controller erstellt
- PercentageController eingebaut
-##Icons / Catagories
+
+## Requrirements
+
+Das Plugin benötigt Modul Python-Requests. Dies sollte mit dem Core immer auf dem aktuellen Stand mitkommen.
+
+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.
+
-
Value
-
Description
-
Notes
+
COMPUTER
+
A non-mobile computer, such as a desktop computer.
-
-
-
ACTIVITY_TRIGGER
-
Describes a combination of devices set to a specific state, when the state change must occur in a specific order. For example, a "watch Netflix" scene might require the: 1. TV to be powered on & 2. Input set to HDMI1.
-
Applies to Scenes
+
CONTACT_SENSOR
+
An endpoint that detects and reports changes in contact between two surfaces.
-
CAMERA
-
Indicates media devices with video or photo capabilities.
-
+
DOOR
+
A door.
-
CONTACT_SENSOR
-
Indicates an endpoint that detects and reports changes in contact between two surfaces.
-
+
DOORBELL
+
A doorbell.
-
DOOR
-
Indicates a door.
-
+
EXTERIOR_BLIND
+
A window covering on the outside of a structure.
-
DOORBELL
-
Indicates a doorbell.
-
+
FAN
+
A fan.
-
LIGHT
-
Indicates light sources or fixtures.
-
+
GAME_CONSOLE
+
A game console, such as Microsoft Xbox or Nintendo Switch
-
MICROWAVE
-
Indicates a microwave oven endpoint.
-
+
GARAGE_DOOR
+
A garage door. Garage doors must implement the ModeController interface to open and close the door.
-
MOTION_SENSOR
-
Indicates an endpoint that detects and reports movement in an area.
-
+
INTERIOR_BLIND
+
A window covering on the inside of a structure.
-
OTHER
-
An endpoint that cannot be described in one of the other categories.
-
+
LAPTOP
+
A laptop or other mobile computer.
-
SCENE_TRIGGER
-
Describes a combination of devices set to a specific state, when the order of the state change is not important. For example a bedtime scene might include turning off lights and lowering the thermostat, but the order is unimportant.
-
Applies to Scenes
+
LIGHT
+
A light source or fixture.
-
SMARTLOCK
-
Indicates an endpoint that locks.
-
+
MICROWAVE
+
A microwave oven.
-
SMARTPLUG
-
Indicates modules that are plugged into an existing electrical outlet.
-
Can control a variety of devices.
+
MOBILE_PHONE
+
A mobile phone.
-
SPEAKER
-
Indicates the endpoint is a speaker or speaker system.
-
+
MOTION_SENSOR
+
An endpoint that detects and reports movement in an area.
-
SWITCH
-
Indicates in-wall switches wired to the electrical system.
-
Can control a variety of devices.
+
MUSIC_SYSTEM
+
A network-connected music system.
-
TEMPERATURE_SENSOR
-
Indicates endpoints that report the temperature only.
-
The endpoint's temperature data is not shown in the Alexa app.
+
NETWORK_HARDWARE
+
A network router.
-
THERMOSTAT
-
Indicates endpoints that control temperature, stand-alone air conditioners, or heaters with direct temperature control.
-
+
OTHER
+
An endpoint that doesn't belong to one of the other categories.
-
TV
-
Indicates the endpoint is a television.
-
+
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 )
@@ -294,7 +392,7 @@ OG:
enforce_updates: 'true'
-##Entwicklung / Einbau von neuen Fähigkeiten
+## 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)
@@ -350,7 +448,7 @@ alexa_icon = "THERMOSTAT" = Thermostatcontroller
alexa_icon = "TEMPERATURE_SENSOR" = Temperatursensor
-###Thermostatsensor
+### 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.
@@ -362,7 +460,7 @@ alexa_actions : "ReportTemperature"
Alexa wie ist die Temperatur in der Küche ?
-###Verändern der Temperatur (SetTargetTemperature AdjustTargetTemperature)
+### Verändern der Temperatur (SetTargetTemperature AdjustTargetTemperature)
alexa_actions = "SetTargetTemperature AdjustTargetTemperature"
@@ -377,7 +475,7 @@ Alexa stelle die Temperatur in der Küche auf zweiundzwanzig Grad
Alexa wie ist die Temperatur in der Küche eingestellt ?
-###Thermostatmode
+### Thermostatmode
alexa_actions = "SetThermostatMode"
@@ -571,7 +669,7 @@ Beispiel :
## Alexa-PowerLevelController
-## !!!! erst ab Plugin-Version 1.0.0.0.1 oder höher !!!!
+## !!!! 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
@@ -647,7 +745,7 @@ Beispiel Konfiguration im yaml-Format:
## Alexa-LockController
-## !!!! erst ab Plugin-Version 1.0.0.0.2 oder höher !!!!
+## !!!! 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
@@ -753,7 +851,7 @@ Beispiel mit einem Aktor-Kanal für öffnen, ein Aktor-Kanal für schliessen mit
## Alexa-CameraStreamContoller
-## !!!! erst ab Plugin-Version 1.0.0.0.1 oder höher !!!!
+## !!!! erst ab Plugin-Version 1.0.1 oder höher !!!!
Alexa zeige die Haustür Kamera.
@@ -763,7 +861,7 @@ d.h. :
- 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 : CamProxy4AlexpaP3
+#Look out for : AlexaCamProxy4P3
Aus den bereitgestellten Streams wird
@@ -771,12 +869,16 @@ 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_camera_imageUri: die URL des Vorschau-Pictures der Kamera
+##### 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_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
+##### 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):
@@ -809,6 +911,7 @@ siehe Tabelle unten für mögliche Werte
alexa_stream_3: '{.......
}'
alexa_csc_proxy_uri: alexatestcam.ddns.de:443
+ alexa_proxy_credentials: user:pwd
Als Action ist fix "alexa_actions: InitializeCameraStreams" anzugeben.
@@ -867,7 +970,7 @@ scene:
alexa_retrievable : false
-##ContactSensor Interface
+## ContactSensor Interface
Alexa ist das Küchenfenster geschlossen ?
Alexa ist das Küchenfenster geöffnet ?
@@ -892,7 +995,7 @@ fensterkontakt:
-##ColorController
+## ColorController
Alexa, setze Licht Speicher auf rot
@@ -943,3 +1046,217 @@ Speicher:
- G_WERT = list[1]
- B_WERT = list[2]
+
+
+## RangeController
+
+
+Folgende Paramter sind anzugeben :
+
+
+
+
+## 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
+
+
+
+## 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
+![webif_Seite1](./assets/Alexa4P3_Seite1.jpg)
+
+Auf der Zweiten Seite wird ein Kommunikationsprotokoll zur Alexa-Cloud angezeigt.
+![webif_Seite2](./assets/Alexa4P3_Seite2.jpg)
+
+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.
+
+![webif_Seite3](./assets/Alexa4P3_Seite3.jpg)
+
+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.
+
+![webif_Seite4](./assets/Alexa4P3_Seite4.jpg)
+
+
+# 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.
+
+
+
+
+ {% if yaml_result %}{% else %}{{ _('no data available') }}{% endif %}
+
+
+
+
+{% endblock bodytab4 %}
+
+
+
+
diff --git a/alexarc4shng/.idea/.gitignore b/alexarc4shng/.idea/.gitignore
new file mode 100644
index 000000000..5c98b4288
--- /dev/null
+++ b/alexarc4shng/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/alexarc4shng/.idea/alexarc4shng.iml b/alexarc4shng/.idea/alexarc4shng.iml
new file mode 100644
index 000000000..671160631
--- /dev/null
+++ b/alexarc4shng/.idea/alexarc4shng.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/inspectionProfiles/Project_Default.xml b/alexarc4shng/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 000000000..6bacd1e29
--- /dev/null
+++ b/alexarc4shng/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/markdown-navigator.xml b/alexarc4shng/.idea/markdown-navigator.xml
new file mode 100644
index 000000000..6b82ce2ab
--- /dev/null
+++ b/alexarc4shng/.idea/markdown-navigator.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/markdown-navigator/profiles_settings.xml b/alexarc4shng/.idea/markdown-navigator/profiles_settings.xml
new file mode 100644
index 000000000..db0626632
--- /dev/null
+++ b/alexarc4shng/.idea/markdown-navigator/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/misc.xml b/alexarc4shng/.idea/misc.xml
new file mode 100644
index 000000000..6c993b77f
--- /dev/null
+++ b/alexarc4shng/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/modules.xml b/alexarc4shng/.idea/modules.xml
new file mode 100644
index 000000000..4c62daab7
--- /dev/null
+++ b/alexarc4shng/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/.idea/vcs.xml b/alexarc4shng/.idea/vcs.xml
new file mode 100644
index 000000000..94a25f7f4
--- /dev/null
+++ b/alexarc4shng/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alexarc4shng/README.md b/alexarc4shng/README.md
new file mode 100755
index 000000000..cff9dd7f9
--- /dev/null
+++ b/alexarc4shng/README.md
@@ -0,0 +1,431 @@
+# AlexaRc4shNG
+
+#### Version 1.0.2
+
+The plugin gives the possibilty to control an Alexa-Echo-Device remote by smartHomeNG. So its possible to switch on an TuneIn-Radio Channel, send some messages via Text2Speech when an event happens on the knx-bus or on the Visu. On the Web-Interface you can define your own commandlets (functions). The follwing functions are available on the Web-Interface :
+
+- Store a cookie-file to get access to the Alexa-WebInterface
+- manually Login with your credentials (stored in the /etc/plugin.yaml)
+- See all available devices, select one to send Test-Functions
+- define Commandlets - you can load,store,delete, check and test Commandlets
+- the Commandlets can be loaded to the webinterface by clicking on the list
+- the Json-Structure can be checked on the WebInterface
+
+In the API-URL and in the json-payload you have to replace the real values from the Alexa-Webinterface with the following placeholders. For testing functions its not really neccessary to use the placeholders.
+
+This plugin for smarthomeNG is mainly based on the informations of
+[Lötzimmer](https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html) ,[Apollon77](https://github.com/Apollon77/alexa-remote) and the [openhab2](https://community.openhab.org/t/released-openhab2-amazon-echo-control-binding-controlling-alexa-from-openhab2/37844)
+
+Special thanks to Jonofe from the [Edomi-Forum](https://knx-user-forum.de/forum/projektforen/edomi/1240964-alexa-smarthome-skill-payload-version-3) who spent a nigth and half an evenning to support me with SSML.
+#### !! So many thanks for the very good research and development in the past !!
+
+## table of content
+
+1. [PlaceHolders](#placeholders)
+2. [Change Log](#changelog)
+3. [Requirements](#requirements)
+4. [Cookie](#cookie)
+5. [Configuration](#config)
+6. [functions](#functions)
+7. [Web-Interface](#webinterface)
+8. [How to implement new Commands](#newCommand)
+9. [Tips for existing Command-Lets](#tipps)
+
+### Existing Command-Lets
+
+
+- Play (Plays the last paused Media)
+- Pause (pauses the actual media)
+- Text2Speech (sends a Text to the echo, echo will speak it)
+- StartTuneInStation (starts a TuneInRadiostation with the guideID you send)
+- SSML (Speak to Text with[Speech Synthesis Markup Language](https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html))
+- VolumeAdj (adjusts the volume during playing some media not working from webinterface test functions)
+- VolumeSet (sets the volume to value from 0-100 percent)
+
+
+### Placeholders :
+```yaml
+ = 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
+```
+#### !! Please keep in mind to use the "<", ">", "#" and "/#" to qualify the placeholders !!
+
+## ChangeLog
+
+#### 2020.03.20 Version 1.0.2
+
+- changed public function "send_cmd_by_curl" to "send_cmd"
+- removed pycurl
+- changed Communication to Python Requests
+- added translation for the Web-Interface
+- added public function "get_last_alexa"
+
+#### 2018.07.26 Version 1.0.1
+- Encoding credentials now possible on the Web-Interface (for security reason use this function to encode you credentials)
+
+#### 2018.05.20 Version 1.0.1
+- replaced lib.scheduler with self.scheduler_add
+
+#### 2018.05.19 - Version 1.0.1
+- changed version to 1.0.1
+- changed to lib.item and lib.scheduler
+- the credentials have to be stored in base64 encoded
+- added Login / LogOff Button to the Web-Interface
+- added Auto-Login function - when there is no cookie-file with correct values and credentials are specicified, the plugin automaticaly logs in
+- the log-in (the cookie) will be refreshed after the login_update_cycle
+- changed methods-names and parameters to lower case and underscore separated names
+
+
+#### 2018.04.30 - Version 1.0.0
+- added CommandLet for SSML-Support
+- added CommandLet for Play (Plays the paused media)
+- added CommandLet for Pause (pauses media)
+- added CommandLet for VolumeAdj (only working while media is playing, not working from test functions on the webinterface)
+- added CommandLet for VolumeSet (working all the time)
+- added CommandLet for LoadPlayerInfo (right now loaded but nowhere stored)
+- added Item to enable AlexaRemoteControl by UZSU
+
+### Changes Since version 1.x.x
+
+- no Changes, first Version
+
+
+
+## Requirements
+
+
+### Needed software
+
+* smarthomeNg 1.5.2 and above for the web-interface
+* needs Python requests
+* a valid [Cookie](#cookie) from an alexa.amazon-Web-Site Session
+* if you work with Autologin the credentials have to be entered "base64"-encoded. You can encode you credentials on the web-interface of the plugin "user.test@gmail.com:your_pwd" you will get ```dXNlci50ZXN0QGdtYWlsLmNvbTp5b3VyX3B3ZA==``` .
+So please enter ```dXNlci50ZXN0QGdtYWlsLmNvbTp5b3VyX3B3ZA==``` in the /etc/plugin.yaml
+
+If you don trust the website for encoding you credential, you can do it in the python-console.
+Open a terminal and try the following code.
+
+```
+python3
+import base64
+base64.b64encode('user.test@gmail.com:your_pwd'.encode('utf-8'))
+
+you will get
+
+b'dXNlci50ZXN0QGdtYWlsLmNvbTp5b3VyX3B3ZA=='
+
+use
+
+dXNlci50ZXN0QGdtYWlsLmNvbTp5b3VyX3B3ZA==
+
+for your credentials
+```
+
+
+
+### Supported Hardware
+
+* all Amazon Echo-Devices
+
+## Cookie
+
+First possibility - without Credentials :
+
+Plugins are available for most of the common browsers.
+After installing the plugin you have to login to your alexa.amazon-Web console. Now Export the cookie by using the plugin.
+Open the cookie-file with a Texteditor select all and copy it to the clipboard.
+Go to the Webinterface of the plugin and paste the content of the cookie-file to the textarea on Tab "Cookie-Handling". Store the cookie.
+When the cookie was successfull stored you can find you Echo-Devices on the Tab with the Alexa-devices.
+
+Second possibility - with Credentials :
+
+When the plugin will be started and credentials are found in plugin.yaml, the plugin tests if the informations in the cookie-file are still guilty. If not the plugin tries to login with the credentials himself and stores the informations in the cookie-file. The cookie will updated in the cycle specified in "login_update_cycle" in the plugin.yaml
+
+## Configuration
+
+### plugin.yaml
+
+The plugin needs to be defined in the /etc/plugin.yaml in this way.
+The attributes are :
+plugin_name -> fix AlexaRc4shNG
+
+cookiefile -> the path to the cookie-file. Here it will stored from the Web-Interfache. Take care that you have write-permissions
+host -> the adress of you Alexa-WebInterface
+Item2EnableAlexaRC->Item controlled by UZSU or something else which enables the communication to Alexa-Amazon-devices. if you leave it blank the communication is enabled all the time 24/7. 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. In logics you have to check it yourself.
AlexaCredentials->User and Password for the Amazon-Alex-WebSite for automtic login
+alexa_credentials-> user:pwd (base64 encoded)
+item_2_enable_alexa_rc -> Item to allow smarthomeNG to send Commands to Echo's
+login_update_cycle->seconds to wait for automatic Login in to refresh the cookie
+
+
+
+```yaml
+AlexaRc4shNG:
+ plugin_name: AlexaRc4shNG
+ cookiefile: /usr/local/smarthome/plugins/alexarc4shng/cookies.txt
+ host: alexa.amazon.de
+ item_2_enable_alexa_rc: Item_to_enable_Alexaremote
+ alexa_credentials: :
+ login_update_cycle: 432000
+```
+
+
+
+### items.yaml
+
+The configuration of the item are done in the following way :
+
+alexa_cmd_01: comparison:EchoDevice:Commandlet:Value_to_Send
+
+
+### supported comparisons are :
+
+"True", "False" and for numeric values "<=",">=","=","<",">"
+
+#### Sample to switch on a Radiostation by using TuneIN
+```yaml
+Value = True means the item() becomes "ON"
+EchodotKueche = Devicename where the Command should be send to StartTuneInStaion = Name of the Commandlet
+s96141 = Value of the Radiostation (here S96141 = baden.fm)
+```
+
+example:
+`
+alexa_cmd_01: True:EchoDotKueche:StartTuneInStation:s96141
+`
+#### Sample to send Text with item-value included based on value lower then 20 degrees
+
+```yaml
+Value = <20.0 - send command when value of the item becomes less then 20.0
+EchodotKueche = Devicename where the Command should be send to
+Text2Speech = Name of the Commandlet
+Value_to_Send = Die Temperatur in der Kueche ist niedriger als 20 Grad Die Temperatur ist jetzt #test.testzimmer.temperature.actual/# Grad #test.testzimmer.temperature.actual/# = item-path of the value that should be inserted
+```
+
+example:
+`
+alexa_cmd_01: <20.0:EchoDotKueche:Text2Speech:Die Temperatur in der Kueche ist niedriger als 20 Grad Die Temperatur ist jetzt \#test.testzimmer.temperature.actual/\# Grad
+`
+
+You can find the paths of the items on the backend-WebInterface - section items.
+
+#### alexa_cmd_XX
+
+You can specify up to 99 Commands per shng-item. The plugin scanns the item.conf/item.yaml during initialization for commands starting with 01 up to 99.
+
+Please start all the time with 01 per item, the command-numbers must be serial, dont forget one. The scan of commands stops when there is no command found with the next number
+
+#### Example
+
+Example for settings in an item.yaml file :
+
+```yaml
+# items/my.yaml
+%YAML 1.1
+---
+
+OG:
+
+ Buero:
+ name: Buero
+ Licht:
+ type: bool
+ alexa_name: Licht Büro
+ alexa_description: Licht Büro
+ alexa_actions: TurnOn TurnOff
+ alexa_icon: LIGHT
+ alexa_cmd_01: True:EchoDotKueche:StartTuneInStation:s96141
+ alexa_cmd_02: True:EchoDotKueche:Text2Speech:Hallo das Licht im Buero ist eingeschalten
+ alexa_cmd_03: False:EchoDotKueche:Text2Speech:Hallo das Licht im Buero 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'
+
+```
+Example for settings in an item.conf file :
+
+```yaml
+# items/my.conf
+
+[OG]
+ [[Buero]]
+ name = Buero
+ [[[Licht]]]
+ type = bool
+ alexa_name = "Licht Büro"
+ alexa_description = "Licht Büro"
+ alexa_actions = "TurnOn TurnOff"
+ alexa_icon = "LIGHT"
+ alexa_cmd_01 = '"True:EchoDotKueche:StartTuneInStation:s96141"
+ alexa_cmd_02 ="True:EchoDotKueche:Text2Speech:Hallo das Licht im Buero ist eingeschalten"
+ alexa_cmd_03 = "False:EchoDotKueche:Text2Speech:Hallo das Licht im Buero 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 = truey_attr: setting
+```
+
+### logic.yaml
+Right now no logics are implemented. But you can trigger the functions by your own logic
+
+
+## Plugin-functions
+
+The plugin provides the following publich functions. You can use it for example in logics.
+
+### send_cmd(dvName, cmdName, mValue)
+
+example how to use in logics:
+
+```yaml
+sh.alexarc4shng.send_cmd("yourDevice", "Text2Speech", "yourValue")
+---
+sh.alexarc4shng.send_cmd('Kueche','Text2Speech','Der Sensor der Hebenlage signalisiert ein Problem.')
+```
+Sends a command to the device. "dvName" is the name of the device, "cmdName" is the name of the CommandLet, mValue is the value you would send.
+You can find all this informations on the Web-Interface.
+You can also user the [placeholders](#placeholders)
+
+- the result will be the HTTP-Status of the request as string (str)
+
+### get_last_alexa()
+
+This function returns the Device-Name of the last Echo Device which got a voice command. You can use it in logics to trigger events based on the last used Echo device.
+
+```yaml
+myLastDevice = sh.alexarc4shng.get_last_alexa()
+
+```
+# Web-Interface
+
+The Webinterface is reachable on you smarthomeNG server here :
+
+yourserver:8383/alexarc4shng/
+
+## Cookie-Handling
+
+On the Webinterface you can store you cookie-file to the shng-Server.
+Export it with a cookie.txt AddOn from the browser. Copy it to the clipboard.
+Paste it to the textarea in the Web-Interface and Store it.
+
+Now the available devices from your alexa-account will be discoverd an shown on the second tab.
+
+You can also login / logoff when credentials are available. Please see results in the textarea on the right. Please refresh page manually after successfull login via the Web-Interface.
+
+![PlaceHolder](./assets/webif1.jpg "jpg")
+
+## Alexa devices
+
+By click on one device the device will be selected as acutal device for tests.
+![PlaceHolder](./assets/webif2.jpg "jpg")
+
+## Command-Handling
+
+The Web-Interface gives help to define new Command-Lets. How you get the informations for new Commands see [section New Commands](#newCommand)
+
+Here you can define new Command-Lets, test them, save and delete them.
+You can check the JSON-Structure of you payload.
+
+When you click on an existing Command-Let it will be load to the Web-Interface.
+
+You can enter test values in the field for the values. Press Test and the command will be send to the device. You get back the HTTP-Status of the Request.
+
+For test dont modify the payload, just use the test-value-field
+
+![PlaceHolder](./assets/webif3.jpg "jpg")
+
+
+## How to create new Command-Lets (spy out the Amazon-Web-Interface)
+
+#### This documentation is based on Google-Chrome, but it's also possible to do this with other browsers.
+
+Open the Web-Interface for Alexa on Amazon. Select the page you want to spy out. Before click the command open the Debugger of the browser (F12). Select the network tab.
+When you click the command that you want to spy out the network traffic will be displayed in the debugger. Here you can get all the informations you need.
+Normally information will be send to amazon. So you have to concentrate on Post - Methods.
+
+
+![PlaceHolder](./assets/pic1.jpg "jpg")
+
+
+As example to spy out the station-id of a TuneIn Radio Station-ID you will it see it directly on context when you move your mouse to the post-command.
+You can copy the URL to the Clipboard an use is it in the AlexaRc4shNG.
+
+You can also copy it as cUrl Paste it into an editor and can find the payload in the --data section of the curl
+
+
+
+
+![PlaceHolder](./assets/pic2.jpg "jpg")
+
+For some commands you need to now the payload. You can get this by spying out the data. You have to select the network command. Then select the tab with Headers. In the bottom you will find the form-data. You can copy the payload to the clipboard an paste it into the AlexaRcshNG-WebInterface.
+
+
+![PlaceHolder](./assets/pic3.jpg "jpg")
+
+#### !! Dont forget to replace the values for deviceOwnerCustomerIdcustomerID serialNumber, serialNumber, family with the placeholders !!
+```
+
+
+
+
+
+!! for the Values !!
+
+ (for alpha Values)
+ (for numeric Values )
+
+```
+
+## Tips for existing Command-Lets :
+
+#### TuneIn
+You have to specify the guideID from Amazom as stationID "mValue". Station-Names are not supported right now.
+for example try the following:
+
+To locate your station ID, search for your station on TuneIn.com. Access your page and use the last set of digits of the resulting URL for your ID. For example:
+If your TuneIn.com URL is 'http://tunein.com/radio/tuneinstation-s######/', then your station ID would be 's######'
+(https://help.tunein.com/what-is-my-station-or-program-id-SJbg90quwz)
+
+#### SSML
+You have to put the SSML-Values into
+```
+
+```
+
+Find complete documentation to SSML [here](https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html)
+
+example :
+```
+
+I want to tell you a secret.I am not a real human..
+ Can you believe it?
+
+```
+
+You can also use [SpeechCons](https://developer.amazon.com/docs/custom-skills/speechcon-reference-interjections-german.html#including-a-speechcon-in-the-text-to-speech-response)
+
+example
+```
+
+ Here is an example of a speechcon.
+ ach du liebe zeit..
+
+```
+## Credits
+
+The idea for writing this plugin came from henfri. Got most of the informations from : http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html (German). Thank you Alex! A lot of code came from Ingo. He has done the alexa iobrokern implementation https://github.com/Apollon77 Thank you Ingo ! Also a lot of informations come from for the open-hab2 implemenation! Thank you [Michael](https://community.openhab.org/t/released-openhab2-amazon-echo-control-binding-controlling-alexa-from-openhab2/37844)
+
+Special thanks to Jonofe from the Edomi-Forum who spent a nigth and half an evenning to support my with SSML. Thank you Andre.
+#### !! So many thanks for the very good research and development)
+## Trademark Disclaimer
+
+TuneIn, Amazon Echo, Amazon Echo Spot, Amazon Echo Show, Amazon Music, Amazon Prime, Alexa and all other products and Amazon, TuneIn and other companies are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
diff --git a/alexarc4shng/__init__.py b/alexarc4shng/__init__.py
new file mode 100755
index 000000000..1adf74d95
--- /dev/null
+++ b/alexarc4shng/__init__.py
@@ -0,0 +1,1454 @@
+#!/usr/bin/env python3
+# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
+#########################################################################
+# Copyright 2020 AndreK andre.kohler01@googlemail.com
+#########################################################################
+# This file is part of SmartHomeNG.
+#
+# Sample plugin for new plugins to run with SmartHomeNG version 1.5.2 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 .
+#
+#########################################################################
+
+from lib.module import Modules
+from lib.model.smartplugin import *
+from lib.item import Items
+from lib.shtime import Shtime
+
+
+from datetime import datetime
+from io import BytesIO
+
+from subprocess import Popen, PIPE
+import json
+import sys
+import os
+import re
+import urllib3
+import time
+import base64
+import requests
+
+
+
+
+
+
+
+class shngObjects(object):
+ def __init__(self):
+ self.Devices = {}
+
+ def exists(self, id):
+ return id in self.Devices
+
+ def get(self, id):
+ return self.Devices[id]
+
+ def put(self, newID):
+ self.Devices[newID] = Device()
+
+ def all(self):
+ return list( self.Devices.values() )
+
+class Device(object):
+ def __init__(self):
+ self.Commands=[]
+
+class Cmd(object):
+ def __init__(self, id):
+ self.id = id
+ self.command = ''
+ self.ItemValue = ''
+ self.EndPoint = ''
+ self.Action = ''
+ self.Value = ''
+
+
+##############################################################################
+class EchoDevices(object):
+ def __init__(self):
+ self.devices = {}
+
+ def exists(self, id):
+ return id in self.devices
+
+ def get(self, id):
+ return self.devices[id]
+
+ def put(self, device):
+ self.devices[device.id] = device
+
+ def all(self):
+ return list( self.devices.values() )
+
+ def get_Device_by_Serial(self, serialNo):
+ for device in self.devices:
+ if (serialNo == self.devices[device].serialNumber):
+ return self.devices[device].id
+class Echo(object):
+ def __init__(self, id):
+ self.id = id
+ self.name = ""
+ self.serialNumber = ""
+ self.family = ""
+ self.deviceType = ""
+ self.deviceOwnerCustomerId = ""
+ self.playerinfo = {}
+ self.queueinfo = {}
+
+##############################################################################
+
+class AlexaRc4shNG(SmartPlugin):
+ PLUGIN_VERSION = '1.0.2'
+ ALLOW_MULTIINSTANCE = False
+ """
+ Main class of the Plugin. Does all plugin specific stuff and provides
+ the update functions for the items
+ """
+
+ def __init__(self, sh, *args, **kwargs):
+ # get Instances
+ self.logger = logging.getLogger(__name__)
+ self.sh = self.get_sh()
+ self.items = Items.get_instance()
+ self.shngObjects = shngObjects()
+ self.shtime = Shtime.get_instance()
+
+ # Init values
+ self.header = ''
+ self.cookie = {}
+ self.csrf = 'N/A'
+ self.postfields=''
+ self.login_state = False
+ self.last_update_time = ''
+ self.next_update_time = ''
+ # get parameters
+ self.cookiefile = self.get_parameter_value('cookiefile')
+ self.host = self.get_parameter_value('host')
+ self.AlexaEnableItem = self.get_parameter_value('item_2_enable_alexa_rc')
+ self.credentials = self.get_parameter_value('alexa_credentials').encode('utf-8')
+ self.credentials = base64.decodebytes(self.credentials).decode('utf-8')
+ self.LoginUpdateCycle = self.get_parameter_value('login_update_cycle')
+ self.update_file=self.sh.get_basedir()+"/plugins/alexarc4shng/lastlogin.txt"
+ self.rotating_log = []
+
+ if not self.init_webinterface():
+ self._init_complete = False
+
+ return
+
+ def run(self):
+ """
+ Run method for the plugin
+ """
+ self.logger.info("Plugin '{}': start method called".format(self.get_fullname()))
+ # get additional parameters from files
+ self.csrf = self.parse_cookie_file(self.cookiefile)
+
+ # Check login-state - if logged off and credentials are availabel login in
+ if os.path.isfile(self.cookiefile):
+ self.login_state=self.check_login_state()
+ self.check_refresh_login()
+
+ if (self.login_state == False and self.credentials != ''):
+ try:
+ os.remove(self.update_file)
+ except:
+ pass
+ self.check_refresh_login()
+ self.login_state=self.check_login_state()
+
+ # Collect all devices
+ if (self.login_state):
+ self.Echos = self.get_devices_by_request()
+ else:
+ self.Echos = None
+ # enable scheduler if Login should be updated automatically
+
+ if self.credentials != '':
+ self.scheduler_add('check_login', self.check_refresh_login,cycle=300)
+ #self.scheduler.add('plugins.alexarc4shng.check_login', self.check_refresh_login,cycle=300,from_smartplugin=True)
+ self.alive = True
+
+ # if you want to create child threads, do not make them daemon = True!
+ # They will not shutdown properly. (It's a python bug)
+
+ def stop(self):
+ """
+ Stop method for the plugin
+ """
+ self.logger.debug("Plugin '{}': stop method called".format(self.get_fullname()))
+ self.scheduler_remove('check_login')
+ self.alive = False
+
+ def parse_item(self, item):
+ itemFound=False
+ i=1
+
+ myValue = 'alexa_cmd_{}'.format( '%0.2d' %(i))
+ while myValue in item.conf:
+
+
+ self.logger.debug("Plugin '{}': parse item: {} Command {}".format(self.get_fullname(), item,myValue))
+
+ CmdItem_ID = item._name
+ try:
+ myCommand = item.conf[myValue].split(":")
+
+
+ if not self.shngObjects.exists(CmdItem_ID):
+ self.shngObjects.put(CmdItem_ID)
+
+ actDevice = self.shngObjects.get(CmdItem_ID)
+ actDevice.Commands.append(Cmd(myValue))
+
+ actCommand = len(actDevice.Commands)-1
+
+ actDevice.Commands[actCommand].command = item.conf[myValue]
+ myCommand = actDevice.Commands[actCommand].command.split(":")
+ self.logger.info("Plugin '{}': parse item: {}".format(self.get_fullname(), item.conf[myValue]))
+
+ actDevice.Commands[actCommand].ItemValue = myCommand[0]
+ actDevice.Commands[actCommand].EndPoint = myCommand[1]
+ actDevice.Commands[actCommand].Action = myCommand[2]
+ actDevice.Commands[actCommand].Value = myCommand[3]
+ itemFound=True
+
+ except Exception as err:
+ print("Error:" ,err)
+ i += 1
+ myValue = 'alexa_cmd_{}'.format( '%0.2d' %(i))
+
+ # todo
+ # if interesting item for sending values:
+ # return update_item
+ if itemFound == True:
+ return self.update_item
+ else:
+ return None
+
+ def parse_logic(self, logic):
+ pass
+
+ def update_item(self, item, caller=None, source=None, dest=None):
+
+ # Item was not changed but double triggered the Upate_Item-Function
+ if (self.AlexaEnableItem != ""):
+ AlexaEnabledItem = self.items.return_item(self.AlexaEnableItem)
+ if AlexaEnabledItem() != True:
+ return
+
+ if item._type == "str":
+ newValue=str(item())
+ oldValue=str(item.prev_value())
+ elif item._type =="num":
+ newValue=float(item())
+ oldValue=float(item.prev_value())
+ else:
+ newValue=str(item())
+ oldValue=str(item.prev_value())
+
+ # Nur bei Wertänderung, sonst nix wie raus hier
+ if(oldValue == newValue):
+ return
+
+
+ try:
+ myEchos = self.sh.alexarc4shng.Echos.all()
+
+ except Exception as err:
+ self.logger.debug("Error while getting Echos :",err)
+ # End Test
+
+
+
+ CmdItem_ID = item._name
+
+
+ if self.shngObjects.exists(CmdItem_ID):
+ self.logger.debug("Plugin '{}': update_item ws called with item '{}' from caller '{}', source '{}' and dest '{}'".format(self.get_fullname(), item, caller, source, dest))
+
+ actDevice = self.shngObjects.get(CmdItem_ID)
+
+ for myCommand in actDevice.Commands:
+
+ newValue2Set = myCommand.Value
+ myItemBuffer = myCommand.ItemValue
+ # Spezialfall auf bigger / smaller
+ if myCommand.ItemValue.find("<=") >=0:
+ actValue = "<="
+ myCompValue = myCommand.ItemValue.replace("<="," ")
+ myCompValue = myCompValue.replace(".",",")
+ myCompValue = float(myCompValue)
+ myCommand.ItemValue = actValue
+ if newValue > myCompValue:
+ return
+ elif myCommand.ItemValue.find(">=") >=0:
+ actValue = ">="
+ myCompValue = myCommand.ItemValue.replace(">="," ")
+ myCompValue = myCompValue.replace(".",",")
+ myCompValue = float(myCompValue)
+ myCommand.ItemValue = actValue
+ if newValue < myCompValue:
+ return
+ elif myCommand.ItemValue.find("=") >=0 :
+ actValue = "="
+ myCompValue = myCommand.ItemValue.replace("="," ")
+ myCompValue = myCompValue.replace(".",",")
+ myCompValue = float(myCompValue)
+ myCommand.ItemValue = actValue
+ if newValue != myCompValue:
+ return
+ elif myCommand.ItemValue.find("<") >=0:
+ actValue = "<"
+ myCompValue = myCommand.ItemValue.replace("<"," ")
+ myCompValue = myCompValue.replace(".",",")
+ myCompValue = float(myCompValue)
+ myCommand.ItemValue = actValue
+ if newValue >= myCompValue :
+ return
+ elif myCommand.ItemValue.find(">") >=0:
+ actValue = ">"
+ myCompValue = myCommand.ItemValue.replace(">"," ")
+ myCompValue = myCompValue.replace(".",",")
+ myCompValue = float(myCompValue)
+ myCommand.ItemValue = actValue
+ if newValue <= myCompValue:
+ return
+ else:
+ actValue = str(item())
+
+ if ("volume" in myCommand.Action.lower()):
+ httpStatus, myPlayerInfo = self.receive_info_by_request(myCommand.EndPoint,"LoadPlayerInfo","")
+ # Store Player-Infos to Device
+ if httpStatus == 200:
+ try:
+ myActEcho = self.Echos.get(myCommand.EndPoint)
+ myActEcho.playerinfo = myPlayerInfo['playerInfo']
+ actVolume = self.search(myPlayerInfo, "volume")
+ actVolume = self.search(actVolume, "volume")
+ except:
+ actVolume = 50
+ else:
+ try:
+ actVolume = int(item())
+ except:
+ actVolume = 50
+
+ if ("volumeadj" in myCommand.Action.lower()):
+ myDelta = int(myCommand.Value)
+ if actVolume+myDelta < 0:
+ newValue2Set = 0
+ elif actVolume+myDelta > 100:
+ newValue2Set = 100
+ else:
+ newValue2Set =actVolume+myDelta
+
+ # neuen Wert speichern in item
+ if ("volume" in myCommand.Action.lower()):
+ item._value = newValue2Set
+
+
+ if (actValue == str(myCommand.ItemValue) and myCommand):
+ myCommand.ItemValue = myItemBuffer
+ self.send_cmd(myCommand.EndPoint,myCommand.Action,newValue2Set)
+
+
+
+ # find Value for Key in Json-structure
+
+ def search(self,p, strsearch):
+ if type(p) is dict:
+ if strsearch in p:
+ tokenvalue = p[strsearch]
+ if not tokenvalue is None:
+ return tokenvalue
+ else:
+ for i in p:
+ tokenvalue = self.search(p[i], strsearch)
+ if not tokenvalue is None:
+ return tokenvalue
+
+ # handle Protocoll Entries
+ def _insert_protocoll_entry(self, entry):
+ if len(self.rotating_log) > 400:
+ del self.rotating_log[400:]
+ self.rotating_log.insert (0,entry)
+
+ # Check if update of login is needed
+ def check_refresh_login(self):
+ my_file= self.update_file
+ try:
+ with open (my_file, 'r') as fp:
+ for line in fp:
+ last_update_time = float(line)
+ fp.close()
+ except:
+ last_update_time = 0
+
+ mytime = time.time()
+ if (last_update_time + self.LoginUpdateCycle < mytime):
+ self.log_off()
+ self.auto_login_by_request()
+
+ # set actual values for web-interface
+ self.last_update_time = datetime.fromtimestamp(mytime).strftime('%Y-%m-%d %H:%M:%S')
+ self.next_update_time = datetime.fromtimestamp(mytime+self.LoginUpdateCycle).strftime('%Y-%m-%d %H:%M:%S')
+ self.logger.info('refreshed Login/Cookie: %s' % self.last_update_time)
+ else:
+ self.last_update_time = datetime.fromtimestamp(last_update_time).strftime('%Y-%m-%d %H:%M:%S')
+ self.next_update_time = datetime.fromtimestamp(last_update_time+self.LoginUpdateCycle).strftime('%Y-%m-%d %H:%M:%S')
+
+
+
+
+ def replace_mutated_vowel(self,mValue):
+ search = ["ä" , "ö" , "ü" , "ß" , "Ä" , "Ö", "Ü", "&" , "é", "á", "ó", "ß"]
+ replace = ["ae", "oe", "ue", "ss", "Ae", "Oe","Ue", "und", "e", "a", "o", "ss"]
+
+ counter = 0
+ myNewValue = mValue
+ try:
+ for Replacement in search:
+ myNewValue = myNewValue.replace(search[counter],replace[counter])
+ counter +=1
+ except:
+ pass
+
+ return myNewValue
+
+
+
+ ##############################################
+ # Amazon API - Calls
+ ##############################################
+
+
+ def check_login_state(self):
+ try:
+ myHeader={
+ "DNT":"1",
+ "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0",
+ "Connection":"keep-alive"
+ }
+ mySession = requests.Session()
+ mySession.cookies.update(self.cookie)
+ response= mySession.get('https://'+self.host+'/api/bootstrap?version=0',
+ headers=myHeader,allow_redirects=True)
+
+ myContent= response.content.decode()
+ myHeader = response.headers
+ myDict=json.loads(myContent)
+ mySession.close()
+
+ self.logger.info('Status of check_login_state: %d' % response.status_code)
+
+ logline = str(self.shtime.now())[0:19] +' Status of check_login_state: %d' % response.status_code
+ self._insert_protocoll_entry(logline)
+
+ myAuth =myDict['authentication']['authenticated']
+ if (myAuth == True):
+ self.logger.info('Login-State checked - Result: Logged ON' )
+ logline = str(self.shtime.now())[0:19] +' Login-State checked - Result: Logged ON'
+ self._insert_protocoll_entry(logline)
+ return True
+ else:
+ self.logger.info('Login-State checked - Result: Logged OFF' )
+ logline = str(self.shtime.now())[0:19] +' Login-State checked - Result: Logged OFF'
+ self._insert_protocoll_entry(logline)
+ return False
+
+
+
+ except Exception as err:
+ self.logger.error('Login-State checked - Result: Logged OFF - try to login again')
+ return False
+
+
+ def receive_info_by_request(self,dvName,cmdName,mValue):
+ actEcho = self.Echos.get(dvName)
+ myUrl='https://'+self.host
+ myDescriptions = ''
+ myDict = {}
+ # replace the placeholders in URL
+ myUrl=self.parse_url(myUrl,
+ mValue,
+ actEcho.serialNumber,
+ actEcho.family,
+ actEcho.deviceType,
+ actEcho.deviceOwnerCustomerId)
+
+ myDescription,myUrl,myDict = self.load_command_let(cmdName,None)
+
+ myHeader = { "Host": "alexa.amazon.de",
+ "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Connection": "keep-alive",
+ "Content-Type": "application/json; charset=UTF-8",
+ "Accept-Language": "en-US,en;q=0.5",
+ "Referer": "https://alexa.amazon.de/spa/index.html",
+ "Origin":"https://alexa.amazon.de",
+ "DNT": "1"
+ }
+ mySession = requests.Session()
+ mySession.cookies.update(self.cookie)
+ response= mySession.get(myUrl,headers=myHeader,allow_redirects=True)
+
+ myResult = response.status_code
+ myContent= response.content.decode()
+ myHeader = response.headers
+ myDict=json.loads(myContent)
+ mySession.close()
+
+
+ return myResult,myDict
+
+
+
+ def get_last_alexa(self):
+ myHeader = { "Host": "alexa.amazon.de",
+ "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Connection": "keep-alive",
+ "Content-Type": "application/json; charset=UTF-8",
+ "Accept-Language": "en-US,en;q=0.5",
+ "Referer": "https://alexa.amazon.de/spa/index.html",
+ "Origin":"https://alexa.amazon.de",
+ "DNT": "1"
+ }
+ mySession = requests.Session()
+ mySession.cookies.update(self.cookie)
+ response= mySession.get('https://'+self.host+'/api/activities?startTime=&size=10&offset=0',
+ headers=myHeader,allow_redirects=True)
+
+ myContent= response.content.decode()
+ myHeader = response.headers
+ myDict=json.loads(myContent)
+ mySession.close()
+ myDevice = myDict["activities"][0]["sourceDeviceIds"][0]["serialNumber"]
+ myLastDevice = self.Echos.get_Device_by_Serial(myDevice)
+ return myLastDevice
+
+ def send_cmd(self,dvName, cmdName,mValue,path=None):
+ # Parse the value field for dynamic content
+ if (str(mValue).find("#") >= 0 and str(mValue).find("/#") >0):
+ FirstPos = str(mValue).find("#")
+ LastPos = str(mValue).find("/#",FirstPos)
+ myItemName = str(mValue)[FirstPos+1:LastPos]
+ myItem=self.items.return_item(myItemName)
+
+ if myItem._type == "num":
+ myValue = str(myItem())
+ myValue = myValue.replace(".", ",")
+ elif myitem._type == "bool":
+ myValue = str(myItem())
+ else:
+ myValue = str(myItem())
+ mValue = mValue[0:FirstPos]+myValue+mValue[LastPos:LastPos-2]+mValue[LastPos+2:len(mValue)]
+
+ mValue = self.replace_mutated_vowel(mValue)
+
+
+ buffer = BytesIO()
+ actEcho = None
+ try:
+ actEcho = self.Echos.get(dvName)
+ except:
+ self.logger.warning('found no Echo with Name : {}'.format(dvname))
+ self._insert_protocoll_entry('found no Echo with Name : {}'.format(dvname))
+ return
+ if actEcho == None:
+ self.logger.warning('found no Echo with Name : {}'.format(dvname))
+ self._insert_protocoll_entry('found no Echo with Name : {}'.format(dvname))
+ return
+
+ myUrl='https://'+self.host
+
+ myDescriptions = ''
+ myDict = {}
+
+
+ myDescription,myUrl,myDict = self.load_command_let(cmdName,path)
+ # complete the URL
+ myUrl='https://'+self.host+myUrl
+
+ # replace the placeholders in URL
+ myUrl=self.parse_url(myUrl,
+ mValue,
+ actEcho.serialNumber,
+ actEcho.family,
+ actEcho.deviceType,
+ actEcho.deviceOwnerCustomerId)
+
+ # replace the placeholders in Payload
+ myHeaders=self.create_request_header()
+
+
+ postfields = self.parse_json(myDict,
+ mValue,
+ actEcho.serialNumber,
+ actEcho.family,
+ actEcho.deviceType,
+ actEcho.deviceOwnerCustomerId)
+
+
+ myStatus,myRespHeader, myRespCookie, myContent = self.send_post_request(myUrl,myHeaders,self.cookie,postfields)
+
+
+ myResult = myStatus
+
+
+ self.logger.info('Status of send_cmd: %d' % myResult)
+
+
+ return myResult
+
+ def get_devices_by_request(self):
+ try:
+ myHeader={
+ "DNT":"1",
+ "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0",
+ "Connection":"keep-alive"
+ }
+ mySession = requests.Session()
+ mySession.cookies.update(self.cookie)
+ response= mySession.get('https://alexa.amazon.de/api/devices-v2/device?cached=false',
+ headers=myHeader,allow_redirects=True)
+
+ myContent= response.content.decode()
+ myHeader = response.headers
+ myDict=json.loads(myContent)
+ mySession.close()
+ myDevices = EchoDevices()
+
+ self.logger.info('Status of get_devices_by_request: %d' % response.status_code)
+
+
+
+ except Exception as err:
+ self.logger.error('Error while getting Devices: %s' %err)
+ return None
+
+ for device in myDict['devices']:
+ deviceFamily=device['deviceFamily']
+ #if deviceFamily == 'WHA' or deviceFamily == 'VOX' or deviceFamily == 'FIRE_TV' or deviceFamily == 'TABLET':
+ # continue
+ try:
+ actName = device['accountName']
+ myDevices.put(Echo(actName))
+
+ actDevice = myDevices.get(actName)
+ actDevice.serialNumber=device['serialNumber']
+ actDevice.deviceType=device['deviceType']
+ actDevice.family=device['deviceFamily']
+ actDevice.name=device['accountName']
+ actDevice.deviceOwnerCustomerId=device['deviceOwnerCustomerId']
+ except Exception as err:
+ self.logger.debug('Error while getting Devices: %s' %err)
+ myDevices = None
+
+ return myDevices
+
+
+
+
+ def parse_cookie_file(self,cookiefile):
+ self.cookie = {}
+ csrf = 'N/A'
+ try:
+ with open (cookiefile, 'r') as fp:
+ for line in fp:
+ if line.find('amazon.de')<0:
+ continue
+
+ lineFields = line.strip().split('\t')
+ if len(lineFields) >= 7:
+ # add Line to self.cookie
+ if lineFields[2] == '/':
+ self.cookie[lineFields[5]]=lineFields[6]
+
+
+ if lineFields[5] == 'csrf':
+ csrf = lineFields[6]
+ fp.close()
+ except Exception as err:
+ self.logger.debug('Cookiefile could not be opened %s' % cookiefile)
+
+ return csrf
+
+
+ def parse_url(self,myDummy,mValue,serialNumber,familiy,deviceType,deviceOwnerCustomerId):
+
+ myDummy = myDummy.strip()
+ myDummy=myDummy.replace(' ','')
+ # for String
+ try:
+ myDummy=myDummy.replace('',mValue)
+ except Exception as err:
+ print("no String")
+ # for Numbers
+ try:
+ myDummy=myDummy.replace('""',mValue)
+ except Exception as err:
+ print("no Integer")
+
+ # Inject the Device informations
+ myDummy=myDummy.replace('',serialNumber)
+ myDummy=myDummy.replace('',familiy)
+ myDummy=myDummy.replace('',deviceType)
+ myDummy=myDummy.replace('',deviceOwnerCustomerId)
+
+ return myDummy
+
+
+ def parse_json(self,myDict,mValue,serialNumber,familiy,deviceType,deviceOwnerCustomerId):
+
+ myDummy = json.dumps(myDict, sort_keys=True)
+
+ count = 0
+ for char in myDummy:
+ if char == '{':
+ count = count + 1
+
+
+ if count > 1:
+ # Find First Pos for inner Object
+ FirstPos = myDummy.find("{",1)
+
+ # Find last Pos for inner Object
+ LastPos = 0
+ pos1 = 1
+ while pos1 > 0:
+ pos1 = myDummy.find("}",LastPos+1)
+ if (pos1 >= 0):
+ correctPos = LastPos
+ LastPos = pos1
+ LastPos = correctPos
+
+
+ innerJson = myDummy[FirstPos+1:LastPos]
+ innerJson = innerJson.replace('"','\\"')
+
+ myDummy = myDummy[0:FirstPos]+'"{'+innerJson+'}"'+myDummy[LastPos+1:myDummy.__len__()]
+
+
+ myDummy = myDummy.strip()
+ myDummy=myDummy.replace(' ','')
+ # for String
+ try:
+ myDummy=myDummy.replace('',mValue)
+ except Exception as err:
+ print("no String")
+ # for Numbers
+ try:
+ myDummy=myDummy.replace('""',str(mValue))
+ except Exception as err:
+ print("no Integer")
+
+ # Inject the Device informations
+ myDummy=myDummy.replace('',serialNumber)
+ myDummy=myDummy.replace('',familiy)
+ myDummy=myDummy.replace('',deviceType)
+ myDummy=myDummy.replace('',deviceOwnerCustomerId)
+
+ return myDummy
+
+
+ def create_request_header(self):
+ myheaders= {"Host": "alexa.amazon.de",
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0",
+ "Accept": "*/*",
+ "Accept-Encoding": "deflate, gzip",
+ "DNT": "1",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Accept-Language": "de,nl-BE;q=0.8,en-US;q=0.5,en;q=0.3",
+ "Referer": "https://alexa.amazon.de/spa/index.html",
+ "Origin": "https://alexa.amazon.de",
+ "csrf": self.csrf,
+ "Cache-Control": "no-cache"
+ }
+ return myheaders
+
+ def load_command_let(self,cmdName,path=None):
+ myDescription = ''
+ myUrl = ''
+ myJson = ''
+ retJson = {}
+
+ if path==None:
+ path=self.sh.get_basedir()+"/plugins/alexarc4shng/cmd/"
+
+ try:
+ file=open(path+cmdName+'.cmd','r')
+ for line in file:
+ line=line.replace("\r\n","")
+ line=line.replace("\n","")
+ myFields=line.split("|")
+ if (myFields[0]=="apiurl"):
+ myUrl=myFields[1]
+ pass
+ if (myFields[0]=="description"):
+ myDescription=myFields[1]
+ pass
+ if (myFields[0]=="json"):
+ myJson=myFields[1]
+ retJson=json.loads(myJson)
+ pass
+ file.close()
+ except:
+ self.logger.error("Error while loading Commandlet : {}".format(cmdName))
+ return myDescription,myUrl,retJson
+
+
+
+ def load_cmd_list(self):
+ retValue=[]
+
+ files = os.listdir(self.sh.get_basedir()+'/plugins/alexarc4shng/cmd/')
+ for line in files:
+ try:
+ line=line.split(".")
+ if line[1] == "cmd":
+ newCmd = {'Name':line[0]}
+ retValue.append(newCmd)
+ except:
+ pass
+
+ return json.dumps(retValue)
+
+ def check_json(self,payload):
+ try:
+ myDump = json.loads(payload)
+ return 'Json OK'
+ except Exception as err:
+ return 'Json - Not OK - '+ err.args[0]
+
+ def delete_cmd_let(self,name):
+ result = ""
+ try:
+ os.remove(self.sh.get_basedir()+"/plugins/alexarc4shng/cmd/"+name+'.cmd')
+ result = "Status:OK\n"
+ result += "value1:File deleted\n"
+ except Exception as err:
+ result = "Status:failure\n"
+ result += "value1:Error - "+err.args[1]+"\n"
+
+ ##################
+ # prepare Response
+ ##################
+ myResult = result.splitlines()
+ myResponse=[]
+ newEntry=dict()
+ for line in myResult:
+ myFields=line.split(":")
+ newEntry[myFields[0]] = myFields[1]
+
+ myResponse.append(newEntry)
+ ##################
+ return json.dumps(myResponse,sort_keys=True)
+
+ def test_cmd_let(self,selectedDevice,txtValue,txtDescription,txt_payload,txtApiUrl):
+ result = ""
+ if (txtApiUrl[0:1] != "/"):
+ txtApiUrl = "/"+txtApiUrl
+
+ JsonResult = self.check_json(txt_payload)
+ if (JsonResult != 'Json OK'):
+ result = "Status:failure\n"
+ result += "value1:"+JsonResult+"\n"
+ else:
+ try:
+ self.save_cmd_let("test", txtDescription, txt_payload, txtApiUrl, "/tmp/")
+ retVal = self.send_cmd(selectedDevice,"test",txtValue,"/tmp/")
+ result = "Status:OK\n"
+ result += "value1: HTTP "+str(retVal)+"\n"
+ except Exception as err:
+ result = "Status:failure\n"
+ result += "value1:"+err.args[0]+"\n"
+
+ ##################
+ # prepare Response
+ ##################
+ myResult = result.splitlines()
+ myResponse=[]
+ newEntry=dict()
+ for line in myResult:
+ myFields=line.split(":")
+ newEntry[myFields[0]] = myFields[1]
+
+ myResponse.append(newEntry)
+ ##################
+ return json.dumps(myResponse,sort_keys=True)
+
+ def load_cmd_2_webIf(self,txtCmdName):
+ try:
+ myDescription,myUrl,myDict = self.load_command_let(txtCmdName,None)
+ result = "Status|OK\n"
+ result += "Description|"+myDescription+"\n"
+ result += "myUrl|"+myUrl+"\n"
+ result += "payload|"+str(myDict)+"\n"
+ except Exception as err:
+ result = "Status|failure\n"
+ result += "value1|"+err.args[0]+"\n"
+ ##################
+ # prepare Response
+ ##################
+ myResult = result.splitlines()
+ myResponse=[]
+ newEntry=dict()
+ for line in myResult:
+ myFields=line.split("|")
+ newEntry[myFields[0]] = myFields[1]
+
+ myResponse.append(newEntry)
+ ##################
+ return json.dumps(myResponse,sort_keys=True)
+
+
+ def save_cmd_let(self,name,description,payload,ApiURL,path=None):
+ if path==None:
+ path=self.sh.get_basedir()+"/plugins/alexarc4shng/cmd/"
+
+ result = ""
+ mydummy = ApiURL[0:1]
+ if (ApiURL[0:1] != "/"):
+ ApiURL = "/"+ApiURL
+
+ JsonResult = self.check_json(payload)
+ if (JsonResult != 'Json OK'):
+ result = "Status:failure\n"
+ result += "value1:"+JsonResult+"\n"
+
+ else:
+ try:
+ myDict = json.loads(payload)
+ myDump = json.dumps(myDict)
+ description=description.replace("\r"," ")
+ description=description.replace("\n"," ")
+ file=open(path+name+".cmd","w")
+ file.write("apiurl|"+ApiURL+"\r\n")
+ file.write("description|"+description+"\r\n")
+ file.write("json|"+myDump+"\r\n")
+ file.close
+
+ result = "Status:OK\n"
+ result += "value1:"+JsonResult + "\n"
+ result += "value2:Saved Commandlet\n"
+ except Exception as err:
+ print (err)
+
+ ##################
+ # prepare Response
+ ##################
+ myResult = result.splitlines()
+ myResponse=[]
+ newEntry=dict()
+ for line in myResult:
+ myFields=line.split(":")
+ newEntry[myFields[0]] = myFields[1]
+
+ myResponse.append(newEntry)
+ ##################
+ return json.dumps(myResponse,sort_keys=True)
+
+ def send_get_request(self,url="", myHeader="",Cookie=""):
+ mySession = requests.Session()
+ mySession.cookies.update(Cookie)
+ response=mySession.get(url,
+ headers=myHeader,
+ allow_redirects=True)
+ return response.status_code, response.headers, response.cookies, response.content.decode(),response.url
+
+ def send_post_request(self,url="", myHeader="",Cookie="",postdata=""):
+ mySession = requests.Session()
+ mySession.cookies.update(Cookie)
+ response=mySession.post(url,
+ headers=myHeader,
+ data=postdata,
+ allow_redirects=True)
+ mySession.close()
+ return response.status_code, response.headers, mySession.cookies, response.content.decode()
+
+ def parse_response_cookie_2_txt(self, cookie, CollectingTxtCookie):
+ for c in cookie:
+ if c.domain != '':
+ CollectingTxtCookie += c.domain+"\t"+str(c.domain_specified)+"\t"+ c.path+"\t"+ str(c.secure)+"\t"+ str(c.expires)+"\t"+ c.name+"\t"+ c.value+"\r\n"
+ return CollectingTxtCookie
+
+ def parse_response_cookie(self, cookie, CollectingCookie):
+ for c in cookie:
+ CollectingCookie[c.name] = c.value
+ return CollectingCookie
+
+ def collect_postdata(self,content):
+ content = str(content.replace('hidden', '\r\nhidden'))
+ postdata = {}
+ myFile = content.splitlines()
+ for myLine in myFile:
+ if 'hidden' in myLine:
+ data = re.findall(r'hidden.*name="([^"]+).*value="([^"]+).*/',myLine)
+ if len(data) >0:
+ postdata[data[0][0]]= data[0][1]
+
+
+ postdata['showPasswordChecked'] = 'false'
+ return postdata
+
+
+ def auto_login_by_request(self):
+ if self.credentials == '':
+ return False
+ if self.credentials != '':
+ dummy = self.credentials.split(":")
+ user = dummy[0]
+ pwd = dummy[1]
+ myResults= []
+ myCollectionTxtCookie = ""
+ myCollectionCookie = {}
+ ####################################################
+ # Start Step 1 - get Page without Post-Fields
+ ####################################################
+ myHeaders={
+ "Accept-Language":"de,en-US;q=0.7,en;q=0.3",
+ "DNT" : "1",
+ "Upgrade-Insecure-Requests" : "1",
+ "Connection":"keep-alive",
+ "Content-Type" : "text/plain;charset=UTF-8",
+ "User-Agent" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Connection" : "keep-alive",
+ "Accept-Encoding" : "gzip, deflate, br"
+ }
+ myStatus,myRespHeader, myRespCookie, myContent,myLocation = self.send_get_request('https://'+self.host,myHeaders)
+ myCollectionTxtCookie = self.parse_response_cookie_2_txt(myRespCookie,myCollectionTxtCookie)
+ myCollectionCookie = self.parse_response_cookie(myRespCookie,myCollectionCookie)
+ PostData = self.collect_postdata(myContent)
+
+ actSessionID = myRespCookie['session-id']
+
+ self.logger.info('Status of Auto-Login First Step: %d' % myStatus)
+ myResults.append('HTTP : ' + str(myStatus)+'- Step 1 - get Session-ID')
+ ####################################################
+ # Start Step 2 - login with form
+ ####################################################
+ myHeaders={
+ "User-Agent" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Accept-Language":"de,en-US;q=0.7,en;q=0.3",
+ "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+ "DNT" : "1",
+ "Upgrade-Insecure-Requests":"1",
+ "Connection":"keep-alive",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Accept-Encoding" : "gzip, deflate, br",
+ "Referer": myLocation
+ }
+ newUrl = "https://www.amazon.de"+"/ap/signin/"+actSessionID
+ postfields = urllib3.request.urlencode(PostData)
+
+ myStatus,myRespHeader, myRespCookie, myContent = self.send_post_request(newUrl,myHeaders,myCollectionCookie,PostData)
+ myCollectionTxtCookie = self.parse_response_cookie_2_txt(myRespCookie,myCollectionTxtCookie)
+ myCollectionCookie = self.parse_response_cookie(myRespCookie,myCollectionCookie)
+ PostData = self.collect_postdata(myContent)
+
+ #actSessionID = myRespCookie['session-id']
+
+ self.logger.info('Status of Auto-Login Second Step: %d' % myStatus)
+ myResults.append('HTTP : ' + str(myStatus)+'- Step 2 - login blank to get referer')
+
+ ####################################################
+ # Start Step 3 - login with form
+ ####################################################
+ myHeaders ={
+ "User-Agent" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Accept-Language" :"de,en-US;q=0.7,en;q=0.3",
+ "Accept" : "*/*",
+ "DNT" : "1",
+ "Accept-Encoding" : "gzip, deflate, br",
+ "Connection":"keep-alive",
+ "Upgrade-Insecure-Requests":"1",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Host":"www.amazon.de",
+ "Referer":"https://www.amazon.de/ap/signin/" + actSessionID
+ }
+
+ newUrl = "https://www.amazon.de/ap/signin"
+
+ PostData['email'] =user
+ PostData['password'] = pwd
+
+ postfields = urllib3.request.urlencode(PostData)
+ myStatus,myRespHeader, myRespCookie, myContent = self.send_post_request(newUrl,myHeaders,myCollectionCookie,PostData)
+ myCollectionTxtCookie = self.parse_response_cookie_2_txt(myRespCookie,myCollectionTxtCookie)
+ myCollectionCookie = self.parse_response_cookie(myRespCookie,myCollectionCookie)
+ PostData = self.collect_postdata(myContent)
+
+ self.logger.info('Status of Auto-Login third Step: %d' % myStatus)
+ myResults.append('HTTP : ' + str(myStatus)+'- Step 3 - login with credentials')
+ file=open("/tmp/alexa_step2.html","w")
+ file.write(myContent)
+ file.close
+
+ #################################################################
+ ## done - third Step - logged in now go an get the goal (csrf)
+ #################################################################
+
+ myHeaders ={
+ "User-Agent" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0",
+ "Accept-Language" : "de,en-US;q=0.7,en;q=0.3",
+ "DNT" : "1",
+ "Connection" : "keep-alive",
+ "Accept-Encoding" : "gzip, deflate",
+ "Referer" : "https://"+self.host+ "/spa/index.html",
+ "Origin":"https://"+self.host
+ }
+ Url = 'https://'+self.host+'/templates/oobe/d-device-pick.handlebars'
+ #Url = 'https://'+self.host+'/api/language'
+
+ myStatus,myRespHeader, myRespCookie, myContent,myLocation = self.send_get_request(Url,myHeaders,myCollectionCookie)
+ myCollectionTxtCookie = self.parse_response_cookie_2_txt(myRespCookie,myCollectionTxtCookie)
+ myCollectionCookie = self.parse_response_cookie(myRespCookie,myCollectionCookie)
+
+ myResults.append('HTTP : ' + str(myStatus)+'- Step 4 - get csrf')
+ self.logger.info('Status of Auto-Login fourth Step: %d' % myStatus)
+
+ ####################################################
+ # check the csrf
+ ####################################################
+ myCsrf = self.search(myCollectionCookie, "csrf")
+ if myCsrf != None:
+ myResults.append('check CSRF- Step 5 - got good csrf')
+ self.logger.info('Status of Auto-Login fifth Step - got CSRF: %s' % myCsrf)
+ self.csrf = myCsrf
+ else:
+ myResults.append('check CSRF- Step 5 - got no CSRF')
+ self.logger.info('Status of Auto-Login fifth Step - got no CSRF')
+
+ ####################################################
+ # store the new Cookie-File
+ ####################################################
+ try:
+ with open (self.cookiefile, 'w') as myFile:
+
+
+ myFile.write("# AlexaRc4shNG HTTP Cookie File"+"\r\n")
+ myFile.write("# https://www.smarthomeng.de/user/"+"\r\n")
+ myFile.write("# This file was generated by alexarc4shng@smarthomeNG! Edit at your own risk."+"\r\n")
+ myFile.write("\r\n")
+ for line in myCollectionTxtCookie.splitlines():
+ myFile.write(line+"\r\n")
+ myFile.close()
+
+ myResults.append('cookieFile- Step 6 - creation done')
+ self.cookie = myCollectionCookie
+ self.login_state= self.check_login_state()
+ mytime = time.time()
+ file=open(self.update_file,"w")
+ file.write(str(mytime)+"\r\n")
+ file.close()
+
+ myResults.append('login state : %s' % self.login_state)
+ except:
+ myResults.append('cookieFile- Step 6 - error while writing new cookie-File')
+
+ for entry in myResults:
+ logline = str(self.shtime.now())[0:19] + ' ' + entry
+ self._insert_protocoll_entry(logline)
+ return myResults
+
+
+
+
+
+ def log_off(self):
+ myUrl='https://'+self.host+"/logout"
+ myHeaders={"DNT" :"1",
+ "Connection":"keep-alive",
+ "User-Agent":"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0"
+ }
+
+ myStatus,myRespHeader, myRespCookie, myContent,myLocation = self.send_get_request(myUrl,myHeaders, self.cookie)
+
+ self.logger.info('Status of log_off: {}'.format(myStatus))
+
+ if myStatus == 200:
+ logline = str(self.shtime.now())[0:19] +' successfully logged off'
+ self._insert_protocoll_entry(logline)
+ return "HTTP - " + str(myStatus)+" successfully logged off"
+ else:
+ logline = str(self.shtime.now())[0:19] +' Error while logging off'
+ return "HTTP - " + str(myStatus)+" Error while logging off"
+
+
+
+ ##############################################
+ # Web-Interface
+ ##############################################
+
+ def init_webinterface(self):
+ """"
+ Initialize the web interface for this plugin
+
+ This method is only needed if the plugin is implementing a web interface
+ """
+ try:
+ self.mod_http = Modules.get_instance().get_module(
+ 'http') # try/except to handle running in a core version that does not support modules
+ except:
+ self.mod_http = None
+ if self.mod_http == None:
+ self.logger.error("Plugin '{}': Not initializing the web interface".format(self.get_shortname()))
+ return False
+
+ # set application configuration for cherrypy
+ webif_dir = self.path_join(self.get_plugin_dir(), 'webif')
+ config = {
+ '/': {
+ 'tools.staticdir.root': webif_dir,
+ },
+ '/static': {
+ 'tools.staticdir.on': True,
+ 'tools.staticdir.dir': 'static'
+ }
+ }
+
+ # Register the web interface as a cherrypy app
+ self.mod_http.register_webif(WebInterface(webif_dir, self),
+ self.get_shortname(),
+ config,
+ self.get_classname(), self.get_instance_name(),
+ description='')
+
+ return True
+
+
+
+
+
+# ------------------------------------------
+# Webinterface of the plugin
+# ------------------------------------------
+
+import cherrypy
+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 = logging.getLogger(__name__)
+ self.webif_dir = webif_dir
+ self.plugin = plugin
+ self.tplenv = self.init_template_environment()
+
+
+ def render_template(self, tmpl_name, **kwargs):
+ """
+
+ Render a template and add vars needed gobally (for navigation, etc.)
+
+ :param tmpl_name: Name of the template file to be rendered
+ :param **kwargs: keyworded arguments to use while rendering
+
+ :return: contents of the template after beeing rendered
+
+ """
+ tmpl = self.tplenv.get_template(tmpl_name)
+ return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(),
+ plugin_info=self.plugin.get_info(), p=self.plugin,
+ **kwargs)
+
+ def set_cookie_pic(self,CookieOK=False):
+ dstFile = self.plugin.sh.get_basedir()+'/plugins/alexarc4shng/webif/static/img/plugin_logo.png'
+ srcGood = self.plugin.sh.get_basedir()+'/plugins/alexarc4shng/webif/static/img/alexa_cookie_good.png'
+ srcBad = self.plugin.sh.get_basedir()+'/plugins/alexarc4shng/webif/static/img/alexa_cookie_bad.png'
+ if os.path.isfile(dstFile):
+ os.remove(dstFile)
+ if CookieOK==True:
+ if os.path.isfile(srcGood):
+ os.popen('cp '+srcGood + ' ' + dstFile)
+ else:
+ if os.path.isfile(srcBad):
+ os.popen('cp '+srcBad + ' ' + dstFile)
+
+ @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
+ """
+
+ if (self.plugin.login_state != 'N/A'):
+ self.set_cookie_pic(True)
+ else:
+ self.set_cookie_pic(False)
+
+ log_file = ''
+ for line in self.plugin.rotating_log:
+ log_file += str(line)+'\n'
+
+ myDevices = self.get_device_list()
+ alexa_device_count = len(myDevices)
+
+ login_info = self.plugin.last_update_time + '('+ self.plugin.next_update_time + ')'
+ return self.render_template('index.html',device_list=myDevices,csrf_cookie=self.plugin.csrf,alexa_device_count=alexa_device_count,time_auto_login=login_info, log_file=log_file)
+
+
+ @cherrypy.expose
+ def log_off_html(self,txt_Result=None):
+ txt_Result=self.plugin.log_off()
+ return json.dumps(txt_Result)
+
+ @cherrypy.expose
+ def log_in_html(self,txt_Result=None):
+ txt_Result=self.plugin.auto_login_by_request()
+ return json.dumps(txt_Result)
+
+
+ @cherrypy.expose
+ def handle_buttons_html(self,txtValue=None, selectedDevice=None,txtButton=None,txt_payload=None,txtCmdName=None,txtApiUrl=None,txtDescription=None):
+ if txtButton=="BtnSave":
+ result = self.plugin.save_cmd_let(txtCmdName,txtDescription,txt_payload,txtApiUrl)
+ elif txtButton =="BtnCheck":
+ pass
+ elif txtButton =="BtnLoad":
+ result = self.plugin.load_cmd_2_webIf(txtCmdName)
+ pass
+ elif txtButton =="BtnTest":
+ result = self.plugin.test_cmd_let(selectedDevice,txtValue,txtDescription,txt_payload,txtApiUrl)
+ elif txtButton =="BtnDelete":
+ result = self.plugin.delete_cmd_let(txtCmdName)
+ else:
+ pass
+
+ #return self.render_template("index.html",txtresult=result)
+ return result
+
+
+ @cherrypy.expose
+ def build_cmd_list_html(self,reload=None):
+ myCommands = self.plugin.load_cmd_list()
+ return myCommands
+
+
+ def get_device_list(self):
+ if (self.plugin.login_state == True):
+ self.plugin.Echos = self.plugin.get_devices_by_request()
+
+ Device_items = []
+ try:
+ myDevices = self.plugin.Echos.devices
+ for actDevice in myDevices:
+ newEntry=dict()
+ Echo2Add=self.plugin.Echos.devices.get(actDevice)
+ newEntry['name'] = Echo2Add.id
+ newEntry['serialNumber'] = Echo2Add.serialNumber
+ newEntry['family'] = Echo2Add.family
+ newEntry['deviceType'] = Echo2Add.deviceType
+ newEntry['deviceOwnerCustomerId'] = Echo2Add.deviceOwnerCustomerId
+ Device_items.append(newEntry)
+
+ except Exception as err:
+ self.logger.debug('No devices found',err)
+
+ return Device_items
+
+ @cherrypy.expose
+ def store_credentials_html(self, encoded='', pwd = '', user= '', store_2_config=None):
+ txt_Result = []
+ myCredentials = user+':'+pwd
+ byte_credentials = base64.b64encode(myCredentials.encode('utf-8'))
+ encoded = byte_credentials.decode("utf-8")
+ txt_Result.append("encoded:"+encoded)
+ txt_Result.append("Encoding done")
+ conf_file=self.plugin.sh.get_basedir()+'/etc/plugin.yaml'
+ if (store_2_config == 'true'):
+ new_conf = ""
+ with open (conf_file, 'r') as myFile:
+ for line in myFile:
+ if line.find('alexa_credentials') > 0:
+ line = ' alexa_credentials: '+encoded+ "\r\n"
+ new_conf += line
+ myFile.close()
+ txt_Result.append("replaced credentials in temporary file")
+ with open (conf_file, 'w') as myFile:
+ for line in new_conf.splitlines():
+ myFile.write(line+'\r\n')
+ myFile.close()
+ txt_Result.append("stored new config to filesystem")
+ return json.dumps(txt_Result)
+
+ @cherrypy.expose
+ def storecookie_html(self, save=None, cookie_txt=None, txt_Result=None, txtUser=None, txtPwd=None, txtEncoded=None, store_2_config=None):
+ myLines = cookie_txt.splitlines()
+ #
+ # Problem - different Handling of Cookies by Browser
+
+ file=open("/tmp/cookie.txt","w")
+ for line in myLines:
+ file.write(line+"\r\n")
+ file.close()
+ value1 = self.plugin.parse_cookie_file("/tmp/cookie.txt")
+ self.plugin.login_state = self.plugin.check_login_state()
+
+ if (self.plugin.login_state == True):
+ self.set_cookie_pic(True)
+ else:
+ self.set_cookie_pic(False)
+
+
+ if (self.plugin.login_state == False) :
+ # Cookies not found give back an error
+ tmpl = self.tplenv.get_template('index.html')
+ return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(),
+ plugin_info=self.plugin.get_info(), p=self.plugin,
+ txt_Result=' Cookies are not saved missing csrf',
+ cookie_txt=cookie_txt,
+ csrf_cookie=value1)
+
+ # Store the Cookie-file for permanent use
+ file=open(self.plugin.cookiefile,"w")
+ for line in myLines:
+ file.write(line+"\r\n")
+ file.close()
+
+ self.plugin.csrf = value1
+
+
+ myDevices = self.get_device_list()
+ alexa_device_count = len(myDevices)
+
+
+ tmpl = self.tplenv.get_template('index.html')
+ return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(),
+ plugin_info=self.plugin.get_info(), p=self.plugin,
+ txt_Result=' Cookies were saved - everything OK',
+ cookie_txt=cookie_txt,
+ csrf_cookie=value1,
+ device_list=myDevices,
+ alexa_device_count=alexa_device_count)
+
+
+
+
diff --git a/alexarc4shng/assets/pic1.jpg b/alexarc4shng/assets/pic1.jpg
new file mode 100755
index 000000000..ad22e5d2a
Binary files /dev/null and b/alexarc4shng/assets/pic1.jpg differ
diff --git a/alexarc4shng/assets/pic2.jpg b/alexarc4shng/assets/pic2.jpg
new file mode 100755
index 000000000..7ebc36e76
Binary files /dev/null and b/alexarc4shng/assets/pic2.jpg differ
diff --git a/alexarc4shng/assets/pic3.jpg b/alexarc4shng/assets/pic3.jpg
new file mode 100755
index 000000000..dfc7a365d
Binary files /dev/null and b/alexarc4shng/assets/pic3.jpg differ
diff --git a/alexarc4shng/assets/webif1.jpg b/alexarc4shng/assets/webif1.jpg
new file mode 100755
index 000000000..6d9c14224
Binary files /dev/null and b/alexarc4shng/assets/webif1.jpg differ
diff --git a/alexarc4shng/assets/webif2.jpg b/alexarc4shng/assets/webif2.jpg
new file mode 100755
index 000000000..f82816d5a
Binary files /dev/null and b/alexarc4shng/assets/webif2.jpg differ
diff --git a/alexarc4shng/assets/webif3.jpg b/alexarc4shng/assets/webif3.jpg
new file mode 100755
index 000000000..5e01d62a1
Binary files /dev/null and b/alexarc4shng/assets/webif3.jpg differ
diff --git a/alexarc4shng/cmd/LastAlexa.cmd b/alexarc4shng/cmd/LastAlexa.cmd
new file mode 100755
index 000000000..5aee38405
--- /dev/null
+++ b/alexarc4shng/cmd/LastAlexa.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/activities?startTime=&size=10&offset=0
+description|Get the last Alexa
+json|{}
diff --git a/alexarc4shng/cmd/LoadPlayerInfo.cmd b/alexarc4shng/cmd/LoadPlayerInfo.cmd
new file mode 100755
index 000000000..b18aa5090
--- /dev/null
+++ b/alexarc4shng/cmd/LoadPlayerInfo.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/np/player?deviceSerialNumber=&deviceType=
+description|Load Player informations into dict
+json|{}
diff --git a/alexarc4shng/cmd/Pause.cmd b/alexarc4shng/cmd/Pause.cmd
new file mode 100755
index 000000000..b440d4650
--- /dev/null
+++ b/alexarc4shng/cmd/Pause.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/np/command?deviceSerialNumber=&deviceType=
+description|pauses the device - silence
+json|{"type": "PauseCommand"}
diff --git a/alexarc4shng/cmd/Play.cmd b/alexarc4shng/cmd/Play.cmd
new file mode 100755
index 000000000..08732dc33
--- /dev/null
+++ b/alexarc4shng/cmd/Play.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/np/command?deviceSerialNumber=&deviceType=
+description|Play the actual paused media
+json|{"type": "PlayCommand"}
diff --git a/alexarc4shng/cmd/SSML.cmd b/alexarc4shng/cmd/SSML.cmd
new file mode 100755
index 000000000..483fdbc50
--- /dev/null
+++ b/alexarc4shng/cmd/SSML.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/behaviors/preview
+description|Use SSML to speak-Example:
+json|{"behaviorId": "PREVIEW", "sequenceJson": {"@type": "com.amazon.alexa.behaviors.model.Sequence", "startNode": {"operationPayload": {"customerId": "", "content": [{"display": {"title": "smartHomeNG", "body": ""}, "speak": {"type": "ssml", "value": ""}, "locale": "de-DE"}], "expireAfter": "PT5S", "target": {"customerId": "", "devices": [{"deviceSerialNumber": "", "deviceTypeId": ""}]}}, "type": "AlexaAnnouncement", "@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode"}}, "status": "ENABLED"}
diff --git a/alexarc4shng/cmd/StartTuneInStation.cmd b/alexarc4shng/cmd/StartTuneInStation.cmd
new file mode 100755
index 000000000..8a87e97d6
--- /dev/null
+++ b/alexarc4shng/cmd/StartTuneInStation.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/tunein/queue-and-play?deviceSerialNumber=&deviceType=&guideId=&contentType=station&callSign=&mediaOwnerCustomerId=
+description|Startet einen TuneIn Radio-Kanel
+json|{}
diff --git a/alexarc4shng/cmd/Text2Speech.cmd b/alexarc4shng/cmd/Text2Speech.cmd
new file mode 100755
index 000000000..c31f92904
--- /dev/null
+++ b/alexarc4shng/cmd/Text2Speech.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/behaviors/preview
+description|Text to speach
+json|{"status": "ENABLED", "sequenceJson": {"@type": "com.amazon.alexa.behaviors.model.Sequence", "startNode": {"@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode", "type": "Alexa.Speak", "operationPayload": {"textToSpeak": "", "locale": "de-DE", "customerId": "", "deviceSerialNumber": "", "deviceType": ""}}}, "behaviorId": "PREVIEW"}
diff --git a/alexarc4shng/cmd/VolumeAdj.cmd b/alexarc4shng/cmd/VolumeAdj.cmd
new file mode 100755
index 000000000..11e105be6
--- /dev/null
+++ b/alexarc4shng/cmd/VolumeAdj.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/np/command?deviceSerialNumber=&deviceType=
+description|The volume will be set +xx / -xx percent from the actual volume
+json|{"type": "VolumeLevelCommand", "volumeLevel": ""}
diff --git a/alexarc4shng/cmd/VolumeSet.cmd b/alexarc4shng/cmd/VolumeSet.cmd
new file mode 100755
index 000000000..5f86caa00
--- /dev/null
+++ b/alexarc4shng/cmd/VolumeSet.cmd
@@ -0,0 +1,3 @@
+apiurl|/api/np/command?deviceSerialNumber=&deviceType=
+description|Sets the volume to a value from 0-100 percent
+json|{"type": "VolumeLevelCommand", "volumeLevel": ""}
diff --git a/alexarc4shng/locale.yaml b/alexarc4shng/locale.yaml
new file mode 100755
index 000000000..712460ac6
--- /dev/null
+++ b/alexarc4shng/locale.yaml
@@ -0,0 +1,48 @@
+plugin_translations:
+ # Translations for the plugin specially for the web interface
+ 'allowed IP': {'de': 'erlaubte IP', 'en': '=', 'fr': ''}
+ 'last Session': {'de': 'letzte Sitzung', 'en': '=', 'fr': ''}
+ 'Stream-Modifiers': {'de': 'Stream-Modikatoren', 'en': '=', 'fr': ''}
+ 'last Session duration': {'de': 'letzte Sitzungs- dauer', 'en': '=', 'fr': ''}
+ 'Sessions total': {'de': 'Sitzungen gesamt', 'en': '=', 'fr': ''}
+ 'Settings': {'de': 'Einstellungen', 'en': '=', 'fr': ''}
+ 'Credentials:': {'de': 'Zugangsdaten:', 'en': '=', 'fr': ''}
+ 'delete Protocol': {'de': 'Protokoll löschen:', 'en': '=', 'fr': ''}
+ 'Real-URL': {'de': 'tatsächliche URL', 'en': '=', 'fr': ''}
+ 'Commit Changes': {'de': 'Änderungen speichern', 'en': '=', 'fr': ''}
+ 'Store to Config': {'de': 'in Konfiguration speichern', 'en': '=', 'fr': ''}
+ 'Settings / Cam-Info': {'de': 'Einstellungen / Kamera-Infos', 'en': '=', 'fr': ''}
+ 'Communication-Log': {'de': 'Kommunikations-Log', 'en': '=', 'fr': ''}
+ 'active Camera Threads': {'de': 'aktive Kamera-Threads', 'en': '=', 'fr': ''}
+ 'SSL Certificate Info': {'de': 'SSL Zertifikas Info', 'en': '=', 'fr': ''}
+ 'Proxy-Credentials': {'de': 'Proxy-Zugangsdaten', 'en': '=', 'fr': ''}
+ 'Proxy-Authorization': {'de': 'Proxy-Authorisierungs-Typ', 'en': '=', 'fr': ''}
+ 'Video-Buffer-Size :': {'de': 'Video-Puffer-Grösse', 'en': '=', 'fr': ''}
+ 'Authorization :': {'de': 'Authorisierungs-Typ', 'en': '=', 'fr': ''}
+ 'Encode': {'de': 'enkodieren', 'en': '=', 'fr': ''}
+ 'encoded Cred.:': {'de': 'enkodierte Zugangsdaten', 'en': '=', 'fr': ''}
+ 'Result :': {'de': 'Ergebnis', 'en': '=', 'fr': ''}
+ 'Value': {'de': 'Wert', 'en': '=', 'fr': ''}
+ 'Property': {'de': 'Eigenschaft', 'en': '=', 'fr': ''}
+ 'Threads existing ...': {'de': 'existierende Threads', 'en': '=', 'fr': ''}
+ 'Auto Update ( 2 sec.)': {'de': 'Auto Update ( 2 Sek.)', 'en': '=', 'fr': ''}
+ 'last/next Auto-Login' : {'de': 'letztes/nächstes Auto-Login', 'en': '=', 'fr': ''}
+ 'selected Device' : {'de': 'gwähltes Gerät', 'en': '=', 'fr': ''}
+ 'No. of Alexa-Devices': {'de': 'Anzahl Alexa-Geräte', 'en': '=', 'fr': ''}
+ 'LogOff': {'de': 'Ausloggen', 'en': '=', 'fr': ''}
+ 'LogIn': {'de': 'Einloggen', 'en': '=', 'fr': ''}
+ 'Store Cookie': {'de': 'Cookie speichern', 'en': '=', 'fr': ''}
+ 'Paste the Cookie-File here': {'de': 'Cookie File hier einfügen', 'en': '=', 'fr': ''}
+ 'existing Commands': {'de': 'existierende Kommandos', 'en': '=', 'fr': ''}
+ 'Command-Name': {'de': 'Kommando-Name', 'en': '=', 'fr': ''}
+
+
+# '': {'de': 'Proxy-Authorisierungs-Typ', 'en': '=', 'fr': ''}
+
+
+
+
+
+
+
+
diff --git a/alexarc4shng/plugin.yaml b/alexarc4shng/plugin.yaml
new file mode 100755
index 000000000..984f597e3
--- /dev/null
+++ b/alexarc4shng/plugin.yaml
@@ -0,0 +1,97 @@
+# Metadata for the plugin
+plugin:
+ # Global plugin attributes
+ type: interface # plugin type (gateway, interface, protocol, system, web)
+ description:
+ de: 'Plugin zur Steuerung von Amazon Echo Geräten Zugriff via Web-Browser API und Cookie'
+ en: 'Plugin to remote control Echo Show/Spot/Fire'
+ maintainer: AndreK
+ tester: henfri, juergen, psilo
+ documentation: https://www.smarthomeng.de/user/plugins/alexarc4shng/user_doc.html # url of documentation
+ version: 1.0.2 # Plugin version
+ sh_minversion: 1.5.2 # minimum shNG version to use this plugin
+ multi_instance: False # plugin supports multi instance
+ classname: AlexaRc4shNG # class containing the plugin
+ keywords: Alexa Amazon Remote Control
+ support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1336416-alexa-text-to-speach
+ state: develop # State of the Plugin
+ restartable: True # Plugin is restartable
+
+plugin_functions:
+ # Definition of function interface of the plugin
+
+ send_cmd:
+ type: str
+ description:
+ de: "Sendet einen Befehl an Alexa."
+ en: "Sends a command to Alexa."
+ parameters:
+ dvName:
+ type: str
+ description:
+ de: "Name des Alexa Devices."
+ en: "Name of Alexa device."
+ cmdName:
+ type: str
+ description:
+ de: "Name des Befehls, z.b. Text2Speech."
+ en: "Name of command, e.g. Text2Speech."
+ mValue:
+ type: str
+ description:
+ de: "Wert, der gesendet werden soll, numerische Werte ohne Hochkomma als Zahl"
+ en: "Value to send, numeric Values without Quotes as Num"
+
+ 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"
+
+logic_parameters: NONE # No logic parameters for this plugin
+item_structs: NONE # no item structure needed
+item_attributes:
+ # Definition of item attributes defined by this plugin
+ alexa_cmd_XX:
+ type: str
+ mandatory: True
+ description:
+ de: 'String um die Befehle zu definieren'
+ en: 'string to define orders'
+
+parameters:
+ # Definition of parameters to be configured in etc/plugin.yaml
+ cookiefile:
+ type: str
+ default: ''
+ description:
+ de: 'Cookiefile mit komplettem Pfad'
+ en: 'Cookiefile with complete path'
+ host:
+ type: str
+ default: 'alexa.amazon.de'
+ description:
+ de: 'Amazon-Host z.b. alexa.amazon.de ohne Protokoll (https)'
+ en: 'Amazon-Host a.e. alexa.amazon.de without protocoll (https)'
+
+ item_2_enable_alexa_rc:
+ 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)'
+
+ alexa_credentials:
+ type: str
+ default: ''
+ description:
+ de: 'Zugangsdaten für das Amazon-Alexa-Web-Site :, base64 encodiert'
+ en: 'credentials for the amazon-alexa-website :, base64 encoded'
+
+ login_update_cycle:
+ type: num
+ default: 432000
+ description:
+ de: 'Sekunden bis zum automatischen refreshen des Cookie-files'
+ en: 'seconds till the next automatic login to get a new cookie'
+
diff --git a/alexarc4shng/requirements.txt b/alexarc4shng/requirements.txt
new file mode 100755
index 000000000..7491ce9ce
--- /dev/null
+++ b/alexarc4shng/requirements.txt
@@ -0,0 +1 @@
+#requests requirement moved to core
diff --git a/alexarc4shng/user_doc.rst b/alexarc4shng/user_doc.rst
new file mode 100755
index 000000000..84b35f223
--- /dev/null
+++ b/alexarc4shng/user_doc.rst
@@ -0,0 +1,57 @@
+.. index:: Plugins; Remote Control for Alexa devices
+.. index:: alexarc4shng
+
+AlexaRc4shNG
+###
+
+Konfiguration
+=============
+
+Die Informationen zur Konfiguration des Plugins sind unter :doc:`/plugins_doc/config/alexarc4shng` beschrieben.
+
+
+Web Interface
+=============
+
+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
+
+.. important::
+
+ 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.
+
+
+Aufruf des Webinterfaces
+------------------------
+
+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.
+
+Außerdem kann das Webinterface direkt über ``http://smarthome.local:8383/alexarc4shng`` aufgerufen werden.
+
+
+Beispiele
+---------
+
+Folgende Informationen können im Webinterface angezeigt werden:
+
+Oben rechts werden allgemeine Parameter zum Plugin angezeigt.
+
+Im ersten Tab kann das Cookie File gespeichert werden - in die Textarea via Cut & Paste einfügen und speichern:
+
+.. image:: assets/webif1.jpg
+ :class: screenshot
+
+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:
+
+.. image:: assets/webif3.jpg
+ :class: screenshot
+
+
diff --git a/alexarc4shng/webif/static/img/alexa_cookie_bad.jpg b/alexarc4shng/webif/static/img/alexa_cookie_bad.jpg
new file mode 100755
index 000000000..e0148bb35
Binary files /dev/null and b/alexarc4shng/webif/static/img/alexa_cookie_bad.jpg differ
diff --git a/alexarc4shng/webif/static/img/alexa_cookie_bad.png b/alexarc4shng/webif/static/img/alexa_cookie_bad.png
new file mode 100755
index 000000000..59f3408ce
Binary files /dev/null and b/alexarc4shng/webif/static/img/alexa_cookie_bad.png differ
diff --git a/alexarc4shng/webif/static/img/alexa_cookie_bad2.png b/alexarc4shng/webif/static/img/alexa_cookie_bad2.png
new file mode 100755
index 000000000..4fa6e1fe1
Binary files /dev/null and b/alexarc4shng/webif/static/img/alexa_cookie_bad2.png differ
diff --git a/alexarc4shng/webif/static/img/alexa_cookie_good.png b/alexarc4shng/webif/static/img/alexa_cookie_good.png
new file mode 100755
index 000000000..75a6edfa7
Binary files /dev/null and b/alexarc4shng/webif/static/img/alexa_cookie_good.png differ
diff --git a/alexarc4shng/webif/static/img/alexa_cookie_good1.png b/alexarc4shng/webif/static/img/alexa_cookie_good1.png
new file mode 100755
index 000000000..449af7b5c
Binary files /dev/null and b/alexarc4shng/webif/static/img/alexa_cookie_good1.png differ
diff --git a/alexarc4shng/webif/static/img/favicon.ico b/alexarc4shng/webif/static/img/favicon.ico
new file mode 100755
index 000000000..8a22cecf1
Binary files /dev/null and b/alexarc4shng/webif/static/img/favicon.ico differ
diff --git a/alexarc4shng/webif/static/img/logo_big.png b/alexarc4shng/webif/static/img/logo_big.png
new file mode 100755
index 000000000..93865a18e
Binary files /dev/null and b/alexarc4shng/webif/static/img/logo_big.png differ
diff --git a/alexarc4shng/webif/static/img/plugin_logo.jpg b/alexarc4shng/webif/static/img/plugin_logo.jpg
new file mode 100755
index 000000000..c12056236
Binary files /dev/null and b/alexarc4shng/webif/static/img/plugin_logo.jpg differ
diff --git a/alexarc4shng/webif/static/img/plugin_logo.png b/alexarc4shng/webif/static/img/plugin_logo.png
new file mode 100755
index 000000000..75a6edfa7
Binary files /dev/null and b/alexarc4shng/webif/static/img/plugin_logo.png differ
diff --git a/alexarc4shng/webif/static/img/plugin_logo_old.png b/alexarc4shng/webif/static/img/plugin_logo_old.png
new file mode 100755
index 000000000..31b76039a
Binary files /dev/null and b/alexarc4shng/webif/static/img/plugin_logo_old.png differ
diff --git a/alexarc4shng/webif/static/js/handler.js b/alexarc4shng/webif/static/js/handler.js
new file mode 100755
index 000000000..1734601ab
--- /dev/null
+++ b/alexarc4shng/webif/static/js/handler.js
@@ -0,0 +1,519 @@
+var selectedDevice;
+
+//*******************************************
+// Button Handler for Encoding credentials
+//*******************************************
+
+function BtnEncode(result)
+{
+ user = document.getElementById("txtUser").value;
+ pwd = document.getElementById("txtPwd").value;
+ store2config = document.getElementById("store_2_config").checked;
+ encoded=user+":"+pwd;
+ encoded=btoa(encoded);
+ //document.getElementById("txtEncoded").value = encoded;
+ $.ajax({
+ url: "store_credentials.html",
+ type: "GET",
+ data: { encoded : encoded,
+ user : user,
+ pwd : pwd,
+ store_2_config : store2config
+ },
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ ValidateEncodeResponse(response);
+ },
+ error: function () {
+ document.getElementById("txt_Result").innerHTML = "Error while Communication !";
+ }
+ });
+ return
+}
+//*******************************************
+// Button Handler LogIn to Amazon-Site
+//*******************************************
+
+function BtnLogIn(result)
+{
+ $.ajax({
+ url: "log_in.html",
+ type: "GET",
+ data: {} ,
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ ValidateLoginResponse(response);
+ },
+ error: function () {
+ document.getElementById("txt_Result").innerHTML = "Error while Communication !";
+ }
+ });
+ return
+}
+
+//*******************************************
+// Button Handler LogOff from Amazon-Site
+//*******************************************
+
+function BtnLogOff(result)
+{
+ $.ajax({
+ url: "log_off.html",
+ type: "GET",
+ data: {} ,
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ document.getElementById("txt_Result").innerHTML = response;
+ },
+ error: function () {
+ document.getElementById("txt_Result").innerHTML = "Error while Communication !";
+ }
+ });
+ return
+}
+
+//*******************************************
+// Button Handler for saving Commandlet
+//*******************************************
+
+function BtnSave(result)
+{
+ document.getElementById("txtresult").value = "";
+
+
+ if (document.getElementById("txtCmdName").value == "")
+ {
+ alert ("No Name given for CommandLet, please enter one");
+ return;
+ }
+ if (document.getElementById("txtApiUrl").value == "")
+ {
+ alert ("No API-URL given for CommandLet, please enter one");
+ return;
+ }
+
+ document.getElementById("txtButton").value ="BtnSave";
+
+ myPayload = myCodeMirrorConf.getValue();
+ StoreCMD
+ (
+ document.getElementById("txtValue").value,
+ document.getElementById("selectedDevice").value,
+ myPayload,
+ document.getElementById("txtCmdName").value,
+ document.getElementById("txtApiUrl").value,
+ document.getElementById("txtDescription").value
+ );
+
+}
+
+//*******************************************
+// Button Handler for checking Json
+//*******************************************
+
+function BtnCheck(result)
+{
+
+ document.getElementById("txtButton").value ="BtnCheck";
+ try {
+ // Block of code to try
+ myValue = document.getElementById("txtValue").value
+ myPayload = myCodeMirrorConf.getValue();
+ myPayload = myPayload.replace("",myValue);
+ var myTest = JSON.stringify(JSON.parse(myPayload),null,2)
+ myCodeMirrorConf.setValue(myTest);
+ myCodeMirrorConf.focus;
+ myCodeMirrorConf.setCursor(myCodeMirrorConf.lineCount(),0);
+ document.getElementById("txtresult").value = "JSON-Structure is OK";
+ document.getElementById("resultOK").style.visibility="visible";
+ document.getElementById("resultNOK").style.visibility="hidden";
+
+ }
+ catch(err) {
+ // Block of code to handle errors
+ document.getElementById("txtresult").value = "JSON-Structure is not OK\n"+err;
+ document.getElementById("resultOK").style.visibility="hidden";
+ document.getElementById("resultNOK").style.visibility="visible";
+ }
+}
+
+//*******************************************
+// Button Handler for testing
+//*******************************************
+
+function BtnTest(result)
+{
+ selectedDevice = document.getElementById("selectedDevice").value;
+
+ if (selectedDevice == "no Device selected")
+ {
+ alert ("No Device selected for Test, first select one");
+ return;
+ }
+ txtValue = document.getElementById("txtValue").value;
+ if (txtValue == "")
+ {
+ alert ("No Value set to send, please enter value");
+ return;
+ }
+
+ document.getElementById("txtButton").value ="BtnTest";
+ myPayload = myCodeMirrorConf.getValue();
+
+ TestCMD
+ (
+ document.getElementById("txtValue").value,
+ document.getElementById("selectedDevice").value,
+ myPayload,
+ document.getElementById("txtCmdName").value,
+ document.getElementById("txtApiUrl").value,
+ document.getElementById("txtDescription").value
+ );
+}
+
+//*******************************************
+// Button Handler for deleting
+//*******************************************
+
+function BtnDelete(result)
+{
+ buildCmdSequence();
+ return
+ var filetodelete = document.getElementById("txtCmdName").value;
+ if (filetodelete == "") {
+ alert ("No Command selected to delete, first select one");
+ return;
+ }
+ filetodelete=filetodelete+".cmd";
+ var r = confirm("Your really want to delete\n\n"+ filetodelete + "\n\nContinue ?");
+ if (r == false) {
+ return;
+ }
+ document.getElementById("txtButton").value ="BtnDelete";
+ DeleteCMD
+ (
+ document.getElementById("txtValue").value,
+ document.getElementById("selectedDevice").value,
+ "",
+ document.getElementById("txtCmdName").value,
+ document.getElementById("txtApiUrl").value,
+ document.getElementById("txtDescription").value
+ );
+}
+
+
+//*************************************************************
+// ValidateLoginResponse -checks the login-button
+//*************************************************************
+
+function ValidateLoginResponse(response)
+{
+var myResult = ""
+var temp = ""
+var objResponse = JSON.parse(response)
+for (x in objResponse)
+ {
+ temp = temp + objResponse[x]+"\n";
+ }
+
+document.getElementById("txt_Result").innerHTML = temp;
+}
+
+//*************************************************************
+// ValidateEncodeResponse -checks the login-button
+//*************************************************************
+
+function ValidateEncodeResponse(response)
+{
+var myResult = ""
+var temp = ""
+var objResponse = JSON.parse(response)
+for (x in objResponse)
+ {
+ if (x == "0")
+ {
+ document.getElementById("txtEncoded").value = objResponse[x].substr(8);
+ }
+ else
+ {
+ temp = temp + objResponse[x]+"\n";
+ }
+ }
+
+document.getElementById("txt_Result").value = temp;
+}
+
+
+
+//*************************************************************
+// ValidateResponse - checks the response for button-Actions
+//*************************************************************
+
+function ValidateResponse(response)
+{
+var myResult = ""
+var temp = ""
+var objResponse = JSON.parse(response)
+for (x in objResponse[0])
+ {
+ if (x == "Status") {
+ myResult = objResponse[0][x];
+ }
+ else {
+ temp = temp + objResponse[0][x]+"\n";
+ }
+ }
+
+document.getElementById("txtresult").value = temp;
+if (myResult == "OK")
+{
+ document.getElementById("resultOK").style.visibility="visible";
+ document.getElementById("resultNOK").style.visibility="hidden";
+ reloadCmds();
+}
+else
+{
+ document.getElementById("resultOK").style.visibility="hidden";
+ document.getElementById("resultNOK").style.visibility="visible";
+}
+
+}
+
+//*******************************************
+// Function to Test Command-Let
+//*******************************************
+
+function TestCMD(txtValue,selectedDevice,txt_payload,txtCmdName,txtApiUrl,txtDescription) {
+ $.ajax({
+ url: "handle_buttons.html",
+ type: "GET",
+ data: { txtValue : txtValue,
+ selectedDevice:selectedDevice,
+ txtButton : "BtnTest",
+ txt_payload : txt_payload,
+ txtCmdName : txtCmdName,
+ txtApiUrl : txtApiUrl,
+ txtDescription : txtDescription} ,
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ ValidateResponse(response)
+ },
+ error: function () {
+ document.getElementById("txtresult").value = "Error while Communication !";
+ document.getElementById("resultOK").style.visibility="hidden";
+ document.getElementById("resultNOK").style.visibility="visible";
+ }
+ });
+ return
+}
+
+//*******************************************
+// Function to Save Command-Let
+//*******************************************
+
+function StoreCMD(txtValue,selectedDevice,txt_payload,txtCmdName,txtApiUrl,txtDescription) {
+ $.ajax({
+ url: "handle_buttons.html",
+ type: "GET",
+ data: { txtValue : "",
+ selectedDevice:selectedDevice,
+ txtButton : "BtnSave",
+ txt_payload : txt_payload,
+ txtCmdName : txtCmdName,
+ txtApiUrl : txtApiUrl,
+ txtDescription : txtDescription} ,
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ ValidateResponse(response)
+ },
+ error: function () {
+ document.getElementById("txtresult").value = "Error while Communication !";
+ document.getElementById("resultOK").style.visibility="hidden";
+ document.getElementById("resultNOK").style.visibility="visible";
+ }
+ });
+ return
+}
+
+//*******************************************
+// Function to Delete Command-Let
+//*******************************************
+
+function DeleteCMD(txtValue,selectedDevice,txt_payload,txtCmdName,txtApiUrl,txtDescription) {
+ $.ajax({
+ url: "handle_buttons.html",
+ type: "GET",
+ data: { txtValue : "",
+ selectedDevice:selectedDevice,
+ txtButton : "BtnDelete",
+ txt_payload : txt_payload,
+ txtCmdName : txtCmdName,
+ txtApiUrl : txtApiUrl,
+ txtDescription : txtDescription} ,
+ contentType: "application/json; charset=utf-8",
+ success: function (response) {
+ ValidateResponse(response)
+ },
+ error: function () {
+ document.getElementById("txtresult").value = "Error while Communication !";
+ document.getElementById("resultOK").style.visibility="hidden";
+ document.getElementById("resultNOK").style.visibility="visible";
+ }
+ });
+ return
+}
+
+
+//************************************************
+// OnClick-function for Command-List
+//************************************************
+
+function SelectCmd()
+{
+
+//$("#AlexaDevices").on("click", "tr",function()
+
+ var value = $(this).closest("tr").find("td").first().text();
+
+ if (value != "") {
+ alert(value);
+ }
+
+}
+
+//************************************************
+// builds and show table with saves Commandlets
+//************************************************
+
+
+function build_cmd_list(result)
+{
+
+ var temp ="";
+ temp = "