Tchibo Wetterstation

Aus Hobbyelektronik.org
Version vom 11. Mai 2012, 20:59 Uhr von Chris (Diskussion | Beiträge) (Jetzt wird es mir langsam zu kalt draußen (und die Mücken zu bissig) - gleich geht's drinnen weiter ;))
Tchibo Wetterstation

Letztes Jahr gab es von meiner Schwester eine Funk-Wetterstation (TCM 279340) zu Weihnachten. Ich hatte zwar schon eine etwa gleichwertige, aber man will sich Neuerungen natürlich nicht querstellen.

Interessant an dem Teil war auf dem ersten Blick das Farbdisplay, das sich nach näherer Betrachtung als Farbfolie auf einem Schwarz-Weiß-LCD herausstellte. Diese Effekthascherei sieht ehrlich gesagt nicht einmal allzu schlecht aus, obwohl sie durch den massiv niedrigeren Kontrast eigentlich nur Nachteile bringt.

Aber bekanntlich zählen ja die inneren Werte; und die sind trotz der Preisklasse (und der angesetzten Zielgruppe) nicht allzu schlecht.

Aufgrund von Prüfungsvorbereitungen blieb das Teil relativ lange unbehelligt, bis mich endgültig die Neugierige weckte:

Hardware

Die Wetterstation selbst ist mit Thermo- und Hygrometer, sowie einem Barometer ausgestattet, dass auf dem Display lediglich eine Tendenz anzeigt. Neben dem 433MHz-Empfänger ist zugleich ein Zeitzeichenempfänger (DCF77) verbaut, der kein Meteotime empfängt. Da war wohl die Lizenz teurer als das Barometer ;-)

Im "Außenfühler" sitzt wiederum ein Thermo- und Hygrometer. Neben Kanalwahlschalter und Tx-Knopf war's das auch schon wieder. Ok, Batterien frisst das Teil auch noch.

Wie beim EMR7370: Am Anfang war der Schraubendreher. Das Innenleben der Station ist relativ modular aufgebaut. Jeweils eine eigene Platine für DCF77, 433MHz, Barometer und Hygrometer. Auf der Hauptplatine ist dann der Rest. Dadurch war es mehr als einfach, das Signal der Begierde abzufangen. Wenn man schon mal drin ist, habe ich mir auch gleich die Datenleitungen des Barometers herausgeschleift - man weiß ja nie.

Protokoll

Im zweiten Schritt kommt der Logic Analyzer ins Spiel:

Interessant bis ~130ms

Wie man sieht, sind die Pulsweiten (low-Zeiten) nach jedem Flankenwechsel unterschiedlich - darin muss also die Information stecken.

Um Muster besser zu erkennen schnappte ich mir die Daten, importierte sie in Excel und ließ sie mir visualisieren.

Dazu habe ich einfach die Zeitdifferenzen zwischen den einzelnen Zeilen ausgeben lassen und das Ergebnis durch bedingte Formatierung hinterlegt. Zusätzlich habe ich alle "1-Zeiten" herausgefiltert, da dort augenscheinlich keine Informationen übertragen werden

wie man im zweiten Bild deutlich sieht, gibt es Synchronisation und Daten. Dabei ist eine Pause (bzw. Sendepuls, so genau weiß ich es nicht) von 8,9ms das Synchronisationswort und knapp 2 bzw. 4ms eine 1 bzw. 0. Insgesamt werden die Daten 8 mal übertragen - ist zwar nicht gerade edel, aber es funktioniert. Das Wissen über Kanalkodierung, Codeverkettung und Faltungscodes ist bei den Herstellern von solchem Zeug wohl noch nicht angekommen. Hat immerhin den Vorteil, dass es Leute wie ich nicht zu schwer haben.

Bleibt nur noch die Frage der Zuordnung. Was ist eine 1 und was eine 0 - und vor allem: wo steht was? Sicher ist zumindest, dass 36 Bit übertragen werden.

Datenfarm

Nachdem ich über mehrere Abende hinweg sporadisch Werte aufzeichnete (und erzeugte) bekam ich schließlich knapp 50 Datensätze, die ausgewertet werden wollten.

Um Copy & Paste-Orgien in Excel zu ersparen, bastelte ich mir ein kleines PHP-Script, das die Zeitstempel in Binärdaten umwandelt:

function timing2data($filename) {
	$fh = fopen($filename, "r");
	$lasttime = -1;
	$data = array();
	while($line = fgets($fh)) {
		$time = explode(", ", $line);
		$time = floatval($time[0]);
		
		if($lasttime !== -1) {
			$val = round(($time - $lasttime) * 1000);
			switch($val) {
			case 9: echo "\n"; break;
			case 0: echo ""; break;
			case 2: echo "0"; break;
			case 4: echo "1"; break;
			default: echo "<".$val.">"; break;
			}
		}
		$lasttime = $time;
	}
	fclose($fh);
}

