Websocket Fehler bei Python Script ausführung

Hallo allerseits,
ich habe ein Python-Code mit dem ich ein Licht anschalte. Das funktioniert super auf der Konsole,
aber nicht wenn ich es als Code-Snippets einfüge.
Hier mein Python-Code:>

#!/usr/bin/env python3

import asyncio
import websockets

async def hello():
uri = “ws://192.168.178.102:8080”
addr = “00_0_002”
val = 1
payload = “Update GA:”+addr+"="+str(val)
async with websockets.connect(uri) as websocket:
await websocket.send(payload)
asyncio.get_event_loop().run_until_complete(hello())

Wenn ich den als Code Snippets einfüge und bekomme ich beim Ausführen folgende Fehlermeldung:

Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err] Traceback (most recent call last):
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]   File "_ctypes/callbacks.c", line 234, in 'calling callback function'
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]   File "/var/lib/snips/skills/cetax.Spot_On/venv/lib/python3.5/site-packages/hermes_python/ffi/wrappers.py", line 61, in convert_arguments_when_invoking_function
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]     return func(hermes_client, *parsed_args)
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]   File "./action-cetax-Spot_On-cetax.Spot_On.py", line 29, in subscribe_intent_callback
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]     action_wrapper(hermes, intentMessage, conf)
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]   File "./action-cetax-Spot_On-cetax.Spot_On.py", line 34, in action_wrapper
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err]     import websockets
Nov 01 00:48:49 raspberrypi snips-skill-server[12219]: INFO:snips_skill_server_lib::runner            : [cetax-Spot_On-cetax.Spot_On][err] ImportError: No module named 'websockets'

In den skills sieht so aus:

nano /var/lib/snips/skills/cetax.Spot_On/action-cetax-Spot_On-cetax.Spot_On.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import configparser
from hermes_python.hermes import Hermes
from hermes_python.ffi.utils import MqttOptions
from hermes_python.ontology import *
import io

CONFIGURATION_ENCODING_FORMAT = "utf-8"
CONFIG_INI = "config.ini"

class SnipsConfigParser(configparser.SafeConfigParser):
    def to_dict(self):
        return {section : {option_name : option for option_name, option in self.items(section)} for section in self.sections()}


def read_configuration_file(configuration_file):
    try:
        with io.open(configuration_file, encoding=CONFIGURATION_ENCODING_FORMAT) as f:
            conf_parser = SnipsConfigParser()
            conf_parser.readfp(f)
            return conf_parser.to_dict()
    except (IOError, configparser.Error) as e:
        return dict()

def subscribe_intent_callback(hermes, intentMessage):
    conf = read_configuration_file(CONFIG_INI)
    action_wrapper(hermes, intentMessage, conf)


def action_wrapper(hermes, intentMessage, conf):
        import asyncio
        import websockets

        async def hello():
            uri = "ws://192.168.178.102:8080"
            addr = "00_0_002"
            val = 1
            payload = "Update GA:"+addr+"="+str(val)

            async with websockets.connect(uri) as websocket:
                await websocket.send(payload)
        asyncio.get_event_loop().run_until_complete(hello())

if __name__ == "__main__":
    mqtt_opts = MqttOptions()
    with Hermes(mqtt_options=mqtt_opts) as h:
        h.subscribe_intent("cetax:Spot_On", subscribe_intent_callback) \
         .start()

Ich versteh die beiden Fehlermeldungen nicht :frowning:
websockets ist installiert:

pi@raspberrypi:/var/lib/snips/skills/cetax.Spot_On $ pip list
Package          Version
---------------- -------
chardet          2.3.0
cryptography     1.7.1
enum34           1.1.6
future           0.18.2
gevent           1.4.0
greenlet         0.4.15
hermes-python    0.8.1
idna             2.2
keyring          10.1
keyrings.alt     1.3
numpy            1.12.1
picamera         1.13
pip              19.3.1
pyasn1           0.1.9
pycrypto         2.6.1
pygobject        3.22.0
python-apt       1.1.0b5
pyxdg            0.25
requests         2.12.4
SecretStorage    2.3.1
setuptools       33.1.1
six              1.12.0
ssh-import-id    5.6
typing           3.7.4.1
urllib3          1.19.1
websocket        0.2.1
websocket-client 0.56.0
websockets       7.0
wheel            0.29.0

