mshtools Hilfe beim Erweitern bestehender Python scripte

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo,
ich bin kein Programmierer sondern eher ein Bastler. Auf https://gitlab.com/shd290/mshtools/-/bl ... /README.md wurde zwei PY-Scripte veröffentlicht, mit deren Hilfe man die BT-Kommunikation zwischen Smart-Home Komponenten der Aldi/Medion Alarmanlage "mitschneiden" bzw. auswerten kann. Auch im Medion Forum gab es dazu Hinweise: https://community.medion.com/t5/Smart-H ... /m-p/90245. Die bestehenden PY-Scripte beschränken sich auf Bewegungsmelder, Magnetkontakte und Rauchmelder. Ich habe versucht das Coding zu erweitern, damit auch weitere Smart-Home Komponenten ausgelesen werden können. Für die Komponente Alarmaußensirene ist mir das zumindest schon gelungen. Aber meine Versuche eine schaltbare Steckdose, eine Fernbedienung oder den Zustand der Alarmzentrale selbst, auszulesen sind leider nicht erfolgreich.
Ich habe vergeblich versucht beim Programmierer der MSHTOOLS (Mike Hermdson@shd290 bzw. pysh) weitere Hilfe zu bekommen, aber leider keine Reaktion mehr erhalten.
Jetzt könnte ich dringend Hilfe gebrauchen. Gern würde ich einen Python-Experten mal das von mir erweiterte Pythoncoding mailen, verbunden mit der Hoffnung es handelt sich nur um kleine Programmierfehler.

Schon mal vielen Dank für die Hilfe + mfg
deeb
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@deeb: Mein Bauchgefühl sagt mir, dass es eher schwierig sein wird, jemanden für ein Projekt zu begeistern, dass sehr spezielle Hardware voraussetz. Die Wahrscheinlichkeit könnte steigen, wenn du den Code, der Probleme macht, hier zeigst. Was erwartest du, was er tut; was tut er stattdessen und welche Fehlermeldungen gibt es? Erfahrungsgemäß gibt hier dann Menschen, die gerne Hilfe zur Selbsthilfe beitragen.
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo sparrow, gern kann ich den Code hier veröffentlichen, wenn etwas Aussicht auf Hilfe besteht. Fehlermeldungen bekomme ich nicht, aber ich ich kann sagen, was er macht und was er nicht macht aber von mir erwartet wird. Sicherlich gibt es eine Anleitung in diesen Forum, wie ich Code in den Text einfügen kann und was dabei zu beachten ist (alles oder besser nur Codeausschnitte usw.). Da ich den Code schon erweitert habe, ist Hilfe zur Selbsthilfe genau das richtige.
deeb
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Dann zeig den Teil vom Code, der nicht tut, was er soll. Eingefügt zwischen Code Tags (die erscheinen automatisch, wenn du den </>-Button im vollständigen Editor & Vorschau klickst).
Am besten ist ein minimales, lauffähiges Beispiel.
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo sparrow, das ist der erste Teil des von mir ergänzten Codeblockes.
Ursprünglich war dort nur die Aufzählung der DataDeviceType 4 =Türkontakt, 7= Rauchmelder und 10=Bewegungsmelder).
Ich habe die anderen DataDeviceTypen 142=Zentrale, 15= Fernbedienung, 14=Alarmsirene und 5=Schaltsteckdose ergänzt.
Für den DataDeviceTyp 14=Alarmsirene hat es auch funktioniert, aber nicht für 142, 15 und 5.

Code: Alles auswählen

try:
    with open('smarthome.json', 'r') as f:
        distros_dict = json.load(f) 
        for i in range(len(distros_dict)):
            if distros_dict[i]['DataDeviceType'] == 142 or distros_dict[i]['DataDeviceType'] == 15 or distros_dict[i]['DataDeviceType'] == 14 or \
                distros_dict[i]['DataDeviceType'] == 10 or distros_dict[i]['DataDeviceType'] == 7 or distros_dict[i]['DataDeviceType'] == 5 or \
                distros_dict[i]['DataDeviceType'] == 4:
                mac = distros_dict[i]['name']
                dictKeys[mac] = distros_dict[i]['key']
                dictNames[mac] = re.sub('[^A-Za-z0-9_]+', '_', distros_dict[i]['DeviceName']) # get clean name
                dictType[mac] = distros_dict[i]['DataDeviceType']
                if distros_dict[i]['DataDeviceType'] == 7 or distros_dict[i]['DataDeviceType'] == 5 :
                    listSD.append(mac.upper())