Hier habe ich auch die Annahme getroffen, dass ein kurzer Puls 0 und ein langer 1 entspricht (ich sollte richtig liegen).

Die Ausgabe sieht dann zum Beispiel wie folgt aus:

000001010000111001111111100100010000
0000010100001110011111<673><19>

die Werte in den spitzen Klammern sind Pausenzeiten, die nicht als 1, 0 oder Sync erkannt wurden und somit Übertragungsfehler darstellen.

Bei der Auswertung fanden sich dann auch einige Datensätze, bei denen die Auswertung nicht mehr allzu zuverlässig war - also gerade so zwei Übereinstimmungen in den Datenworten. Aber es hat gereicht.

In Excel ging dann die Suche nach der Antwort los.

Beispieldaten

Temperatur Luftfeuchtigkeit 0 1 2 3 4 5 6 7 8
24,1°C 43% 0000 0101 0010 1000 1111 0000 1100 0010 0101
24,6°C 45% 0000 0101 0010 0110 1111 0000 1010 0010 1100
24,8°C 49% 0000 0101 0100 0001 1111 0000 1001 0010 1111
24,9°C 49% 0000 0101 0100 1001 1111 0000 1001 0010 0111
24,7°C 50% 0000 0101 0100 1110 1111 0000 0000 1010 0001
24,8°C 50% 0000 0101 0100 0001 1111 0000 0000 1010 1110
24,6°C 51% 0000 0101 0100 0110 1111 0000 1000 1010 0001
15,7°C 52% 0000 0101 0100 1011 1001 0000 0100 1010 0110
15,9°C 52% 0000 0101 0100 1111 1001 0000 0100 1010 0010
16,1°C 52% 0000 0101 0100 1000 0101 0000 0100 1010 1000
16,3°C 52% 0000 0101 0100 1100 0101 0000 0100 1010 1111
14,9°C 53% 0000 0101 0100 1010 1001 0000 1100 1010 1011
15,1°C 53% 0000 0101 0100 1110 1001 0000 1100 1010 1101
15,5°C 53% 0000 0101 0100 1101 1001 0000 1100 1010 1110
11,7°C 62% 0000 0101 0100 1010 1110 0000 0100 0110 1111
11,8°C 61% 0000 0101 0100 0110 1110 0000 1000 0110 1111
11,9°C 61% 0000 0101 0100 1110 1110 0000 1000 0110 0111
-0,1°C 89% 0000 0101 0000 1111 1111 1111 1001 0001 1110
-0,2°C 89% 0000 0101 0000 0111 1111 1111 1001 0001 0001
-2,5°C 89% 0000 0101 0000 1110 0111 1111 1001 0001 0000

"Immer" gleich

"Sie kam aus einem kleineren Ort, wo das Motto galt: 'Wenn du nicht weiter weißt, schlachte ein Schwein'" (na, welcher Film war's?)

Wie dem auch sei, zu erst einmal kann man die Bits ausschließen, bei denen nichts passiert. Bei meinen Messungen waren es Nibble 0 und 1.

Luftfeuchtigkeit

Der Einfachheit halber kommt als erstes die Luftfeuchtigkeit dran. Dazu wurde die Excel-Tabelle nach dieser Spalte sortiert Da in einigen Datensätzen die Luftfeuchtigkeit konstant, die Temperatur jedoch unterschiedlich ist, kann man mit relativ wenig Aufwand die zuständigen Bits ermitteln.

Dabei hilft es ungemein, die Bits in Gruppen zu unterteilen.

Da sich 36 Bits gut durch 4 teilen lassen, habe ich diese Aufteilung gewählt. Excel-Formeln können beim Auseinanderpflücken der Datenworte enorm helfen (siehe Excel-Sheet). Die entsprechenden Spalten waren schnell gefunden: Nibble 6 und 7.

Zum Dekodieren ist es am einfachsten, ähnliche Werte miteinander zu vergleichen. So sieht man z. B. bei den Werten 50% und 51% dass sich bei 51% das höchstwertigste Bit in Nibble 6 ändert. Bei 52% ist nur das zweithöchste Bit aktiv. Ein Muster?!

Hilfreich ist hier die Exce-Funktion BININDEZ(), die Binär- in Dezimalwerte umrechnet. Da sich hier aber die höherwertigen Bits "früher" ändern als die niederwertigen ist anzunehmen, dass das Bitmuster verdreht ist. Eine Funktion zum Umkehren von Strings gibt es in Excel anscheinend nicht, allerdings kann man in den Formeln selbstdefinierte Funktionen verwenden.

Diese ist in VBA schnell geschrieben:

Public Function Reverse(str as String) As String
Reverse = StrReverse(str)
End Function

BININDEZ(Reverse(<Zelle>)) angewendet auf Nibble 6 gibt die Einerstellen aus, Nibble 7 entspricht den Zehnerstellen. Volltreffer!

Temperatur

Geht man wie bei der Luftfeuchtigkeit vor und sucht Werte mit der kleinstmöglichen Änderung, landet man sehr schnell bei 11,7, 11,8 und 11,9°C. Da Nibble 0, 1, 6 sowie 7 wegfallen, bleiben nur noch Nibble 3. Nibble 8 ist zwar bei 11,8 und 11,9°C unterschiedlich, bei 11,7 und 11,8°C gleich.

Wendet man wieder die Excel-Formel von oben an, kommt man in vielen Fällen auf die richtige Nachkommastelle.

Für eine vollständige Temperatur ist das natürlich noch nicht genug. Wandelt man Nibble 4 auf die gleiche Weise um, und addiert es um 4 Bit verschoben (bzw. mit 16 multipliziert) mit Nibble 3, hat man einen Temperaturbereich von 25,5°C eingeschlossen. Mit Nibble 5 Wächst der Informationsgehalt auf 12 Bit, also auf 212=4096, was 409,5°C (unter Beachtung von 0°C) entsprechen würde. Das ist natürlich Blödsinn - eher schmilzt der Sensor ;-)