Hat jemand von euch ne Idee, warum mein Python-Code mit dem aufruf:
pi@raspberrypi:~/TEST $ python Stube_Tisch.py
Funktioniert, aber nicht mit Snips ?

Vielen Dank für eure Hilfe.

Gruß
Stefan

Hi @Cetax,

wenn du ein Snippet erstellst, wird es beim Übertragen des Assistants in eine Template-Datei eingefügt und diese Datei kommt in einen Template-Ordner, welcher verschiedenen Dateien beinhaltet. Die Template-Datei ist bei dir, wie du schon gesehen hast /var/lib/snips/skills/cetax.Spot_On/action-cetax-Spot_On-cetax.Spot_On.py.

Wenn die Skills mit sam install skills oder sam install assistant übetragen werden, wird von SAM in jedem Ordner die setup.sh Datei ausgeführt - bei Snippets ist diese Datei auch vorgefertigt in dem Template-Ordner. In ihr steht, dass ein Python-Virtual-Environment (venv) in dem Ordner erstellt werden soll, und darin die benötigten Abhängigkeiten von der Datei requirements.txt installiert werden sollen. In der Template requirements.txt Datei von den Snippets steht aber nur hermes-python (kann auch noch mehr sein, weiß ich gerade nicht) und eben nicht websockets.

Wenn du dein Skipt einfach so ausführst, werden die Python-Module von deinem System benutzt, also auch websockets. Mit pip list listest du auch nur die Module deines Systems.
Weil aber der snips-skill-server bevor er die action-*.py ausführt in das Virtual Environment im gleichen Ordner wechselt (was vorher von setup.sh erstellt wurde), sind für die von ihm ausgeführten Dateien nur die Python-Module vorhanden, die in der requirements.txt Datei stehen und somit im Virtual Environment installiert wurden.

Da man mit den Code-Snippets nicht den Inhalt der Datei requirements.txt bestimmen kann, bietet sich bei mehr Abhängigkeiten als hermes-python nur an, ein Git-Repository statt einem Code-Snippet für die App zu benutzen, da man dann den Inhalt aller Dateien selbst anpassen kann.

Dass das nicht schwierig ist, kannst du an meiner App Datum und Uhrzeit sehen :slight_smile:

Sorry für die lange Antwort, ich hoffe du hast es jetzt verstanden. Es schadet aber auch nicht, sich zum Beispiel das hier noch anzuschauen, da lernt man einiges.

Gruß atomix

Hi atomix,

vielen Dank für deine Antwort. Ok, das erklärt schon einiges.

Da ber immer die Meldung Couldnt load websocket modul, stellt sich mir die frage,
ob mosquitto überhaupt websockets unterstützt.
Die aussagen im Netz sind etwas widersprüchlich.

Ich wollte mir gerade mosquitto neu compelieren, mit websocket unterstützung, aber dann fange ich erstmal an, deine links zu studieren…

Was ich leider auch nicht kapier, wie man das mit Git-Repo macht…
Aber dann schaue ich mir das als erstes an…

Vielen Danke für die ausführliche Antwort :+1:
Ich freue mich , das überhaupt jemand geantwortet und dann noch Deutsch :+1::+1:

PS: Wenn du noch Tip hast, wie man das mit dem Git-Repo macht, nehm ich auch sehr gern :wink:

Danke

Gruß
Stefan

Also hast du noch keine Kenntnisse mit git? Wenn das so ist, wäre ein Einstieg gleich mit einer Snips App vielleicht ein bisschen hart…

Hi, läuft jetzt. War nicht so schwer :wink: Gibt ja YT Videos :slight_smile:
Aber hab 2 neue kleine Sachen die ich nicht verstehe…

Im fertig Code steht ganz unten immer :

if __name__ == "__main__":
    mqtt_opts = MqttOptions()
    with Hermes(mqtt_options=mqtt_opts) as h:
        h.subscribe_intent("leibnix:leibnix-licht-test", subscribe_intent_callback) \
            .start()

