Erstellen eines Alexa Skill mit Python und AWS Lambda
Dieser Beitrag soll einen Überblick über die Entwicklung eines benutzerdefinierten Alexa Skill für den Sprachassistenten von Amazon geben. Dabei greifen wir für die Ausgabe der Sensorwerte, wie zum Beispiel Temperatur, Luftfeuchtigkeit und Spannung, auf eine eigene Datenbank per Request über eine AWS Lambda Funktion zurück. Diesen Beitrag habe ich am 05.12.2020 überarbeitet und den Python-Code für Python 3 aktualisiert.
Aufbau eines Alexa Skill
Um ein Custom Skill zu entwickeln, sollte man sich den Ablauf von der Spracheingabe bis zur passenden Antwort einmal vor Augen führen.
Der Ablaufplan sieht ungefähr so aus:
- Der Benutzer spricht mit Echo unter Verwendung von Triggerwörtern, sodass Alexa weiß, dass eine Interaktion stattfindet und welches zugeordnete Skill verwendet werden soll. Zum Beispiel: “Alexa, frage Zentrale nach Temperatur in Küche”. In diesem Fall sind die Auslöseworte “Alexa” zur Aktivierung des Echo und “Zentrale” zur Verwendung bzw. der Weitergabe der Daten an das gewünschten Skill.
- Echo sendet die Anforderung an die Alexa Service Plattform, die die Spracherkennung verarbeitet und die im Skill festgelegten Intents identifiziert und als im JSON-Format codiertes Textdokument an den Host, mit dem entsprechenden verarbeitenden Programm, sendet. In unserem Beispiel ist die Absicht, die “Temperatur” in der “Küche” abzufragen.
- Auf dem Host empfängt das Programm das JSON-Dokument über eine HTTP-Anfrage, beziehungsweise wenn das Programm auf einer AWS-Lambda-Funktion basiert über den direkten Aufruf der Lambda-Funktion. Das Programm liest die übergebenen Absichten aus und verarbeitet diese entsprechend. In unserem Beispiel führt die Übergabe “Temperatur” und “Küche” zur Abfrage unserer Datenbank, mit dem Umweg über ein PHP-Skript per Request, nach dem letzten gespeicherten Temperaturwert in der Küche.
- Die auf der Hostseite angefragten bzw. ermittelten Daten, in unserem Fall zum Beispiel eine “Temperatur” von “20,0” Grad Celsius in der “Küche“, werden wieder per Textdokument im JSON-Format an die Alexa Service Plattform zurückgesandt. Dabei ist zu beachten, der komplette Ausgabetext von Alexa muss als Antwort generiert werden.
- Die Alexa Service Plattform empfängt die Antwort und gibt diese unter Verwendung der Text zu Sprache Funktion, über den Echo, dem Benutzer die Antwort aus. In unserem Fall könnte die Antwort lauten: “Die aktuelle Temperatur am Messort Küche beträgt 20,0 Grad Celsius”.
Für die Umsetzung dieser Alexa Anwendung verwende ich eine AWS Lambda Funktion in Python. Aus dieser greife ich über ein, auf meinem Webhostingpaket gespeichertes PHP Skript auf eine zugehörige MySQL Datenbank zu. Für diesen Weg habe ich mich entschieden, da die Nutzung der AWS Lambda Funktion nur nach Bedarf abgerechnet wird, und für mich somit günstiger als das bei Verwendung eines eigenen Backend notwendige Trusted-Zertifikat ist. Zudem bietet Amazon ein kostenloses Kontingent zum Testen des Services an. Zudem bietet Amazon ein kostenloses Kontingent zum Testen des Services an.
Demonstration
Folgendes Video zeigt die Verwendung eines Amazon Echo Dot mit dem hier vorgestellten Alexa Skill. Dabei rufe ich zuerst nur den Alexa Skill mit den verwendeten Auslösewörtern “Alexa” und “Zentral” ohne direkte Fragestellung auf. Alexa antwortet mit einer im Python Skill vorgefertigten Anweisung. Danach frage ich die zuletzt gespeicherte Temperatur, Luftfeuchtigkeit und Spannung für verschiedene Messorte mit den entsprechenden Begriffen ab. Durch eine Pause bei der Interaktion mit Alexa, gibt diese einen Hinweis zur Beendigung aus. Auch diese Textpassage ist in dem Python Skill vordefiniert. Eine wiederholte Abfrage, “Alexa frage Zentrale nach durchschnittlicher Temperatur in der Küche gestern”, in diesem Fall mit Auslöseworten und hinterlegten Intents, liefert als Antwort die durchschnittliche Temperatur an einem Messort für einen gewünschten Zeitraum. Die weitere Frage mit Auslöseworten und entdprechenden Intents liefert einen Differenzwert zurück.
Voraussetzung für die Umsetzung
Ein Alexa Echo oder Echo Dot wäre natürlich interessant, allerdings ist ein Testen auch auf der Alexa Service Plattform möglich. Was du allerdings unbedingt benötigst, ist ein kostenloser Amazon Developer Account um Zugriff auf die Developer Console zur Entwicklung des Alexa-Skill zu erhalten. Die Nutzung der AWS Lambda Funktion erhältst du über das Amazon-Web-Service Angebot. Damit du dein eigenes Amazon Echo zum Testen ohne große Probleme verwenden kannst, nutze bei der Registrierung die gleichen Daten wie die deines Amazon-Kontos, mit dem das Echo verknüpft ist.
Zur Umsetzung des hier vorgestellten Alexa Skill benötigen wir eine Datenquelle, in diesem Fall eine Datenbank mit entsprechend gespeicherten Werten der verwendeten Sensoren und ein vom Internet aus zugängliches PHP-Skript zur Bereitstellung der angefragten Daten. In meinem Beispiel greife ich auf die in den entsprechenden Beiträgen, zum Beispiel “DS18B20 Temperatursensor am ESP8266….” oder “Temperatur-Sensor DS1820 am Raspberry Pi mit Python” mit der Erweiterung einer entsprechenden Datenbankanbindung verwendeten Datenquellen zurück.
Alexa Skill
Ersteinrichtung
Um mit der Erstellung beginnen zu können, melde dich auf der Developer Console an und starte über “Alexa Skills Kit” in der Registrierkarte “Alexa” die Funktion “Add a New Skill”.
Als Erstes geben wir die “Skill information” ein. Hierzu wählen wir den Skill Type “Custom Interaction Model” und die Sprache “German” aus. Unter Name gibst du die Bezeichnung des Skills ein, dieser wird bei Veröffentlichung im Alexa Skill Store angezeigt. Da wir dieses Skill nur für den Eigengebrauch nutzen, bleibt dieser in der Testphase und du musst dir keine allzu großen Gedanken wegen der Namensgebung machen. 😉 Außerdem kann man die Daten nachträglich immer noch ändern. Wichtiger ist die Eingabe bei Invocation Name, hier wird das Auslösewort, in diesem Fall “zentrale” eingegeben. Die Einstellung bei Global Fields – Audio Player, bleibt bei der Vorauswahl “No”.
Nach der Speicherung bekommen wir eine eindeutige Application Id, diese wird später zur Verbindung in unserer AWS Lambda Funktion benötigt.
Interaction Model
Im Intent-Schema legen wir die Interaktionsmöglichkeit mit Alexa fest. Da in diesem Beispiel verschiedene Messwerte abgefragt werden, allerdings die Örtlichkeiten der angebrachten Sensoren in meinem Fall unterschiedlich sind, habe ich die einzelnen Abfragen zum besseren Verständnis hierzu nach Sensortypen getrennt. Eine Zusammenfassung und Kürzung ist natürlich immer möglich.
Im Intent Schema legen wir die verschiedenen Funktionen zu den einzelnen gewünschten Abfragen fest.
- “intent”: “temperaturAbfrage” – dient zur Abfrage der zuletzt gespeicherten Temperatur eines Messortes
- “intent”: “luftfeuchtigkeitsAbfrage” – dient zur Abfrage der zuletzt gespeicherten Luftfeuchtigkeit eines Messortes
- “intent”: “spannungsAbfrage” – dient zur Abfrage der zuletzt gespeicherten Spannung eines Messortes
- “intent”: “durchschnittsAbfrage” – dient zur Abfrage eines Durchschnittswertes von einem Messort zu einer bestimmten Zeit
- “intent”: “differenzAbfrage” – dient zur Abfrage eines Differenzwertes von einem Messort zu einer bestimmten Zeit
Neben den eigenen Intents, habe ich die folgenden zwei Alexa Standard-Intents in mein Skill eingebunden.
- “intent”: “AMAZON.HelpIntent” – geben dem Benutzer einen Hilfetext
- “intent”: “AMAZON.StopIntent” – lassen den Benutzer einen Vorgang abbrechen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
{ "intents": [ { "intent": "temperaturAbfrage", "slots": [ { "name": "temp_ort", "type": "liste_temp_ort" } ] }, { "intent": "luftfeuchtigkeitsAbfrage", "slots": [ { "name": "luftfeuchte_ort", "type": "liste_luftfeuchte_ort" } ] }, { "intent": "spannungsAbfrage", "slots": [ { "name": "spannungs_ort", "type": "liste_spannungs_ort" } ] }, { "intent": "durchschnittsAbfrage", "slots": [ { "name": "durchschnittsWert", "type": "liste_durchschnitts_wert" }, { "name": "durchschnittsOrt", "type": "liste_temp_ort" }, { "name": "durchschnittsZeit", "type": "liste_durchschnitts_zeit" } ] }, { "intent": "differenzAbfrage", "slots": [ { "name": "differenzWert", "type": "liste_durchschnitts_wert" }, { "name": "differenzOrt", "type": "liste_temp_ort" }, { "name": "differenzZeit", "type": "liste_differenz_zeit" } ] }, { "intent": "AMAZON.HelpIntent" }, { "intent": "AMAZON.StopIntent" } ] } |
In jeder der Funktionen definieren wir mindestens einen Platzhalter für die noch später definierten Satzvariationen. In der Funktion temperaturAbfrage ist dies “temp_ort”, der auf die entsprechende Liste der Custom Slot Types verweist. In dieser Liste werden alle gültigen Parameter für die Funktion aufgelistet. In diesem Fall ist unter dem definierten Slot “liste_temp_ort” folgender Eintrag hinterlegt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
küche gefrierschrank gefrierfach tiefkühlfach kühlschrank schlafzimmer testaufbau gang flur wohnzimmer büro arbeitsplatz bad badezimmer |
Diese Liste kannst und solltest du entsprechend deiner Messorte und verwendeten Begrifflichkeiten erweitern. Zum Beispiel wohnzimmer, wohnraum, wohnstube usw.
Die weiteren einzelnen Type sind in meinem Beispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
liste_differenz_zeit gestern vorwoche vormonat vorjahr liste_durchschnitts_wert temperatur luftfeuchtigkeit spannung liste_durchschnitts_zeit heute gestern woche vorwoche monat vormonat jahr vorjahr liste_luftfeuchte_ort kühlschrank bad badezimmer liste_spannungs_ort testaufbau schlafzimmer flur |
Nun müssen wir nur noch die verschiedenen Satzvariationen mit den entsprechenden {Platzhaltern} unter Sample Utterances anlegen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
temperaturAbfrage temperatur in {temp_ort} temperaturAbfrage wieviel grad sind es in der {temp_ort} temperaturAbfrage temperatur im {temp_ort} temperaturAbfrage wieviel grad sind es im {temp_ort} luftfeuchtigkeitsAbfrage luftfeuchtigkeit im {luftfeuchte_ort} luftfeuchtigkeitsAbfrage wie hoch ist die luftfeuchtigkeit im {luftfeuchte_ort} luftfeuchtigkeitsAbfrage wie viel Prozent beträgt die luftfeuchtigkeit im {luftfeuchte_ort} spannungsAbfrage spannung im {spannungs_ort} durchschnittsAbfrage durchschnittliche {durchschnittsWert} in {durchschnittsOrt} der letzten {durchschnittsZeit} durchschnittsAbfrage durchschnittliche {durchschnittsWert} in {durchschnittsOrt} im letzten {durchschnittsZeit} durchschnittsAbfrage durchschnittliche {durchschnittsWert} in {durchschnittsOrt} {durchschnittsZeit} durchschnittsAbfrage durchschnittliche {durchschnittsWert} in {durchschnittsOrt} diese {durchschnittsZeit} differenzAbfrage unterschied {differenzWert} in {differenzOrt} von heute zu {differenzZeit} differenzAbfrage vergleiche {differenzWert} in {differenzOrt} von heute zu {differenzZeit} differenzAbfrage differenz der {differenzWert} in {differenzOrt} von heute zu {differenzZeit} |
Die Sätze setzten sich aus den folgenden Einzelteilen zusammen, zuerst wird die zu verwendende Funktion benannt, danach kommt der dazugehörige Satz mit den einzelnen Platzhaltern. So wird mit dem Satz, “Temperatur in Küche” die Funktion “temperaturAbfrage” mit der gültigen Begrifflichkeit “Küche” aufgerufen.
Interessanter gestaltet sich zum Beispiel der Satz, “Alexa frage Zentrale nach Unterschied der Spannung in Flur von heute zu Vorwoche”. Neben den Auslöseworten “Alexa” und “Zentrale” werden der Backend-Funktion der Funktionsname “differenzAbfrage” und die Begrifflichkeiten “Spannung” für {differenzWert}, “Flur” für {differenzOrt} und “Vorwoche” für {differenzZeit}.
Configuration
Sobald nun Alexa unser Intent-Schema verwendet, sendet es die entscheidenden Daten per JSON-Dokument an den Host-Server unserer Anwendung. Da wir für dieses Beispiel AWS Lambda verwenden, müssen wir, wie im folgenden Abschnitt beschrieben, eine entsprechende Lambda-Funktion erstellen und die eindeutige ARN dieser Funktion unter Endpoint eintragen.
Test
Nach der Konfiguration gelangen wir zur Testseite. Zum Test ist wie oben schon erwähnt, kein eigener Echo oder Echo Dot notwendig und man kann das Skill über den bereitgestellten Simulator testen.
Programmierung des benutzerdefinierten Skill
Nachdem du dich im Amazon Web Service angemeldet hast, musst du eine neue Lambda-Funktion einrichten. Achte bei der Region auf die vom Alexa Skill Kit geforderte Region Europe. Die notwendigen Schritte zur Erstellung einer Funktion habe ich in folgenden Slider zusammengefasst.
Neben den Quellcode der Funktion musst du einen Namen sowie die Runtime zwingend festlegen. Als Runtime wähle für dieses Beispiel Python 2.7. Aktualisierung am 05.12.2020: Als Runtime wähle für dieses Beispiel Python 3.7.
Danach folgt der Programmcode, den du durch nachstehenden Quellcode ersetzten kannst.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# -*- coding: utf-8 -*- """ Dieser Code setzt auf das Beispielskill von Amazon auf """ from __future__ import print_function import urllib import urllib.request # --------------- Helpers that build all of the responses ---------------------- def build_speechlet_response(title, output, reprompt_text, should_end_session): return { 'outputSpeech': { 'type': 'PlainText', 'text': output }, 'card': { 'type': 'Simple', 'title': "SessionSpeechlet - " + title, 'content': "SessionSpeechlet - " + output }, 'reprompt': { 'outputSpeech': { 'type': 'PlainText', 'text': reprompt_text } }, 'shouldEndSession': should_end_session } def build_response(session_attributes, speechlet_response): return { 'version': '1.0', 'sessionAttributes': session_attributes, 'response': speechlet_response } # --------------- Functions that control the skill's behavior ------------------ def get_welcome_response(): """ If we wanted to initialize the session to have some attributes we could add those here """ session_attributes = {} card_title = "Welcome" speech_output = "Hallo hier Zentrale. " \ "Welchen Messwert von welchem Messort möchtest " \ "Du wissen?" # If the user either does not reply to the welcome message or says something # that is not understood, they will be prompted again with this text. reprompt_text = "Nenne mir bitte den Messort und den Abfragewert " \ "den du wissen möchtest. " should_end_session = False return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def handle_session_end_request(): card_title = "Session Ended" speech_output = "Okay, Abfrage der Zentrale wird beendet. " # Setting this to true ends the session and exits the skill. should_end_session = True return build_response({}, build_speechlet_response( card_title, speech_output, None, should_end_session)) # ---- Funktionen zur Beantwortung der definierten Fragen --------- def get_temp(intent): card_title = "temperaturabfrage" session_attributes = {} should_end_session = False sensor_art = "temperatur" # da es sich um eine Abfrage der Temperatur handelt zeitangabe = "null" # es wird nur der letzte Wert ausgelesen anforderung = "null" # es wird nur der letzte Wert ausgelesen reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'temp_ort' in intent['slots']: messort_name = intent['slots']['temp_ort']['value'] # von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden messwert = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert speech_output = set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung) # Ausgabetext return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def get_luftfeuchte(intent): card_title = "luftfeuchtigkeitabfrage" session_attributes = {} should_end_session = False sensor_art = "luftfeuchtigkeit" # da es sich um eine Abfrage der Luftfeuchtigkeit handelt zeitangabe = "null" # es wird nur der letzte Wert ausgelesen anforderung = "null" # es wird nur der letzte Wert ausgelesen reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'luftfeuchte_ort' in intent['slots']: messort_name = intent['slots']['luftfeuchte_ort']['value'] # von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden messwert = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert speech_output = set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung) # Ausgabetext return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def get_spannung(intent): card_title = "spannungsabfrage" session_attributes = {} should_end_session = False sensor_art = "spannung" # da es sich um eine Abfrage der Spannung handelt zeitangabe = "null" # es wird nur der letzte Wert ausgelesen anforderung = "null" # es wird nur der letzte Wert ausgelesen reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'spannungs_ort' in intent['slots']: messort_name = intent['slots']['spannungs_ort']['value'] # von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden messwert = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert speech_output = set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung) # Ausgabetext return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def get_durchschnitt(intent): card_title = "durchschnittabfrage" session_attributes = {} should_end_session = False sensor_art = intent['slots']['durchschnittsWert']['value'] # von Alexa-Skill übergebene Sensorart zeitangabe = intent['slots']['durchschnittsZeit']['value'] # von Alexa-Skill übergebene Zeitauswahl anforderung = "durchschnitt" # da es sich um eine Abfrage für den Durchschnittswert handelt reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'durchschnittsOrt' in intent['slots']: messort_name = intent['slots']['durchschnittsOrt']['value'] # von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden messwert = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert speech_output = set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung) # Ausgabetext return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def get_differenzwert(intent): card_title = "differenzabfrage" session_attributes = {} should_end_session = False sensor_art = intent['slots']['differenzWert']['value'] # von Alexa-Skill übergebene Sensorart reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'differenzOrt' in intent['slots']: messort_name = intent['slots']['differenzOrt']['value']# von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden, zuerst durchschnittlichen Messwert von heute abfragen anforderung = "durchschnitt" zeitangabe = "heute" messwert_heute = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert # Vergleichswert zu heutigen Messwert abfragen anforderung = "vergleich" zeitangabe = intent['slots']['differenzZeit']['value'] # von Alexa-Skill übergebene Zeitangabe messwert_vergleich_von_db = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert # Generierung der Textausgabe textbaustein_a = "Im Vergleich zur heutigen durchschnitts" + sensor_art + " von " if sensor_art == "temperatur": textbaustein_b = " Grad Celsius " elif sensor_art == "spannung": textbaustein_b = " Volt " elif sensor_art == "luftfeuchtigkeit": textbaustein_b = " Prozent " textbaustein_c = "ist der Wert am Messort " + messort_name + " um " if "-" in messwert_vergleich_von_db: if sensor_art == "temperatur": textbaustein_d = "kälter" else: textbaustein_d = "niedriger" wert = str(messwert_vergleich_von_db) inhalt = wert.split("-") messwert_vergleich = inhalt[1] else: if sensor_art == "temperatur": textbaustein_d = "wärmer" else: textbaustein_d = "höher" messwert_vergleich = messwert_vergleich_von_db textbaustein_e = "als die durchschnittliche " + sensor_art if zeitangabe == "gestern": textbaustein_f = " von " + zeitangabe elif zeitangabe == "vorwoche": textbaustein_f = " der " + zeitangabe elif zeitangabe == "vormonat": textbaustein_f = " des vormonats" else: textbaustein_f = " des vorjahres" speech_output = textbaustein_a + messwert_heute + textbaustein_b + textbaustein_c + messwert_vergleich speech_output = speech_output + textbaustein_b + textbaustein_d + textbaustein_e + textbaustein_f return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) def datenbankabfrage(sensorort, sensorwert, zeitangabe, anforderung): url = "......." abfrageOrt = sensorort.encode('UTF-8') abfrageWert = sensorwert.encode('UTF-8') zeitraumWert = zeitangabe.encode('UTF-8') anforderungWert = anforderung.encode('UTF-8') # erstelle Dictionary mit Sendedaten, Codierung dc = {"data_ort": abfrageOrt,"data_wert": abfrageWert, "data_zeitraum": zeitraumWert, "data_anforderung": anforderungWert} data=urllib.parse.urlencode(dc).encode("utf-8") # sende Daten u = urllib.request.urlopen(url, data) # Empfang der Antowrt li = u.readlines() u.close() wertAusTabelle = "" for element in li: wertAusTabelle += element.decode("utf-8") # Vorbereitung sprachliche Ausgabe des Messwertes wert = str(wertAusTabelle) try: textausgabe = wert.split(".") wertausgabe = textausgabe[0] + " komma " + textausgabe[1] except: wertausgabe = textausgabe[0] + " komma 0" return wertausgabe def get_messort_code(messort_name, messwert): if messwert == "temperatur": if messort_name == "küche": return ("küche") elif messort_name == "gefrierschrank": return ("gefrierfach") elif messort_name == "gefrierfach": return ("gefrierfach") elif messort_name == "tiefkühlfach": return ("gefrierfach") elif messort_name == "kühlschrank": return ("kühlschrank") elif messort_name == "schlafzimmer": return ("schlafzimmer") elif messort_name == "flur": return ("flur") elif messort_name == "gang": return ("gang") elif messort_name == "wohnzimmer": return ("wohnzimmer") elif messort_name == "büro": return ("büro") elif messort_name == "arbeitsplatz": return ("büro") elif messort_name == "bad": return ("bad") elif messort_name == "badezimmer": return ("bad") else : return ("unbekannt") elif messwert == "luftfeuchtigkeit": if messort_name == "kühlschrank": return ("kuehlschrank") elif messort_name == "bad": return ("bad") elif messort_name == "badezimmer": return ("bad") else: return ("unbekannt") elif messwert == "spannung": if messort_name == "schlafzimmer": return ("schlafzimmer") elif messort_name == "flur": return ("flur") elif messort_name == "gang": return ("gang") else: return ("unbekannt") return ("unbekannt") # Bereich der Generierung der Ausgabe def set_reprompt(): rueckgabe = "Wenn Du keine weiteren Fragen hast, " \ "beende bitte die Abfrage mit Stopp." return rueckgabe def set_speech_output_standard(sensor_art, messort_name): rueckgabe = "Für den angefragten Messort " + messort_name if sensor_art == "temperatur": rueckgabe = rueckgabe + " sind keine Temperatursensoren angebracht. " elif sensor_art == "luftfeuchtigkeit": rueckgabe = rueckgabe + " ist kein Luftfeuchtigkeitssensor angebracht. " elif sensor_art == "spannung": rueckgabe = rueckgabe + " ist keine Spannungsmessung vorhanden. " else: rueckgabe = rueckgabe + " ist kein angefragter Sensor vorhanden. " rueckgabe = rueckgabe + "Frage bitte nach einem anderen Messwert oder Ort ." return rueckgabe def set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung): # Anpassung der Ausgabe in Abhängigkeit von anforderung if anforderung == "durchschnitt": textbaustein_a = "Die durchschnittliche " else: textbaustein_a = "Die " # Anpassung der Ausgabe in Abhängigkeit von sensor if sensor_art == "temperatur": textbaustein_c = " Grad Celsius." elif sensor_art == "luftfeuchtigkeit": textbaustein_c = " Prozent." elif sensor_art == "spannung": textbaustein_c = " Volt." # Anpassung der Ausgabe in Abhängigkeit von zeitangabe if zeitangabe == "null": textbaustein_b = " beträgt aktuell " elif zeitangabe == "heute": textbaustein_b = "beträgt " + zeitangabe elif zeitangabe == "gestern": textbaustein_b = "betrug " + zeitangabe elif zeitangabe == "woche": textbaustein_b = "beträgt diese " + zeitangabe elif zeitangabe == "vorwoche": textbaustein_b = "betrug in der " + zeitangabe elif zeitangabe == "monat": textbaustein_b = "beträgt dieses " + zeitangabe elif zeitangabe == "vormonat": textbaustein_b = "betrug im " + zeitangabe elif zeitangabe == "jahr": textbaustein_b = "beträgt dieses " + zeitangabe else : textbaustein_b = "betrug im " + zeitangabe rueckgabe = textbaustein_a + sensor_art + "am Messort " + messort_name + textbaustein_b + messwert + textbaustein_c return rueckgabe # --------------- Events ------------------ def on_session_started(session_started_request, session): """ Called when the session starts """ print("on_session_started requestId=" + session_started_request['requestId'] + ", sessionId=" + session['sessionId']) def on_launch(launch_request, session): """ Called when the user launches the skill without specifying what they want """ print("on_launch requestId=" + launch_request['requestId'] + ", sessionId=" + session['sessionId']) # Dispatch to your skill's launch return get_welcome_response() def on_intent(intent_request, session): """ Called when the user specifies an intent for this skill """ print("on_intent requestId=" + intent_request['requestId'] + ", sessionId=" + session['sessionId']) intent = intent_request['intent'] intent_name = intent_request['intent']['name'] # Dispatch to your skill's intent handlers if intent_name == "temperaturAbfrage": return get_temp(intent) elif intent_name == "luftfeuchtigkeitsAbfrage": return get_luftfeuchte(intent) elif intent_name == "spannungsAbfrage": return get_spannung(intent) elif intent_name == "durchschnittsAbfrage": return get_durchschnitt(intent) elif intent_name == "differenzAbfrage": return get_differenzwert(intent) elif intent_name == "AMAZON.HelpIntent": return get_welcome_response() elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent": return handle_session_end_request() else: raise ValueError("Invalid intent") def on_session_ended(session_ended_request, session): """ Called when the user ends the session. Is not called when the skill returns should_end_session=true """ print("on_session_ended requestId=" + session_ended_request['requestId'] + ", sessionId=" + session['sessionId']) # add cleanup logic here # --------------- Main handler ------------------ def lambda_handler(event, context): """ Route the incoming request based on type (LaunchRequest, IntentRequest, etc.) The JSON body of the request is provided in the event parameter. """ print("event.session.application.applicationId=" + event['session']['application']['applicationId']) """ Uncomment this if statement and populate with your skill's application ID to prevent someone else from configuring a skill that sends requests to this function. """ if (event['session']['application']['applicationId'] != "*************"): raise ValueError("Invalid Application ID") if event['session']['new']: on_session_started({'requestId': event['request']['requestId']}, event['session']) if event['request']['type'] == "LaunchRequest": return on_launch(event['request'], event['session']) elif event['request']['type'] == "IntentRequest": return on_intent(event['request'], event['session']) elif event['request']['type'] == "SessionEndedRequest": return on_session_ended(event['request'], event['session']) |
Der Einstiegspunkt ist wie bei allen Lambda-Funktionen:
1 |
def lambda_handler(event, context): |
am Ende im Bereich “#– Main handler –” des Programmcodes. Hier musst du in der if-Abfrage die Sternchen mit deiner Application Id des erstellten Alexa Skills ersetzen.
1 2 3 |
if (event['session']['application']['applicationId'] != "*************"): raise ValueError("Invalid Application ID") |
Die notwendigen Events bei Aufruf des Skills durch Alexa findest du im Bereich “#– Events –“. Hier ist für uns vor allem die if-Abfrage des eingehenden “intent” in folgender Funktion interessant und ggf. anzupassen, da in dieser Funktion auf die zur Datenermittlung notwendigen Funktionen verwiesen wird.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def on_intent(intent_request, session): """ Called when the user specifies an intent for this skill """ print("on_intent requestId=" + intent_request['requestId'] + ", sessionId=" + session['sessionId']) intent = intent_request['intent'] intent_name = intent_request['intent']['name'] # Dispatch to your skill's intent handlers if intent_name == "temperaturAbfrage": return get_temp(intent) elif intent_name == "luftfeuchtigkeitsAbfrage": return get_luftfeuchte(intent) elif intent_name == "spannungsAbfrage": return get_spannung(intent) elif intent_name == "durchschnittsAbfrage": return get_durchschnitt(intent) elif intent_name == "differenzAbfrage": return get_differenzwert(intent) elif intent_name == "AMAZON.HelpIntent": return get_welcome_response() elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent": return handle_session_end_request() else: raise ValueError("Invalid intent") |
Wenn zum Beispiel die Anfrage zur “Temperatur in der Küche” eingeht, wird über das Alexa Skill der Intent “temperaturAbfrage” übergeben. Die Funktion ruft in diesem Fall folgende Funktion auf.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def get_temp(intent): card_title = "temperaturabfrage" session_attributes = {} should_end_session = False sensor_art = "temperatur" # da es sich um eine Abfrage der Temperatur handelt zeitangabe = "null" # es wird nur der letzte Wert ausgelesen anforderung = "null" # es wird nur der letzte Wert ausgelesen reprompt_text = set_reprompt() # Standardtext für Hinweis bei längerer Ruhe if 'temp_ort' in intent['slots']: messort_name = intent['slots']['temp_ort']['value'] # von Alexa-Skill übergebener Messort messort_code = get_messort_code(messort_name.lower(), sensor_art) # für PHP Abfrage notwendige Messortbezeichnung speech_output = set_speech_output_standard(sensor_art, messort_name) # Standardtext für Hinweis bei nicht vorhandenen Sensor if messort_code != "unbekannt": # Messort ist vorhanden messwert = datenbankabfrage(messort_code, sensor_art, zeitangabe, anforderung) # Abfrage Messwert speech_output = set_speech_output_messwert(sensor_art, messort_name, messwert, zeitangabe, anforderung) # Ausgabetext return build_response(session_attributes, build_speechlet_response( card_title, speech_output, reprompt_text, should_end_session)) |
In dieser Funktion vergebe ich zuerst einen beliebigen card_title. Danach lege ich manuell fest, dass es sich um eine Temperaturabfrage handelt. Man könnte natürlich die Funktion für alle verwendeten Sensoren verwenden und den zugehörigen Intent auslesen. Siehe dir hierzu zum Beispiel einmal die Funktion get_differenzwert() an. Das Selbige gilt auch für die Werte zeitangabe und anforderung, diese sind notwendig für die Datenbankabfrage.
Die verwendete Zeichenkette “reprompt_text” bzw. “speech_output” beinhalten die Textausgaben, welche nach der Rückgabe an das Alexa Skill vom Amazon Echo ausgegeben werden.
Da nicht unbedingt alle verwendeten Begrifflichkeiten für den Messort so in der Datenbank abgespeichert sind, passe ich diese in der Funktion get_messort_code an bzw. gebe ein unbekannt zurück. Dies führt zu dem Hinweis, es ist kein Sensor vorhanden.
Die Request Anfrage zum PHP Skript findest du in der folgenden Funktion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def datenbankabfrage(sensorort, sensorwert, zeitangabe, anforderung): url = "......." abfrageOrt = sensorort.encode('UTF-8') abfrageWert = sensorwert.encode('UTF-8') zeitraumWert = zeitangabe.encode('UTF-8') anforderungWert = anforderung.encode('UTF-8') # erstelle Dictionary mit Sendedaten, Codierung dc = {"data_ort": abfrageOrt,"data_wert": abfrageWert, "data_zeitraum": zeitraumWert, "data_anforderung": anforderungWert} data=urllib.parse.urlencode(dc).encode("utf-8") # sende Daten u = urllib.request.urlopen(url, data) # Empfang der Antowrt li = u.readlines() u.close() wertAusTabelle = "" for element in li: wertAusTabelle += element.decode("utf-8") # Vorbereitung sprachliche Ausgabe des Messwertes wert = str(wertAusTabelle) try: textausgabe = wert.split(".") wertausgabe = textausgabe[0] + " komma " + textausgabe[1] except: wertausgabe = textausgabe[0] + " komma 0" return wertausgabe |
In der Zeichenkette “url” gibst du die Adresse zu deinem PHP-Skript ein. Damit Alexa den Sensorwert korrekt ausgeben bzw. aussprechen kann, bearbeite ich die vom Request übergebene Zeichenkette “wertAusTabelle” am Schluss der Funktion.
Die Definierung der Sprachausgabe bei Aufruf und Beendigung des Alexa Skills findest du im Bereich “# —- Functions that control the skill’s behavior —” in den beiden Funktionen.
1 2 3 |
def get_welcome_response(): def handle_session_end_request(): |
Das PHP-Skript und die verwendete Tabellenstruktur
Das PHP-Skript zur gewünschten Datenbankabfrage könnte wie folgt aussehen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
<?php define("HOST", "****"); // Der Host mit dem du dich verbinden willst. define("USER", "****"); // Der Datenbank-Benutzername. define("PASSWORD", "****"); // Das Datenbank-Passwort. define("DATABASE", "****"); // Der Datenbankname. define("CAN_REGISTER", "any"); define("DEFAULT_ROLE", "member"); define("SECURE", TRUE); // - bei HTTPS auf TRUE sonst FALSE $mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE); mysqli_query($mysqli, "SET NAMES 'utf8'"); $error_net = 0; $rueckgabewert = 0; function sensorwertAbfrage($sensorname, $sensormessart, $anforderung, $abfragezeitraum, $mysqli){ $sql = ""; $sensorwerte = 0; $num = 0; $abfragedatum = date("Y-m-d H:i:s"); $aktuellesdatum = date("Y-m-d"); $datum = explode('-',$aktuellesdatum); if ($datum[1] == 1){ // wenn das Monat der Januar ist, Wert erhöhen damit der Dezember anzeigt wird $datum[1] = 13; } switch ($sensormessart){ case "temperatur": $sql = "SELECT sensorwert_a FROM messwerte WHERE sensorname = ?"; break; case "luftfeuchtigkeit": $sql = "SELECT sensorwert_b FROM messwerte WHERE sensorname = ?"; break; case "spannung": $sql = "SELECT sensorwert_c FROM messwerte WHERE sensorname = ?"; break; } if ($anforderung != "vergleich"){ if ($abfragezeitraum == "null"){ $sql = $sql . " ORDER BY id DESC LIMIT 1"; if ($stmt = $mysqli->prepare($sql)) { $stmt->bind_param('s', $sensorname); $stmt->execute(); $stmt->store_result(); $num = $stmt->num_rows; $stmt->bind_result($result); while($row = $stmt->fetch()){ $sensorwert = $result; } } } else { if ($abfragezeitraum == "heute"){ $abfragedatumbegin = $aktuellesdatum . " 00:00:00"; $abfragedatumende = $abfragedatum; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); } else { switch ($abfragezeitraum){ case "gestern": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime)-1,date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "woche": $dateTime = new DateTime($aktuellesdatum); $dateTime->modify('last Monday'); $abfragedatumbegin = $dateTime->format('Y-m-d') . " 00:00:00"; $abfragedatumende = $abfragedatum; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vorwoche": $dateTime = new DateTime($aktuellesdatum); $dateTime->modify('last Monday'); $strtime = strtotime($dateTime->format('Y-m-d')); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime)-7,date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime)-1,date("Y",$strtime)); $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "monat": $jahr = $datum[0]; $monat = $datum[1]; $tag = '01'; $abfragedatumbegin = $jahr . '-' . $monat . '-' . $tag . ' 00:00:00'; $abfragedatumende = $abfragedatum; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vormonat": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime)-1,date("d",$strtime),date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "jahr": $jahr = $datum[0]; $monat = '01'; $tag = '01'; $abfragedatumbegin = $jahr . '-' . $monat . '-' . $tag . ' 00:00:00'; $jahr = $datum[0]; $monat = $datum[1]; $tag = $datum[2]; $abfragedatumende = $abfragedatum; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vorjahr": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime),date("Y",$strtime)-1); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwert = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; } } } } else { $anforderung = "durchschnitt"; // von vergleich auf durchschnitt zur Werteermittlung $abfragedatumbegin = $aktuellesdatum . " 00:00:00"; $abfragedatumende = $abfragedatum; $sql = $sql . " AND speicherzeitpunkt > ? AND speicherzeitpunkt < ?"; $sensorwertheute = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); switch ($abfragezeitraum){ case "gestern": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime)-1,date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sensorwertvergleich = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vorwoche": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime)-7,date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sensorwertvergleich = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vormonat": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime)-1,date("d",$strtime),date("Y",$strtime)); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sensorwertvergleich = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; case "vorjahr": $strtime = strtotime($aktuellesdatum); $berechnetesdatum = mktime(0,0,0,date("m",$strtime),date("d",$strtime),date("Y",$strtime)-1); $abfragedatumbegin = date("Y-m-d", $berechnetesdatum) . " 00:00:00"; $abfragedatumende = date("Y-m-d", $berechnetesdatum) . " 23:59:59"; $sensorwertvergleich = datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli); break; } $sensorwert = $sensorwertheute - $sensorwertvergleich; } return $sensorwert; } function datenbankAbfrageDurchschnitt($sql, $sensorname, $abfragedatumbegin, $abfragedatumende, $anforderung, $mysqli){ $sensorwerte = 0; $datenbankauswertung = 0; if ($stmt = $mysqli->prepare($sql)) { $stmt->bind_param('sss', $sensorname, $abfragedatumbegin, $abfragedatumende); $stmt->execute(); $stmt->store_result(); $num = $stmt->num_rows; $stmt->bind_result($result); $i = 0; while($row = $stmt->fetch()){ switch ($anforderung){ case "durchschnitt": $sensorwerte = $sensorwerte + $result; break; case "min": if ($i == 0){ $datenbankauswertung = $result; } else { if ($datenbankauswertung > $result){ $datenbankauswertung = $result; } } break; case "max": if ($i == 0){ $datenbankauswertung = $result; } else { if ($datenbankauswertung < $result){ $datenbankauswertung = $result; } } break; } $i = $i + 1; } } if ($anforderung == "durchschnitt"){ if ($sensorwerte != 0){ $datenbankauswertung = round(($sensorwerte / $num), 1); } else { $datenbankauswertung = 0; } } return $datenbankauswertung; } if (isset($_POST['data_ort'], $_POST['data_wert'], $_POST['data_zeitraum'], $_POST['data_anforderung'])) { $sensorort = $_POST['data_ort']; // Messort des abgefragten Sensor küche - wohnzimmer - bad - usw $sensorabfragewert = $_POST['data_wert']; // Art des Sensor temperatur - luftfeuchtigkeit - spannung $abfragezeitraum = $_POST['data_zeitraum']; // null - heute - gestern - woche - vorwoche - monat - vormonat - jahr - vorjahr $anforderung = $_POST['data_anforderung']; // null - durchschnitt - min - max // aktuellen Wert abfragen $rueckgabewert = sensorwertAbfrage($dbsensorort, $sensorabfragewert, $anforderung, $abfragezeitraum, $mysqli); echo $rueckgabewert; } else { echo 'error'; } ?> |
Die Struktur, der von mir für dieses Projekt verwendete Tabelle in der Datenbank zeigt, folgende Skizze.
Fazit und Anmerkung
Die Entwicklung eines Alexa Skill für die Amazon Echo Plattform gestaltet sich relativ einfach. Und da die Nutzung des Skills keine Veröffentlichung im Alexa-Skill-Store voraussetzt, lässt sich die Anbindung der eigenen Sensoren somit gut umsetzten.
Hallo Wolfgang
Dein Projekt ist genial und ich würde es gerne als Basis für eine eigene Idee verwenden.
Mir fehlen irgendwie die Verbindungen und hoffe auf deine Hilfe.
Skill erstellen, mit Intents und Slots spielen das klappt.
Ich weiss aber nicht wie und wo ich die Libraries unterbringen soll, damit über “import” etwas eingebunden werden kann.
Was meinst du mit Modul “urllib” . Das urlib muss doch importiert werden.
Die Libs an der richtigen Stelle unterzubringen macht mir immer Probleme.
Hast du auch am ENDPOINT etwas geändert?
Wo läuft das PHP?
Als erstes möchte ich auf die Datenbank schreiben können. Das wäre super.
Gibt es ein Möglichkeit die Slots während der Laufzeit zu ändern?
Also wenn der Slot “Werkzeug” heisst und es darin einen Schraubedreher, einen Hammer und eine Spitzzange gibt. , habe ich vielleicht irgendwann das Bedürfnis noch einen Gabelschlüssel und eine Ratsche einzufügen.
Gruss und danke
Lothar
Hallo Lothar,
vielen Dank für deinen Kommentar. Die benötigten Bibliotheken musst du nirgendwo ablegen, die stellt Amazon zur Verfügung. Der Import erfolgt wie gewohnt am Anfang des Skripts.
Am Endpunkt musst du die eindeutige ARN der Lambda-Funktion eintragen. Dies ist notwendig, damit dein erstelltes Interaction Model mit dem Python-Skript kommunizieren kann.
Ich verwende das PHP-Tool als BackEnd, um mit der Datenbank zu kommunizieren. Die Datenbank und das PHP-Tool befinden sich auf einem meiner Webspaces.
Du musst den Slot nicht zur Laufzeit ändern, wenn du alles über die Programmlogik machst. So kannst du die Tools in deiner Tabelle verwalten und dann entsprechend ausgeben. Allerdings musst du z.B. den Gabelschlüssel in der Datenbank gespeichert haben oder über den Skill und die Datenbanksteuerung dort anlegen.
Viele Grüße
Wolfgang
Danke! Mit Deiner Anleitung und Codebeispiel hab ich die Hürde geschafft.
Jetzt muß ich noch schauen, wie ich meinen JSON-query code in Lambda reinkriege.
Entweder das Requests odul umständlich per ZIP-File hochladen, oder eingebaute Module benutzen?
Unable to import module ‘lambda_function’: No module named ‘requests’
Hallo Matthias,
zuerst einmal vielen Dank für deine Rückmeldung. Soweit mir bekannt, bleibt dir für das Request-Modul nur der etwas umständliche Weg über den Bibliothek Import.
Viele Grüße
Wolfgang
Hi,
ich habe durch Zufall deinen Artikel hier gefunden. Ich habe ähnliches vor und orientiere mich gerade an der Anleitung. Allerdings spuckt mir der Code Editor beim anlegen der Slots in die Suppe. Sobald ich bei einem der intents Anlege, will er sie auch für alle anderen. Einfaches Copy&Paste von deinem Beispiel ging aber ebenfalls nicht ohne Fehler, weil du intentes mehrfach aufrufst, was laut Code editor nicht erlaubt ist. Hast du ggf. Screenshots deiner Intents, Slot Types und des Codes?
Des Weiteren ist mir nicht ganz klar wo du deine liste “temp_ort_liste” und “temp_ort” abgelegt hast.
Grüße
Hallo,
wie Du schon bemerkt hast, bei der Anlage des Skills bzw. des Interaction Model ist kein Copy&Paste möglich, da Amazon bei der Eingabe eine Überprüfung durchführt. Daher muss jeder “intent” einzeln angelegt werden. Die Fehlermeldung kommt daher eher zustande, da bei einem Copy&Paste noch nicht alle notwendigen Daten eingetragen sind. Die “Listen” werden in den Custom Slot Types eingetragen.
Viele Grüße
Hallo Wolfgang,
eine super Arbeit.
Vielen Dank
Klaus
Hallo Klaus,
vielen Dank für Deine positive Rückmeldung.
Viele Grüße
Wolfgang
Hi Wolfgang,
vor einigen Tagen ist Alexa bei mir eingezogen Eine einfache Wemo-Switch-Emulation mit einem ESP8266 habe ich schon gebaut. Nun will ich mehr!
Deine Anleitung ist sehr ausführlich und das Ergebnis beeindruckend. Nun bin ich neben dem Umgang mit Amazon Echo auch noch Anfänger, was Python betrifft. Deshalb bleiben ein paar Fragen. Die wichtigsten:
In welchem Kontext wird die Datenbankabfrage ausgführt? Auf dem Amazon-Service-Server, d.h. die Datenbank muss über eine öffentliche Adresse erreichbar sein, oder im Kontext des WLANs (direkt vom Echo-Modul), d.h. es reicht ein DB-Server im WLAN?
Und
Ist es möglich, statt der DB-Abfrage z.B. einen Web-Server per HTTP abzufragen oder gibt es dabei Hindernisse, die das Vorhaben prinzipiell unmöglich machen (dass ich noch nicht weiß, wie man das mit Python macht, gehört (hoffentlich) nicht in diese Kategorie :-)) )?
Viele Grüße
Ulli
Hallo Ulli,
vielen Dank für Deinen Kommentar. Am Anfang hatte ich das Gefühl, Alexa versteht mich überhaupt nicht 😀
Doch mit der Zeit haben wir uns gut aneinander angepasst. Besonders nach den ersten Versuchen mit der Erstellung eines komplett eigenen Skills wurde mir die Funktionsweise erst wirklich bewußt.
Die Datenbank muss über eine öffentliche Adresse zugänglich sein, da der Zugriff nicht von Deinem Echo erfolgt, sondern von der ASP (Alexa Service Plattform). Da die ASP per JSON kommuniziert, benötigst Du eine entsprechende Gegenstelle im öffentlichen Netz, die dringend per HTTPS erreichbar sein muss. Amazon bietet den Alexa Developers einen günstigen und einfach einzurichtenden Backend-Bereich auf seinem AWS Lambda an. Auf AWS Lambda stehen Dir, soweit ich mich erinnere, für die Alexa-Skills die Programmiersprachen Python 2.7, JAVA und Node JS zur Verfügung. Die Datenbank-Abfrage ist nicht notwendig, so könnte die Antwort einer Anfrage wie zum Beispiel “Alexa frage Zufallsgenerator nach einer Zufallszahl von 1 bis 100?” direkt in dem kleinen Programm erstellt werden. Eine Abfrage eines Web-Server per HTTP stellt kein Problem dar, da eine HTTPS-Verbindung nur zwischen dem ASP und zum Beispiel AWS notwendig ist.Für die Kommunikation zwischen Python und dem Internet ist das Modul “urllib” eine erste Anlaufstelle 😉 In meinem Beispiel greife ich auch über dieses Modul auf meinen “einfachen” Webspace zur Datenbankabfrage zu, da mir eine direkte Datenbankabfrage über Python außerhalb des AWS nicht möglich war.
Ich hoffe, ich konnte Dir mit meiner Antwort behilflich sein.
Viele Grüße
Wolfgang
Kleiner Hinweis, alle Kommentare werden moderiert.
Dies bedeutet, der Kommentar wird vor der Veröffentlichung durchgelesen und von mir geprüft. Auch behalte ich mir vor, jeden Kommentar zu löschen, der nicht direkt auf das Thema abzielt oder nur den Zweck hat, Leser oder Autoren herabzuwürdigen.
Neuste Beiträge
Kleinen Spickzettel für Raspbian OS Befehle
Entdecke die Welt von Webnist.de
Erfahre mehr über die Hintergründe meines Blogs und wie ich dich bei deinen digitalen Projekten unterstützen kann.Kategorien
KATEGORIEN
Aktuelles Video auf YouTube
Beschreibung der Verwendung eines TTP223B Touch Sensors am GPIO Port des Raspberry Pi mit Python.
Die aktuell 6 derzeit meistbesuchten Beiträge
Beiträge mit den meisten Kommentaren
Funktional Immer aktiv
Vorlieben
Statistiken
Marketing