Der Grund hierfür ist bei den negativen Temperaturen zu finden (die die Anzahl der Bits nochmals bestätigen), die im Zweierkomplement übertragen werden. Dadurch verliert man ein Bit an das Vorzeichen, was immerhin noch Temperaturen von -104,8 bis 204,7°C ermöglichen würde. Der Grund für diesen weiten Anzeigebereich dürfte sein, dass das Rechnen mit 4 Bit einfacher ist, als etwas krummes zu verwenden (aber nur eine Annahme).

Schalter

Der Sender hat, wie oben im Bild gezeigt, einen Kanalwahlschalter. Dieser Kanal müsste also auch bei der Übertragung mitgesendet werden.

Hier ist die Ermittlung denkbar einfach: Umschalten und warten, bis Pakete eintreffen. Diese Information ist in den niederwertigsten zwei Bits (bei Beachtung des "Bitreversals") von Nibble 2. Etwas verwirrend ist hierbei, dass die Bits widerum vertauscht sind.

Synchronisierung

Beim Herumspielen ist mir aufgefallen, dass das manuelle Synchronisieren von Sender und Empfänger zu einem Piepsen der Wetterstation führt. Also muss dies auch übertragen werden.

Gleiches wie bei der Schalterstellung: Daten einfangen und vergleichen. Die Information steckt im 3. Bit von Nibble 2 (ebenfalls nach Bitreversal).

Low Battery

Wenn man schräg auf das Display der Wetterstation schaut, sieht man ein Low-Battery-Symbol neben der Anzeige der Außentemperatur.

Auch diesen Zustand kann man sehr einfach erreichen, indem man die Batterien durch ein Labornetzteil ersetzt. Nach Herunterdrehen des Potis erschien tatsächlich das Symbol im Display. Der Logic Analyzer sagt: Nibble 3, niederwertigstes Bit (muss ich Bitreversal noch erwähnen?).

Rest

Damit wären eigentlich alle nötigen Informationen entschlüsselt. Es bleiben "nur noch" 12 Bit übrig. Wegen der Anordnung vermute ich hinter dem letzten Nibble eine Prüfsumme. Da sich das erste Nibble nur nach Batteriewechsel änderte, gehe ich davon aus, dass es sich um eine Zufällige Bitfolge zur Erweiterung des Addressraum handelt. Die restlichen 4 Bit sind mir bis dato noch gänzlich unbekannt.

Prüfsumme

Ich mag sie (zumindest bei Reverse Engineering) einfach nicht. Hauptgrund ist, dass es schlichtweg beliebig viele Verfahren gibt, diese zu berechnen. Egal ob CRC, XOR oder eine einfache Summe wie beim ECL-Bus-Decoder - es ist einfach undankbar, diese zu rekonstruieren.

Aber genug gejammert, Sicherheit kostet nunmal Aufwand.

Am Anfang habe ich noch per Bruteforce versucht, die Prüfsumme zu ermitteln, indem ich einfach alle Werte ver-CRC-t, mit XOR verprügelt und addiert habe. Das führte nicht unbedingt zum erwünschten Erfolg - wobei ich dazu sagen muss, dass ich dort auch noch mit der Datenfolge in Reinform (also ohne das umdrehen der 4-Bit-Gruppen) gearbeitet habe.