Bei mir steht immer was von h.subscribe_intent("cetax:…* wenn ich als das Snipped mache, aber wenn ich da was reinstelle von Github, steht da h.subscribe_intent("leibnix:…

Was hat das für eine Auswirkung auf das auszuführende Python-Script ?
@atomix bei dir steht wiederum was ganz anderes… Kannst mir das vielleicht erklären ?

Und die 2. Sache, mein Code funktioniert nur einmal :frowning:
Wenn ich nach 2 min das nochmal sage, bekomme ich zwar ein feedback, aber es passiert nix, ich muss dann einen sudo systemctl restart snips-skill-server.service machen und dann geht es wieder einmal. Ich habe das gefühl, mein Code blockiert nach dem ersten mal ausführen , alles weiteren durchläufte.

Hast dafür vielleicht ne idee ?
kannst ja vielleicht mal reinschauen, wenn du Zeit hast… DAAANKE

gruß
Stefan

Hi, mit

h.subscribe_intent("<username>:<intentname>", <pythonfunction>)

“abonierst” du ein einzelnes Intent. Von der Hermes Bibliothek wird das dann in das MQTT-Topic
hermes/intent/<Username>:<Intentname>
umgewandelt und immer wenn dein Script auf diesem Topic eine Nachricht erhält, führt er die Python-Funktion aus, die nach dem Komma angegeben wurde - eine Intent-Callback-Funktion also. Du kannst für jedes Intent eine eigene Funktion erstellen und darin verschiedene Aktionen ausführen.

Wenn du das mit

h.subscribe_intents(<pythonfunction>)

machst (wie ich bei meiner Uhrzeit-App), wird jedes Intent aboniert, die Bibliothek übersetzt diesen Aufruf in das MQTT-Topic
hermes/intent/#
Das # ist dabei ein Platzhalter, damit das Skript auf alle Nachrichten, die mit hermes/intent/ anfangen, reagiert. In meiner Uhrzeit-App habe ich das (warum auch immer) so gemacht, dass in der Callback-Funktion erst überprüft wird, welches Intent überhaupt vom Benutzer gewollt ist - eigentlich etwas ineffizient, da die Callback-Funktion dann bei jedem Intent aufgerufen wird, egal ob es jetzt Uhrzeit oder etwas anderes war.

Der Benutzername wird an den Intentnamen dran gehängt, weil es sonst zu Verwechselungen kommen könnte - wenn zwei Entwickler für ihre App die gleichen Intent-Namen gewählt hätten und man beide Apps installieren würde, könnten die Entwickler dann ja nicht mehr davon ausgehen, dass genau ihr Action-Code ausgeführt wird, weil der andere ja auch genau den Intentnamen auch aboniert hat.
Deshalb musst du zwingend deinen Usernamen vor den Intentnamen dranhängen.
Bei Forks von Apps aus dem Snips App Store wird dann auch der ursprüngliche Username in deinen Usernamen geändert, weshalb Github-Actionscripts dann erst angepasst werden müssen, weil sie sonst nicht mehr reagieren (ein Problem das hier viele Anfänger haben, weil sie die Apps aus dem Store einfach forken und dann nichts mehr passiert).

Ich habe dir mal das Python-Skript verbessert (auch unnötig etwas schöner gemacht) - Die Bibliotheks-Importe dürfen nicht in der Callback-Funktion stehen, da ja sonst jedes Mal beim Aufruf die Bibliothek importiert wird auch wenn sie schon drinnen ist. Deshalb den Import ganz nach oben. Vielleicht war das auch der Fehler, weshalb es immer nur ein Mal ging.
Auch habe ich die Callback-Funktion umbenannt, damit du noch weitere hinzufügen kannst und eine überflüssige Funktion für die Config entfernt.

Wenn du weiter Fragen hast, her damit - so lernt man schnell :slight_smile:

Grüße

Moin Moin,
wow cool :+1: Danke…
habe in dem Code das

#result = ws.rec()

auskommentiert, jetzt klappt es ohne stoppen, das hat auf ein feedback gewartet, das es aber nicht gibt.

Super Erklärung, DANKE.

Wenn ich dich richtig verstehe, kann ich da jetzt noch das “Licht aus” mit einbauen, ohne einen neuen Skill zu erstellen ?

Also nice, wäre es ja , da was einzubauen was sich auf die Räume bezieht.
Etwa so stelle ich mir das vor:

Raum | Wo | An | Aus

Stube | Decke | An | Aus
Stube | Wand | An | Aus

Oder muss ich für jede Lampe einen eigenen Skill anlegen ?

Ich würde jetzt für “Licht aus” das einfügen:

def msg_licht_aus(hermes, intentMessage):

und da drunter den passenden Code.

Ganz am Ende folgendes:

if name == “main”:
mqtt_opts = MqttOptions()
with Hermes(mqtt_options=mqtt_opts) as h:
h.subscribe_intent(“cetax:Esstisch_Licht_an”, msg_licht_an)
else:
if name == “main”:
mqtt_opts = MqttOptions()
with Hermes(mqtt_options=mqtt_opts) as h:
h.subscribe_intent(“cetax:Esstisch_Licht_an”, msg_licht_aus)

Oder wäre das zu einfach ? :blush:

Hi, bitteschön - ich helfe gerne :slight_smile:
Dein Code, der gerade auf GitHub ist:

with Hermes(mqtt_options=mqtt_opts) as h:
    h.subscribe_intent("cetax:Esstisch_Licht_an", msg_licht_an)
    .start()

Die letzte Zeile stimmt noch nicht: entweder muss nach msg_licht_an) noch ein Backslash für Zeilenumbruch oder du schreibst vor das .start() noch ein h , also so:

with Hermes(mqtt_options=mqtt_opts) as h:
    h.subscribe_intent("cetax:Esstisch_Licht_an", msg_licht_an)
    h.start()

Mit dem weiteren h wird einfach die Instanz von dem Hermes-Modul nochmal aufgerufen, in dem Fall wird deren Funktion start aufgerufen.

Für deine Vorstellung habe ich im Prinzip schon die App Lichtsteuerung im App Store, da ist genau das drinnen, was du willst: es gibt ein Intent für Licht an und auch eins für Licht aus.
Du kannst die App also in deinen Assistant rein machen und dein Skript so verändern, dass es die zwei Intents aboniert und für jedes Intent eine andere Callback-Funktion genommen wird. In die zwei Callback-Funktionen müssen dann die Slotwerte ausgelesen und dann die jeweiligen Lampen angesteuert werden.

Mit drei oberen Strichlein (`) fängst du Code-Blöcke hier an und hörst sie auf. Einzelne Strichlein machen einen “Inline”-Code. Bsp:
```python
print(“Hello”)
```
wird zu:

print("Hello")

und `print(“Hello”)` wird zu print("Hello")
Aber jetzt zu deinem Vorschlag:
Du musst nicht mal so viel kopieren (so ist auch der Code falsch), einfach die subscribe_intent-Funktion nochmal aufrufen:

if __name__ == "__main__":
    mqtt_opts = MqttOptions()
    with Hermes(mqtt_options=mqtt_opts) as h:
        h.subscribe_intent("cetax:Esstisch_Licht_an", msg_licht_an)
        h.subscribe_intent("cetax:Esstisch_Licht_aus", msg_licht_aus)
        h.start()

Ok… Ich bin davon ausgegangen, das es eine “else” abfrage geben muss…
Das ist natürlich noch einfacher :slight_smile:

Das heißt (sorry, ich versuche die logik des Programms zu versteh) Snips entscheidet anhand vom “h.subscribe_intent()” ob das Licht AN oder AUS geht.
Je nachdem was er versteht (AN oder AUS)

PS: Dein Licht-Skill schaue ich mir natürlich auch an :wink:

Meine App im Store hat keine Scripte dahinter, weil jeder für sich selbst seine Lichter auf anderem Wege ansteuert, also nicht Wundern, wenn da nichts ist…

Du abonierst die zwei Intents, die du haben willst. Wenn du mit der subscribe_intent-Funktion das Licht-aus-Intent abonierst und die msg_licht_aus-Callbackfunktion angibst, wird genau wenn dieses Intent erkannt wird, diese Callbackfunktion aufgerufen. Deshalb musst du das Licht-an-Intent seperat abonieren und eine eigene Callbackfunktion erstellen, die etwas anderes macht, als bei Licht aus.

Man könnte natürlich das “aus” und “an” auch in einen Slot packen - dann hätte man nur ein Intent und man müsste dann in der Callback-Funktion den Wert von dem Slot nachschauen und abhängig davon das Licht an oder ausschalten (mit if und so). Diese Lösung hat aber den Nachteil, dass der Slot auch immer erkannt werden muss, es also genügend Trainingssätze haben muss. Bei der Lösung oben muss bloß das Intent erkannt werden und dann wird schon die richtige Aktion ausgeführt.

Wenn man schon so gut abkürzen kann, ist der AUS Code bestimmt auch kürzer schreibbar oder ? Muss ich mir mal genauer anschauen…

def msg_licht_aus(hermes, intentMessage):
    conf = read_configuration_file(CONFIG_INI)
    
    ws = create_connection("ws://192.168.178.102:8080")
    ws.send("Update GA:00_0_002=0")
    #result =  ws.recv()
    ws.close()

    if len(intentMessage.slots.house_room) > 0:
        house_room = intentMessage.slots.house_room.first().value # We extract the value from the slot "house_room"
        result_sentence = "Das Licht wird in {} ausgeschaltet".format(str(house_room))  # The response that will be said out loud by the TTS engine.
    else:
        result_sentence = "Lampe aus"

    current_session_id = intentMessage.session_id
    hermes.publish_end_session(current_session_id, result_sentence)```


Den Code würde ich mit reinpacken um das Licht auszuschalten...

Das ist richtig so, gut copy&paste gemacht :wink:

Welche Lampen hast du eigentlich, die mit Websocket angesteuert werden? So Lichter kenn ich bisher nicht…

:see_no_evil: Ist erstmal um das zuverstehen, also wollt ich nicht das Rad neu erfinden :wink:
Ok, ich glaube so langsam komme dahinter, wie diese Abonieren funktioniert und bedeutet.

Ok, dann werde ich mal probieren gehen und mir auch dein Code mal genauer anschauen.:+1:

Danke dir erstmal :blush:

Ich hatte ehlrich gesagt, schon gedacht, das man hier keine Hilfe bekommt und wenn nur auf Englisch, Da ich aber schon ein bissle älter bin, fällt mir das Englische etwas schwer…
Verstehen klappt irgendwann, aber dauert halt…

Also vielen lieben Dank für deine tolle Hilfe. Ich probiere mich jetzt mal durch :wink:

1 Like

Ich habe hier ein EIB System von Gira verbaut. Auf einem Raspi läuft ein eibd der über einen USB Adapter die Befehle sendet (KNX)

Eine Visualisierung auf dem Handy undauf einem Touchscreen läuft schon

OK, so einfach ist es wohl doch nicht :frowning:
Hab ein neues bei github

Ein paar Sachen:

  • wieder das .start() ändern:
if __name__ == "__main__":
    mqtt_opts = MqttOptions()
    with Hermes(mqtt_options=mqtt_opts) as h:
        h.subscribe_intent("cetax:Esstisch_Licht_an", msg_licht_an)
        h.subscribe_intent("cetax:Esstisch_Licht_aus", msg_licht_aus)
.start()

zu

if __name__ == "__main__":
    mqtt_opts = MqttOptions()
    with Hermes(mqtt_options=mqtt_opts) as h:
        h.subscribe_intent("cetax:Esstisch_Licht_an", msg_licht_an)
        h.subscribe_intent("cetax:Esstisch_Licht_aus", msg_licht_aus)
        h.start()

Auch die Einrückung mit den Leerzeichen muss bis dahin sein.

  • Die Datei Stube.py in action-Stube.py umbenennen, sonst wird die vom snips-skill-server (meines Wissens nach) nicht als ausführbare Skriptdatei erkannt.

Wenn was dann noch nicht klappt (und auch in Zukunft) einfach die Fehlermeldungen hier posten, die du mit diesem Komando siehst (das ist dann fortlaufend, also einfach in einem neuen Fenster ausführen und während dem Testen mit dem Sprechbefehl schauen, welcher Fehler kommt):

sam service log snips-skill-server

Mit diesem Befehl kannst du wenn nötig den snips-skill-server neustarten:

sam service restart snips-skill-server

:see_no_evil: Ok … Ich muss wirklich auf die Einrückung achten…
Ich habe es übersehen , das das h.start() am Anfang stand…
Jetzt klappt es :ok_hand::+1:

DANKE :grinning:

Noc eine Frage, wenn ich das ganze Raum bezogen machen möchte, also “Stube Licht an”,
wie binde ich den ORT ein ?

Ist das in deinem Code drin ? Ich schau mir den an…

Sorry, ich habe gerade leider wenig Zeit, ich helfe dir am Wochenende wieder. :slight_smile:

1 Like

Hey atomix, kein stress , alles gut… :slight_smile:
Wie Zeit hast…