except:
    print('error reading smarthome.json')
    exit(0)
if len(dictKeys) == 0:
    print('error no sensors found')
    exit(0)
Etwas weiter im Coding werden dann die eigentlich wichtigen BT Werte für die jeweiligen Melder ausgewertet:

Code: Alles auswählen

        
        if startfound == 1 and datafound == 1 and mac in dictNames and len(data) == 32 and rssivalue < -10:
            # process frame
            key = dictKeys[mac]
            decrMessage = DecryptBLE(data, key, mac)
            if decrMessage.startswith('10') == False or decrMessage.endswith('00000000') == False:
                if debuginfo:
                    print(debugtext)
            else:
                value_int = 0
                intermessage = 'unknown'
                printinfo = False
                initmsg = False
                if mac in dictLast:
                    if dictType[mac] == 10: # 'motion detector':
                        if dictLast[mac] != decrMessage:
                            printinfo = True
                            value_int = int(decrMessage[4:6]+decrMessage[2:4], 16)
                            if decrMessage.endswith('10000000000'):
                                intermessage = 'move'
                            else:
                                intermessage = decrMessage
                    if dictType[mac] == 7: # 'smoke detector':
                        if dictLast[mac] != decrMessage:
                            value_int = 1
                            if decrMessage.endswith('1000000000000000000'):
                                value_int = 1
                                intermessage = 'normal'
                            elif decrMessage.endswith('6000000000000000000'):  
                                intermessage = 'alarm_6'
                            elif decrMessage.endswith('4000000000000000000'):  
                                intermessage = 'alarm_4'
                            else:
                                intermessage = decrMessage
                            printinfo = True
                    if dictType[mac] == 4: # 'door contact':
                        if dictLast[mac][8:] != decrMessage[8:]:
                            value_int = 1
                            printinfo = True
                            if decrMessage.endswith('1000000000000000000'):
                                value_int = 0
                                intermessage = 'close'
                            elif decrMessage.endswith('1370000000000000000'):  
                                value_int = 0
                                intermassage = 'close'
                            elif decrMessage.endswith('2000000000000000000'):  
                                intermessage = 'open'
                            elif decrMessage.endswith('2370000000000000000'):  
                                intermessage = 'open'
                            else:
                                intermessage = decrMessage
                    if dictType[mac] == 142: # 'control panel':
                        if dictLast[mac] != decrMessage:
                            value_int = 1
                            if decrMessage.endswith('2E02'):
                                intermessage = 'Home' 
                                value_int = 1
                            elif decrMessage.endswith('2E01'):  
                                intermessage = 'Aktiviert'
                                value_int = 0
                            else:
                                intermessage = 'Deaktiviert'
                                value_int = 1
                            printinfo = True
                    if dictType[mac] == 14: # 'external siren':
                        if dictLast[mac] != decrMessage:
                            value_int = 1
                            if decrMessage.endswith('1000000000000000000'):
                                intermessage = 'alles ok'
                                value_int = 0
                            elif decrMessage.endswith('3000000000000000000'):  
                                intermessage = 'Alarm!'
                            else:
                                intermessage = decrMessage
                            printinfo = True
                    if dictType[mac] == 5: # 'switch adapter':
                        if dictLast[mac] !=  decrMessage:
                            value_init = 1
                            printinfo = True
                            if decrMessage.endswith('A010B', 0, 18):
                                intermessage = 'Schalter ein'
                                value_init = 1
                            else:
                                intermessage = 'Schalter aus'
                            printinfo = True
                    if dictType[mac] == 15: # 'remote control':
                        if dictLast[mac] != decrMessage:
                            value_init = 1
                            printinfo = True
                            intermessage = 'Fernbedienung'
                    dictLast[mac] = decrMessage
                else:

Was alles davor und dazwischen im Coding noch so passiert, kann ich nicht genau sagen. Vielleicht gibt es da noch weitere Abfragen die dann die anderen Melder von mir rausschmeißen??

Vielen dank für deine Mühe, gern kann ich auch noch den Rest vom Coding, das Drumherum (sind aber 433 Zeilen) zeigen.

Danke DEEB
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Den ganzen Code mag ich nicht durchgehen, aber als Beispiel schreibt man den folgenden Ausschnitt

Code: Alles auswählen