Im zweiten Versuch ging ich wieder über Excel (diese Programm wird man einfach nicht los) - dieses mal mit umgedrehten 4er-Gruppen. Dieses Mal war auch der Ansatz etwas anders: ich suchte mir die Datenpakete mit dem geringsten Hammingabstand - also jene Datenpakete, die möglichst wenige unterschiedliche Bits haben - heraus, was mehr Erfolg versprach.

In der Excel-Datei (Name) habe ich meine "Forschungsergebnisse" zusammengefasst. Es handelt sich tatsächlich nur um eine Quersumme der Nibbles 0 bis 7, die im Modulo 16 mit Nibble 8 ver-XOR-t werden. Kommt 15 heraus, ist alles ok, bei jedem anderen Wert hat das Paket was abbekommen.

Mit vorletztem Satz ist der Pseudo-Code

summe := Summe(nibble[0 .. 7]) Binär_AND 0x0F
WENN (summe Binär_XOR nibble[8]) = 0x0F
DANN
  "Paket ok"
ANDERNFALLS
  "Paket nicht ok"
ENDE WENN

gemeint, falls das besser verständlich ist.

Software

Um weder Logic-Analyzer noch PC für die Erfassung der Außentemperatur und Luftfeuchte zu bemühen, soll der Code wieder auf einen Mikrocontroller. Aus gewohnheit kommt wieder ein AVR ATmega8 zum Einsatz. Zur bequemen Auswertung habe ich dieses mal den ICP (Input Compare Pin) bemüht. Da ich bis jetzt keine größere Erfahrung mit dem Komparator hatte, inspirierte ich mich ein wenig an der Atmel AppNote AVR135. Diese beschreibt die Auswertung des Duty-Cylces eines PWM-Signals, also genau das, was man für den Empfänger braucht.

Um einen ungefähren Ausgangswert zu haben, eine kleine Rechnung: das kürzeste Signal hat eine Pulsweite von etwa 2ms, das längste 9ms. Diese müssen also zuverlässig erkannt werden. Der AVR läuft mit 12MHz (der Quarz steckt noch vom EMR7370 im Breadboard), wodurch der AVR eine Zyklusdauer von 83,33ns entspricht. 8ms enstprechen genau 96000 Taktzyklen - etwas viel für den 16-Bit-Timer. Mit dem großzügigen Vorteiler 1024 kommt man auf knapp 94. Bleib noch die Frage, ob die 2 und 4ms sauber unterschieden werden können. 2ms entspricht 23 Timerzyklen, 4ms 47 - also durchaus brauchbare Werte.

Zur Bestätigung dieser Theorie baute ich die AppNote so um, dass sie mir die Zählerstände für die jeweiligen High-Zyklen ausgab - mit durchschlagendem Erfolg. Zwar wackelten die Werte ein wenig, das aber im mehr als vertretbaren Rahmen.

Zur Auswertung wird eine kleine Statemachine verwendet, was sich schlimmer anhört, als es eigentlich ist. In einer Variable wird der Zustand des Empfangs geschrieben: Warten auf Paket, Synchronisiert + Datenempfang, Fertig. Wird ein Paket empfangen, werden die Bits in ein Array geschrieben und ein Zähler hochgezählt. Ist dieser bei 36 (der Anzahl der Bits) angelangt, geht der Empfänger in den Zustand Fertig über und keine weiteren Bits werden eingelesen. Ist die Auswertung abgeschlossen, geht der Zustand in "Warten" zurück. Wird eine Pulslänge empfangen, die weder einer 1, 0 noch Sync entspricht, wird der Automat ebenfalls in Warten zurückgesetzt, wodurch die zuletzt empfangenen Bits verworfen werden.

Der Trick mit den Bits

Zunächst habe ich Bits einfach wie sie gekommen sind in den Array geschrieben und erst zur Auswertung in die richtige Reihenfolge gebracht. Das kostet Zeit und Speicher, was sich durch geschickten Code minimieren lässt.

Schaut man sich das Bitreversal genauer an,

Bit mit Nr. (Binär) soll in Spalte (Binär) 0 0000 4 0100
1 0001 5 0101
2 0010 6 0110
3 0011 7 0111
4 0100 0 0000
5 0101 1 0001
6 0110 2 0010
7 0111 3 0011

sieht man, dass man für die Zuordnung eigentlich nur Bit 3 kippen muss. Alle Bits ab Bit 4 kann man für die Zuordnung der Bytes verwenden, was im Quellcode wie folgt aussieht:

if(bit == TCM_BIT_1) {
	uint8_t bytepos = tcm_pos >> 3;
	uint8_t bitpos = (tcm_pos ^ 4) & 7;
	tcm_bits[bytepos] |= 1<<bitpos;
}

Das spart wenn ich mich recht erinnere knapp 40 Byte und wahrscheinlich einige Zyklen in der CPU.