Timing von OOK Paketen über 433 MHz

  • Moin,

    ich bin gerade dabei ein Außenthermometer mit einem ESP zu simulieren, damit auf unserer Wetterstation die aktuelle Außentemperatur angezeigt wird. Grund dafür ist, dass unser Außensensor nach einer Weile nicht mehr funktioniert und wir zusätzlich zu dicke Fenster haben, was zu Verbindungsproblemen führt.


    Alten Sensor auslesen (LaCrosse-TX), aktuelle Temperatur aus dem Internet ziehen und diese in einen Binärcode umschreiben funktioniert alles tadellos, auch das Senden hat anfangs für einen Tag funktioniert, doch das Problem muss jetzt irgendwo im Timing des OOK Paketes oder besonderen Fällen der aktuellen Temperatur liegen... Ich weiß da wirklich keinen Rat mehr.


    Um von Binär zu OOK zu kommen, macht man aus jeder 0 ein 110 und aus jeder 1 eine 100. Das habe ich in einem Test-Sketch mit einer festgesetzten Temperatur (49,9°C) ausprobiert und das lief auch problemlos. Hier der Code:

    (Ja, zwei "HIGH" hintereinander sind unnötig, ich habe es der Übersicht halber aber so stehen lassen).


    Jedes Bit in OOK hat eine Länge von 600µs was eher geschätzt war, anhand der Werte des originalen Sensors. Dieser hat laut rtl_433 etwas andere 'pulse width / gap width / pulse period distributions'.


    Was genau bedeutet die Pulse width und wie setzt sie sich zusammen? Die 1300µs von 'long' treffe ich ganz gut, doch bei den 'shorts' habe ich immer so ~100µs zu viel.

    Bezeichnet die Pulse width die Länge an 'HIGH's, also an Einsen hintereinander? Dann würde die Rechnung mit 600+600+etwas Verzögerung = 1300µs nämlich hinkommen und auch die 650µs sind plausibel.


    Dann gibt es da noch Unterschiede bei der Gap und Pulse width: Das Original hat bei Gap eine Zeile mit fast immer 920µs und ich habe dort zwei Zeilen mit 624 und 1268µs, bei denen ich nicht weiß, wie sie Zustande kommen. Und das Original hat zwei Zeilen bei Pulse und ich nur eine. Bei Pulse weiß ich aber, dass es nur die Summe aus den vorherigen Zahlen ist.



    Ist jetzt viel Text geworden, aber vielleicht hat schon mal jemand so etwas versucht und kann mir sagen, wie lange ich jeweils HIGH und LOW halten muss und ob ich noch irgendwelche notwendigen Pausen übersehen habe.


    NACHTRAG 1: rtl_433 kann das Paket richtig entschlüsseln, erkennt es aber nicht als LaCrosse-TX. Wenn ich die Bits händisch übersetze, kommt die richtige Temperatur heraus.


    Grüße

    Kelvin

    Kelvin

    Edited once, last by Kelvin ().

  • Ich glaube, dass ich das Problem gerade dadurch beheben konnte, indem ich einfach nur das Signal 3x schnell hintereinander sende... ^^ Insgesamt sende ich jetzt alle 10 Sekunden das Signal 3x hintereinander mit jeweils einer Pause von 25ms.


    Ich bin aber dennoch an Verbesserungsvorschlägen interessiert, weil rtl_433 und die Wetterstation zwar die Temperatur lesen können, mir ersteres aber noch jede Menge Müll anzeigt, wenn ich mit meinem "Sensor" etwas sende. Da flattern im Millisekundenbereich jede Menge kleine und nicht erkennbare Pakete rein, was man ignorieren kann, aber auch unschön ist.


    EDIT: Zu früh gefreut. Die Station hat wieder nichts empfangen können und auch rtl_433 "versteht" die Pakete nicht. Der Binärcode lässt sich händisch wieder richtig übersetzen (64,5°C - 50°C Offset = 14,5°C), es wird ein OOK Package erkannt, doch es kann nicht richtig als Temperatur interpretiert werden...

    Kelvin

    Edited once, last by Kelvin ().

  • So, ich habe mal probiert die beiden Protokolle (Original/ESP) zu verstehen. Deine Beschreibung trifft genau auf das zu, wie ich das Protokoll des ESP verstehen würde:

    0: Pulsweite = 1300, Pulspause = 624

    1: Pulsweite = 652, Pulspause = 1268

    Alle Pulse (43) haben eine Periodenweite (Pulsweite+Pulspause) von 1924. So weit schlüssig. Die Werte sind wohl eine Art Mittelwert...


    Aber das Protokoll des Originals müsste doch "ganz anders" aussehen als Deine Beschreibung. Alle "Pulspausen" sind 920. Somit käme ich auf folgendes:

    0: Pulsweite = 1304 (Zeile 8, das passt noch ganz gut) , Pulspause 920 (Zeile 11)

    1: Pulsweite = 552 (Zeile 9, da solltest Du schon nachbessern), Pulspause 920 (Zeile 11)

    Ergibt eine Periodenweite von: 0: 2224 (Zeile 13) und 1: 1472 (Zeile 14) - auch das ist schlüssig!


    Mag sein, dass Du Dich an das OOK Protokoll gehalten hast (ich kenne es nicht) - aber das Original hat es offensichtlich dann nicht getan


    Die Umsetzung müsste einfacher sein als die Umrechnerei in jeweils 3 Bits:

    So in etwa in der CHAR - For - Schleife:

    > Case 0: High, 1300us warten,

    > Case 1: High, 550 us warten

    > Low, 920us warten


    Ach ja, wo ich mir nicht sicher bin: Wird ein char Array tatsächlich mit 0 abgeschlossen? Ich würde entweder mit einem String arbeiten oder bei einem Char Array die Länge definieren und damit arbeiten statt strlen zu verwenden. In C wurde jedenfalls meiner Meinung nach kein Char Array automatisch mit 0 abgeschlossen - ob C++ das so macht...? In C gab's auch keine Strings...

    ...wenn Software nicht so hard-ware ;) ...

    Freue mich über jeden like :thumbup:

  • Moin VeryPrivat,

    ich habe es gerade einigermaßen hinbekommen und wollte euch hier auf dem Laufenden halten.

    Aber das Protokoll des Originals müsste doch "ganz anders" aussehen als Deine Beschreibung. Alle "Pulspausen" sind 920. Somit käme ich auf folgendes:

    0: Pulsweite = 1304 (Zeile 8, das passt noch ganz gut) , Pulspause 920 (Zeile 11)

    1: Pulsweite = 552 (Zeile 9, da solltest Du schon nachbessern), Pulspause 920 (Zeile 11)

    Ergibt eine Periodenweite von: 0: 2224 (Zeile 13) und 1: 1472 (Zeile 14) - auch das ist schlüssig!

    Das was du sagst stimmt und darauf bin ich in der letzten Stunde auch drauf gekommen ^^ Die Pulse width gibt an, wie lange man bei einem Bit auf HIGH zieht und dann kommt ein LOW mit der Gap width hinten dran.


    Mein Code zum senden sieht jetzt so aus und deine Verbesserungen bezüglich des Switch-cases werde ich gleich noch einbauen:

    Die Zeiten musste ich noch etwas anpassen, um auf fast genau dieselben Zeiten zu kommen, die der originale Sender liefert.


    Ich lasse das ganze gerade seit 30 Minuten durchlaufen und in 95% der Fälle werden die OOK Pakete richtig von rtl_433 erkannt und mir die entschlüsselte Temperatur angezeigt. Die Wetterstation springt aktuell auch noch darauf an.

    Die ersten 5 Minuten wurde leider gar nichts erkannt, doch jetzt wo der ESP scheinbar warm gelaufen ist, geht es fast durchgehend.


    Ich werde das jetzt erstmal die nächsten 24h beobachten :)



    Ach ja, wo ich mir nicht sicher bin: Wird ein char Array tatsächlich mit 0 abgeschlossen? Ich würde entweder mit einem String arbeiten oder bei einem Char Array die Länge definieren und damit arbeiten statt strlen zu verwenden. In C wurde jedenfalls meiner Meinung nach kein Char Array automatisch mit 0 abgeschlossen - ob C++ das so macht...? In C gab's auch keine Strings...

    Das mit dem char Array ist mir heute Mittag gar nicht aufgefallen. Das war noch ein alter Code, den ich mal im Frühling für etwas ähnliches gebraucht habe. Bei diesem Projekt hier bin ich gleich zu Beginn auf einen String umgestiegen und benutze .length().

    Kelvin

  • Wenn du auf das ESP SDK direkt zugreifst kannst du auch einen Task erstellen, der erhöhte Priorität hat, und auf einem anderen Core läuft. Das hat mir mal bei timing kritischen Aufgaben geholfen. Der ESP macht ja noch ne Menge nebenher für das WIFI, darum kommt sich da sonst gelegentlich was in die Quere.

  • Wenn du auf das ESP SDK direkt zugreifst kannst du auch einen Task erstellen, der erhöhte Priorität hat, und auf einem anderen Core läuft. Das hat mir mal bei timing kritischen Aufgaben geholfen. Der ESP macht ja noch ne Menge nebenher für das WIFI, darum kommt sich da sonst gelegentlich was in die Quere.

    Danke, werde ich mir mal genauer ansehen. Habe so etwas ähnliches schon mal auf dem Pi gemacht, was das Senden deutlich verbessert hat, aber immer noch nicht perfekt war.

    Kelvin

  • Die ersten 5 Minuten wurde leider gar nichts erkannt, doch jetzt wo der ESP scheinbar warm gelaufen ist, geht es fast durchgehend.

    Das kann ich mir nicht erklären. Warmlaufen schon gar nicht. Vermutlich gibt es noch Unterschiede. Sehen Deine Protokolle (Original/ESP) jetzt ganz gleich aus. Optimal wäre es, wenn Du die "Kurvenform" mit einem Logikanalyzer aufnimmst und vergleichst.


    Wenn Du im Code kein WiFi verwendest, sollte Dir da eigentlich nicht viel dazwischen funken. Ich vermute auch nicht, dass das Timing so kritisch ist - der ESP ist ein relativ schneller Controller.

    EDIT: Den Codevorschlag habe ich entfernt, da er offensichtlich keine Verbesserung darstellt (es gibt halt immer noch klügere)

    ...wenn Software nicht so hard-ware ;) ...

    Freue mich über jeden like :thumbup:

    Edited once, last by VeryPrivat ().

  • Das ist nicht optimierter. Der Switch ist nicht teurer als ein if, und dein Code aufgrund der Verzweigung ggf sogar nicht deterministischer. Ich behaupte nicht, dass das wirklich eine Rolle spielt. Aber man kann auch umgekehrt nicht mit “optimaler” argumentieren.


    Der ESP hat ein multi-tasking OS. Und da kommt es durchaus zu Schwankungen bei zeitkritischem Code. Ein weg besteht darin, den Task höher zu priorisieren, wie schon erwähnt. Oder gleich einen ganzen Kern dafür exklusiv abzustellen. Und wenn’s sein muss sogar dessen IRQs zu disablen.

    Edited once, last by MistyFlower59469 ().

  • Warmlaufen schon gar nicht.

    Das war auch nicht so ernst gemeint ^^


    Vermutlich gibt es noch Unterschiede. Sehen Deine Protokolle (Original/ESP) jetzt ganz gleich aus.

    Mit den Zeitabständen bin ich schon sehr nah dran, bin aber immer noch am verfeinern, damit wirklich jedes Paket richtig erkannt wird.

    die Bilanz nach etwas über 20h ist übrigens, dass es fast immer funktioniert hat, wenn ich mal auf die Station geschaut habe. Einen Aussetzer gab es, weshalb ich jetzt auch noch das mit der Priorität ausprobieren werde.


    Wenn Du im Code kein WiFi verwendest, sollte Dir da eigentlich nicht viel dazwischen funken.

    Tatsächlich benutze ich WLAN. Der ESP zieht sich nämlich aus dem Internet die Außentemperatur und sendet diese dann über 433 MHz an die Station. Meinst du, dass ich da noch was rausholen könnte, wenn ich das WLAN jedes Mal deaktiviere, wenn ich über 433 MHz sende (zum einen wegen der CPU-Einsparung und zum Anderen wegen der Minimierung von Interferenzen)?

    Momentan lade ich die aktuelle Temperatur und sende diese dann alle 10 Sekunden für die nächsten 5 Minuten. Dann wird wieder die aktuelle Temperatur geladen und es geht von vorne los. Ich würde das auch noch auf 10 Minuten erhöhen, weil OpenWeatherMap die Werte gar nicht so oft aktualisiert.

    Kelvin

  • Ja, der WIFI Task funkt dazwischen. Aber der läuft meines Wissens nach nur auf einem der beiden Kerne, den anderen zu benutzen schützt also davor.

  • Aber der läuft meines Wissens nach nur auf einem der beiden Kerne, den anderen zu benutzen schützt also davor.

    Müsste ich jetzt herausfinden, auf welchem von beiden Kernen der WLAN Task läuft und dementsprechend den anderen nehmen oder weise ich auch dem WLAN Task einen bestimmten Kern zu?


    EDIT: WLAN soll bereits auf Core 0 gepinned sein und der Rest auf Core 1 laufen (https://esp32.com/viewtopic.php?t=5906#p27673). Habe heute auch noch gelernt, dass mein Vorhaben "Bitbanging" heißt und ich dazu noch viele weitere Anleitungen finden kann.


    EDIT 2: digitalWrite() scheint auch relativ langsam zu sein (https://www.peterbeard.co/blog…ino-digitalwrite-so-slow/)


    EDIT 3: Habe gerade wieder den Moment abgepasst, in dem scheinbar ein paar Bits verschluckt werden, wodurch das Paket wieder nicht richtig erkannt wird. Die pulse width-, gap width- und pulse period-Zeiten sind in diesen zwei Übertragungen exakt gleich, doch die Anzahl ist etwas unterschiedlich. Einmal angefangen hänge ich jetzt schon seit 5 Minuten in dieser Schleife, in der kein Paket mehr erkannt wird. Ein Neustart schafft da sofort Abhilfe. Bevor ich schwere Geschütze auffahre, gebe ich den Funktionen noInterrupts(); und interrupts(); nochmal eine Chance.



    EDIT 4: Habe alles auf Anfang gesetzt und dann konnte gar keine Temperatur mehr entschlüsselt werden. Kein. Einziges. Mal...

    Habe jetzt den Sender auf Pin 4 gesteckt und jetzt funktioniert der unveränderte (!) Code wieder. Sonst habe ich absolut nichts geändert.

    Kelvin

    Edited 4 times, last by Kelvin ().

  • Zu 3: Hat eventuell das Integrity : PARITY in Zeile 5 was zu bedeuten?


    Ich hatte mal am ESP8266 folgendes Problem: Wenn ich die Infos per MQTT verschickt habe, hat die Auswertung, die delayMicroseconds verwendet nicht funktioniert. Wenn ich allerdings die Daten per Webseite abgefragt habe, dann hat es geklappt. Nach langer suche bin ich auf etwas eigenartiges drauf gekommen: Ich hatte eine viel zu lange Wartezeit, sodass ich das Signal richtig auswerten hätte können. Zufällig scheint der Seitenaufruf einen Fehler im delayMicroseconds verursacht zu haben, sodass die Zeit viel kurzer war und es funktioniert hat.


    Was ich damit sagen möchte, es kann meiner Erfahrung nach (manchmal) passieren, dass irgendwas die Funktion delayMicroseconds "aushebelt". Ich habe das mit Hilfe meines Logikanalyzers herausgefunden. Du könntest versuchen die Zeiten in Variablen zu schreiben und nach dem Senden (damit dies keinen Einfluss hat) z.B. seriell auszugeben und zu analysieren....

    ...wenn Software nicht so hard-ware ;) ...

    Freue mich über jeden like :thumbup:

  • Kurzer Zwischenstand: Habe mit unterschiedlichen Tasks herumgespielt, doch dafür muss ich meine Funktionen ändern und da fehlt mir gerade das nötige C-Wissen (ist alles schon wieder eingestaubt). Ist aber auch noch nicht von allzu großer Bedeutung, weil WLAN bei mir schon auf Kern 0 läuft und meine Anwendung zum Senden auf Kern 1 - die Separierung ist also schon vorhanden.

    Was ich aber gemacht habe ist, dass das WLAN jetzt nur zum Holen der Temperatur eingeschaltet ist und seitdem kommt es seltener zu fehlerhaften Signalen.

    Kelvin

  • Habe den Code jetzt bei GitHub veröffentlicht: https://github.com/KLVN/Simulate433TemperatureSensor


    Edit: Bitte nicht den Programmier-Stil beachten 😅 Zum Testen und für die Leserlichkeit wollte ich es möglichst linear, von oben nach unten, strukturieren und nicht jede noch so kleine Aktion in eine eigene Funktion packen. Ich möchte es vermeiden, dass loop() nur aus einer Zeile verschachtelter Funktionen oder ständigen Zuweisungen besteht, z.B.

    Kelvin

    Edited once, last by Kelvin ().