with open('smarthome.json', 'r') as f:
    distros_dict = json.load(f) 
    for i in range(len(distros_dict)):
        if distros_dict[i]['DataDeviceType'] == 142 or distros_dict[i]['DataDeviceType'] == 15 or distros_dict[i]['DataDeviceType'] == 14 or \
            distros_dict[i]['DataDeviceType'] == 10 or distros_dict[i]['DataDeviceType'] == 7 or distros_dict[i]['DataDeviceType'] == 5 or \
            distros_dict[i]['DataDeviceType'] == 4:
            mac = distros_dict[i]['name']
wohl eher besser so:

Code: Alles auswählen

DEVICE_TYPE_SELECTION = set(4, 5, 7, 10, 14, 15, 142)
...

with open('smarthome.json') as f:
    devices = json.load(f) 
    for device in devices:
        if device['DataDeviceType'] in DEVICE_TYPE_SELECTION:
            mac = devices['name']
            ...
D.h Du kannst den Code wahrscheinlich stark eindampfen, was die Übersicht verbessert und bei der Fehlersuche hilft.

Die Bezeichner scheinen unglücklich gewählt. So nehme ich an, dass distros_dict vermutlich kein dict ist, sondern eine Liste von dicts, die wiederum devices repräsentieren.

Um in Erfahrung zu bringen, was da genau passiert, ist es wichtig die Struktur der eingelesenen json-Daten zu kennen.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Der Code ist nicht so hübsch. Die Frage ist: Wie weit möchtest du in das Thema Programmieren einsteigen? Du hast gesagt, dass du Bastler bist. Das eine schließt das andere ja nicht aus. Und wenn der Code nicht von dir ist, kann es sehr sinvoll sein, den erst einmal aufzuräumen.

Code: Alles auswählen

for i in range(len(distros_dict)):
Das ist ein anti-pattern in Python. Man iteriert nciht mit einm künstlichen Index über die Länge etwas Iterierabaren, weil man direkt über die Elemente iterieren kann:

Code: Alles auswählen

#falsch
for i in range(len(distros_dict)):
    if distros_dict[i]['DataDeviceType'] == 142

# besser
for distro in distros_dict:
        if distro['DataDeviceType'] == 142
Und das hier würde man auch anders lösen.

Code: Alles auswählen

# unschön:
            if distros_dict[i]['DataDeviceType'] == 142 or distros_dict[i]['DataDeviceType'] == 15 or distros_dict[i]['DataDeviceType'] == 14 or \
                distros_dict[i]['DataDeviceType'] == 10 or distros_dict[i]['DataDeviceType'] == 7 or distros_dict[i]['DataDeviceType'] == 5 or \
                distros_dict[i]['DataDeviceType'] == 4:

#besser
            if distros_dict[i]['DataDeviceType'] in (142, 15, 14, 10, 7, 5 4):

#mit der Änderung weiter oben:
            if distro['DataDeviceType'] in (142, 15, 14, 10, 7, 5 4):
Damit sind wir aber schon bei dem nächsten Stolperstein: Den magischen Zahlen.
Du merkst selbst, dass du geraded an ganz vielen Stellen herumschrauben musst, damit du eine neue Komponente hinzufügen kannst. Deshalb würde man irgendwo an einer Stelle die Komponenten definieren und deren "Behandlung", anstatt das üerall im Code zu verstreuen.

Die Frage ist also, wie tief du als Bastler in das Programmieren einsteigen möchtest. Denn den Code dort zu debuggen und zu erweitern bedeutet eigentlich ihn komplett zu überarbeiten, um ihn in einen wartbaren Zustand zu bringen. Könnte spannend für dich sein.

Edit: kbr war schneller, aber ich dachte ich gebe mal Motivation mit ;)
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo, danke für die schnelle Reaktionen.

Wenn es mir weiterhilft, bin ich auch gern bereit in die Pythonprogrammierung einzusteigen.
Aber ich glaube nicht, das ich dann gleich den eleganten/besten Weg finden werde, sondern zufrieden bin wenn das passiert, was ich erwarte.
Habe auch noch keine Ahnung wie man sowas debuggen kann, um zur Laufzeit zu sehen was wo passiert. Vielleicht versteht man dann auch das Coding besser.
Bei meinen ersten Python-Anpassungsversuchen bekam ich z.B. Fehlermeldungen weil am Ende einer eingefügten Zeile zwei Leerzeichen fehlten?!

Ich denke auch, dass distros_dict vermutlich kein dict ist, sondern eine Liste von dicts, die wiederum devices repräsentieren.

Die Struktur der eingelesenen json-Datei sieht in etwa so aus (Beispiel für die Zentrale und einen Magnetkontakt) vom Server ausgelesen mit SH_GetCloudData.py

Code: Alles auswählen

 [
 {"name": "3C6A9D007211", "DeviceName": "De_Az1", "DataDeviceType": 142, "PSignalStr": 4, "PBattery": 0, "key": "", "BLEDataPayload": "0x10000000002E03", "DateTime": "2020-06-18 10:22:50"}, 
 {"name": "50338BFF015D", "DeviceName": "MK1 Wohnz. Terrasse 524", "DataDeviceType": 4, "PSignalStr": 3, "PBattery": 100, "key": "0xE523633DC1006927610408AFDA63FAF14DC909E2824E5A35E386D39280ECC399", "BLEDataPayload": "0x10593D22C10901000000000000000000000000", "DateTime": "2020-06-17 18:58:07"}, 
 
In BLEDataPayload stehen die sich in Abhängigkeit vom aktuellen Zustand der einzelnen Melder sich ändernden eigentlich wichtigen Daten.
Name müsste die BT-Mac Adresse sein.

Mit dem Windows Programm MQTTfx bekomme ich noch mehr (Feld value und rssivalue) angezeigt (Beispiel für ein Magnetkontakt):

Code: Alles auswählen

{
  "mac" : "907065F2E9A1",
  "source" : "13-rpi3-MQTT",
  "details" : "close",
  "value" : 0,
  "rssivalue" : -91.0,
  "type" : 4,
  "name" : "MK6_Buero_Balkon"
}
Beispiel für ein Bewegungsmelder:

Code: Alles auswählen

{
  "mac" : "3CA308B8BE1B",
  "source" : "13-rpi3-MQTT",
  "details" : "move",
  "value" : 32703,
  "rssivalue" : -92.0,
  "type" : 10,
  "name" : "BM1_OG_Flur"
}
Im Feld Source steht der Name von dem Raspi auf den die Python-Scripte laufen.
Hier wird im Feld Value noch ein Zähler bei jeder festgestellten Bewegung hochgezählt.

Eure vorgeschlagenen Codeanpassungen werde ich gern (aber erst heute Abend) umsetzen, bin mir aber nicht sicher ob "besserer" Code die Funktionalität verbessert und den Fehler behebt.

Danke für euer Hilfe, bis später und bleibt gesund!!

DEEB
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo kbr,

ich habe die Codingzeile:

Code: Alles auswählen

DEVICE_TYPE_SELECTION = set(4, 5, 7, 10, 14, 15, 142)
bzw.

Code: Alles auswählen

DEVICE_TYPE_SELECTION = set (4, 5, 7, 10, 14, 15, 142)
ziemlich weit oben im Script eingesetzt, weil ich denke es ist eine Art Deklaration.
Ich bekomme dann folgende Fehlermeldung:

Traceback (most recent call last):
File "SH_CheckBLE3.py", line 45, in <module>
DEVICE_TYPE_SELECTION = set (4, 5, 7, 10, 14, 15, 142)
TypeError: set expected at most 1 arguments, got 7


Hallo sparrow,

wenn ich den unschönen Code:

Code: Alles auswählen

            if distros_dict[i]['DataDeviceType'] == 142 or distros_dict[i]['DataDeviceType'] == 15 or distros_dict[i]['DataDeviceType'] == 14 or \
                distros_dict[i]['DataDeviceType'] == 10 or distros_dict[i]['DataDeviceType'] == 7 or distros_dict[i]['DataDeviceType'] == 5 or \
                distros_dict[i]['DataDeviceType'] == 4:
durch folgende Zeile ersetze:

Code: Alles auswählen

if distros_dict[i]['DataDeviceType'] in (142, 15, 14, 10, 7, 5, 4):
bekomme ich folgende Meldung:

CPU Temp: 42.394
error reading smarthome.json


Das würde ich so interpretieren, das das Script weiter durchlaufen wird (einige Codingzeilen später wird tatsächlich die CPU Temperatur ausgelesen) aber irgendwo weiter dann stehen bleibt.

die Änderung:

Code: Alles auswählen

if distro['DataDeviceType'] in (142, 15, 14, 10, 7, 5 4):


bringt folgende Fehlermeldung:
deeb
User
Beiträge: 6
Registriert: Montag 4. Mai 2020, 21:27

Hallo kbr, hallo sparrow,

ich habe eure Vorschläge und Hinweise heute versucht umzusetzen.
Das ist jetzt mein 4. Versuch euch zu antworten. Irgendwie komme ich immer auf eine falsche Taste, und dann ist mein ganzer ausführlicher Antworttext weg.
Vielleicht mag mich der "Vollstandige Editor" nicht.
Bei allen vorgeschlagenen Änderungen bekomme ich Fehlermeldungen:

DEVICE_TYPE_SELECTION = set(4, 5, 7, 10, 14, 15, 142)
Meldung:
Traceback (most recent call last):
File "SH_CheckBLE3.py", line 45, in <module>
DEVICE_TYPE_SELECTION = set (4, 5, 7, 10, 14, 15, 142)
TypeError: set expected at most 1 arguments, got 7

if distro['DataDeviceType'] in (142, 15, 14, 10, 7, 5 4):
Meldung:
SyntaxError: EOL while scanning string literal

if distros_dict['DataDeviceType'] in (142, 15, 14, 10, 7, 5 4):
das Programm läuft etwas weiter bringt dann aber auch eine Meldung:
CPU Temp: 43.47
error reading smarthome.json

Schade, ist vielleicht auch nicht zielführend von mir, (unwissend) einige Codingzeilen auszutauschen und zu hoffen das meine Probleme dadurch gelöst werden.
Habt ihr noch eine Idee für mich?

Wünsche euch noch einen schönen Abend/eine gute Nacht

MfG DEEB
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Etwas unglücklich will set eine Sequenz. Also zb eine Liste. Oder du benutzt die Literale.

Code: Alles auswählen

test = set([1, 2])
oder = {1, 2}
Benutzeravatar
__blackjack__
User
Beiträge: 13114
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@deeb: Die Ausnahme sagt es doch recht eindeutig: `set()` erwartet *ein* Argument, bekommt aber 7 Argumente übergeben. Das ist keine Art Deklaration sondern ein ganz normaler Aufruf von einer Funktion bzw. Klasse. Beispiele wie das richtig aussehen muss stehen in der Dokumentation von diesem Datentyp: https://docs.python.org/3.6/library/stdtypes.html#set

Sets haben auch eine literale Schreibweise. Die wird auch im Tutorial in der Python-Dokumentation gezeigt: https://docs.python.org/3.6/tutorial/da ... .html#sets
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

__deets__ hat geschrieben: Freitag 12. Februar 2021, 23:59 Etwas unglücklich will set eine Sequenz.
So verhalten sich alle Builtins. Warum ist das speziell bei einem Set deiner Meinung nach unglücklich?
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

@snafu: wahrscheinlich nehme ich das so wahr, weil set nicht bin Anfang an dabei war. Ich schreibe niemals list(1, 2), aber set musste man eben schreiben.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

deeb hat geschrieben: Freitag 12. Februar 2021, 23:39 Ich bekomme dann folgende Fehlermeldung:
Traceback (most recent call last):
File "SH_CheckBLE3.py", line 45, in <module>
DEVICE_TYPE_SELECTION = set (4, 5, 7, 10, 14, 15, 142)
TypeError: set expected at most 1 arguments, got 7
@deeb: sorry, ein Flüchtigkeitsfehler von mir. Da ich nicht wusste, ob Du die literale Darstellung kennst, wollte ich es explizit durch den set() Aufruf machen (ursprünglich ging es in Python auch gar nicht anders). Dabei vergesse ich gerne, dass set() nur ein Argument erwartet, welches dann eine Sequenz von Objekten sein muß. Kennt man dieses Detail, ist der Fehler natürlich sofort erkannt und behoben.

Wenn man dagegen neu dabei ist, so wie Du, dann kann einen das anfänglich schon aufhalten. Und wenn Du dann auch noch ein etwas längeres Programm anpassen möchtest, das weder von Dir geschrieben wurde, noch sonderlich übersichtlich erscheint, dann ist all dies in Kombination schon eine Herausforderung.

Viel Erfolg.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Genau genommen erwartet ein set() - auch wieder wie die anderen Builtins - ein Iterable. Somit würde prinzipiell auch ein Generator gehen, auch wenn das selten vorkommt. :)
Benutzeravatar
__blackjack__
User
Beiträge: 13114
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: So selten finde ich das gar nicht. Insbesondere bei `list()` nutze ich das oft, aber auch bei `set()` wenn ich Elemente eines ”iterables” in einer Menge sammeln möchte. In der Zeit zwischen Einführung von Generatorsyntax und „set comprehensions“ musste man das ja sogar für Generatorausdrücke nehmen, wenn man das Zwischenergebnis nicht in einer Liste sammeln wollte oder man musste auf eine ``for``-Schleife ausweichen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten