home     Inhaltsverzeichnis
erste Version am 14.01.2017
letzte Änderung am 11.02.2017

Temperatur- und Helligkeits-Logger - Seite 5


LoggerView 1.0

Heute war ich fleißig. Das Programm zur Daten-Visualisierung funktioniert schon soweit, dass es zu den vier bisher existenten Sensoren die zugehörigen Kurven anzeigt.
LoggerView Screenshot

Es gibt zwei vertikale Skalen. Links die Temperatur, rechts die Helligkeit (von 0 bis 1023).

Zoomen und horizontal scrollen klappt im Ansatz auch schon.
Hier ein einzelner Tag:
erster Screenshot

Morgen sollte ich mir vielleicht mal Testdaten für ein ganzes Jahr generieren.
Derzeit wird stumpf der gesamte Inhalt der Datenbank in ein Python-Dictionary und ein NumPy-Array geladen.
Bisher habe ich die Daten von etwas über einer Woche und das flutscht noch.

Es wird wohl keinen Sinn machen, mehr als eine Woche gleichzeitig anzuzeigen, wenn man mal von einer maximalen Fensterbreite von 1600 Pixeln ausgeht.
Ein Tag hat 60*24=1.440 Minuten. Eine Woche bereits 10.080 Minuten.
Hier lägen bei 1600 Pixeln Breite also bereits 10.080/1.600=6,3 Minuten unter einem Pixel.
Möglicherweise könnte man mit slicing über das Array laufen und mittels mean einen einzelnen Wert pro darzustellendem Pixel rausholen.
Das ist sicher schneller, als dem wx.dc.DrawLines() die sechsfache Anzahl an zu verbindenden Punkten vorzusetzen.

Aber vielleicht kann ich mich stattdessen ja auch irgendwie dazu motivieren, endlich eine zweite Einheit zu bauen.....


LoggerView Performance

Nun habe ich meine Datenbank mal etwas aufgebläht, indem ich die Daten einer Woche 51 mal (mit geändertem Timestamp) dupliziert habe. Die Tabelle Dump enthält jetzt 1.043.628 Sätze, die gesamte Datenbank belegt 63MB auf der Festplatte.

Die Ausführungszeiten sehen folgendermaßen aus:
1
2017-01-15 14:32:17.693659
Startzeit des Programms
2
2017-01-15 14:32:18.397857
Der Connect zur Datenbank ist hergestellt
3
2017-01-15 14:32:23.510775
Die Tabelle Dump befindet sich in einem Python-Dictionary
4
2017-01-15 14:32:24.865189
Das NumPy-Array ist mit den Daten gefüllt
5
2017-01-15 14:32:27.372668
Es erscheint das folgende Fenster auf dem Bildschirm

Screenshot von einem Jahr

Ein horizontales Scrollen dauert bei dieser Ansicht ca. 2,5 Sekunden, das rein-zoomen in die Daten eines Monats ca. 0,2 Sekunden.
Nochmal zoomen auf vier Tage wieder ca. 0,2 Sekunden. Ab dann dauert ein Scroll-Schritt ca. 0,07 Sekunden - also quasi verzögerungsfrei.
Damit ist das deutlich besser, als befürchtet. Und bisher läuft noch alles auf einem Core.

Trotzdem wären 10 Sekunden deutlich zu lang, wenn die bei jedem Programmstart anfielen.
Das sollte sich aber einfach dadurch lösen lassen, dass erstmal nur z.B. die jüngsten zwei Wochen geladen und davon die jüngste Woche angezeigt wird.
In einem weiteren Prozess könnte danach angefangen werden, den Rest zu laden. Vielleicht jeweils einen Monat im Stück. Dafür wäre vorab zu testen, wie zwei Dictionaries möglichst schnell gejoint werden können.

Jedenfalls muss ich nicht den kompletten Ansatz verwerfen, weil er nicht hinreichend performen würde.

BTW: das oben angedachte slicing hatte ich schon vor dem Test mit den Daten eines Jahres eingebaut. Allerdings doch nicht mit slicing, sondern mit einer vorgeschalteten Loop, die alle die Y-Werte zusammenfasst, die auf einer X-Koordinate bzgl. Pixel landen werden.
Weiterhin habe ich noch einen Kurven-Cache eingebaut, der sich die jeweils letzte Kurve je Sensor und Anzeigebereich merkt und somit die Neuberechnung des Inputs für wx.dc.DrawLines() einspart, wenn nur ein Fenster-Refresh erfolgt.


eine nicht existente Stunde

Die Einträge in der Logdatei des SocketServers sahen gestern Abend folgendermaßen aus:
2017.01.20 19:00:00 GET_TIME (2017.01.20 19:00:00)
2017.01.20 19:00:00 ------------------------------------------------------------
2017.01.20 19:00:00 hostname   = Logger-01
2017.01.20 19:00:00 firmware   = 1.3
2017.01.20 19:00:00 last_reboot= 2017.01.11 18:55:29
2017.01.20 19:00:00 warn       = 0
2017.01.20 19:00:00 volts      = 5.16
2017.01.20 19:00:00 ------------------------------------------------------------
2017.01.20 19:00:00 Logger-01 s=1 - 2017.01.20 12:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:00 Logger-01 s=2 - 2017.01.20 12:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:00 Logger-01 s=1 - 2017.01.20 13:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:00 Logger-01 s=2 - 2017.01.20 13:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:00 Logger-01 s=1 - 2017.01.20 14:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=2 - 2017.01.20 14:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=1 - 2017.01.20 15:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=2 - 2017.01.20 15:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=1 - 2017.01.20 16:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=2 - 2017.01.20 16:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:01 Logger-01 s=1 - 2017.01.20 17:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=2 - 2017.01.20 17:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=1 - 2017.01.20 18:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=2 - 2017.01.20 18:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=1 - 2017.01.20 07:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=2 - 2017.01.20 07:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:02 Logger-01 s=1 - 2017.01.20 08:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:03 Logger-01 s=2 - 2017.01.20 08:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:03 Logger-01 s=1 - 2017.01.20 09:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:03 Logger-01 s=2 - 2017.01.20 09:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:03 Logger-01 s=1 - 2017.01.20 10:59 - ins= 1, dup= 0 (good= 1, bad= 0)
2017.01.20 19:00:03 Logger-01 s=2 - 2017.01.20 10:59 - ins= 1, dup= 0 (good= 1, bad= 0)
2017.01.20 19:00:03 Logger-01 s=1 - 2017.01.20 11:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:03 Logger-01 s=2 - 2017.01.20 11:00 - ins=60, dup= 0 (good=60, bad= 0)
2017.01.20 19:00:04 disconnect

Interessant sind die Einträge für die Zeit zwischen 10:00 bis 10:59 Uhr.
Das "(good= 1, bad= 0)" will heißen, dass für diesen Zeitraum nur ein Datensatz erzeugt wurde.
Es gibt in der Datenbank je Sensor einen Satz für "2017-01-20 09:59:00", der nächste Satz ist bereits für "2017-01-20 10:59:00".

Das einzig infrage kommende Stück Programmcode ist dieses hier:
#define SLEEP_MS  4864               // Anzahl Millisekunden für eine Tiefschlaf-Phase (4096+512+256)
[...]
    // sicher in die neue Minute eintreten
    t=getRTC();
    sc=(60-t.second)*1000l;                    // Millisekunden bis zur nächsten Minute
    if(t.minute==(sensorHourRAM_idx_g%60)) {   // wenn die neue Minute schon angefangen hat (der %60 macht aus 60 -> 0)
      cycles_for_full_minute_g=sc/SLEEP_MS-1;  // Anzahl der Tiefschlaf-Phasen bis zur nächsten Minute neu setzen
    } else {                                   // wenn noch alte Minute
      while(sc>SLEEP_MS) {                     // bis zur nächsten Minute schlafen
        deepSleep(SLEEP_MS-512);               // eine Tiefschlaf-Phase schlafen
        blinkAliveLED(4);                      // schläft nochmal 512ms
        sc-=SLEEP_MS;
      }
      deepSleep(sc+16);                        // restliche Zeit kleiner einer Tiefschlaf-Phase warten
      // Die +16 deswegen, weil deepSleep() bei Zeiten <16ms nicht wartet, die neue Minute aber
      // sicher angefangen haben soll.
      // Wurde "sc" z.B. eine Millisekunde vor der neuen Minute ermittelt, beträgt die Wartezeit
      // 1016ms. Somit ist die erste Sekunde der neuen Minute zwar bereits abgelaufen - aber das
      // stört nicht.
      // Andersrum, wenn "sc" eine Millisekunde nach Sekunden-Wechsel ermittelt würde, und
      // sc==1000 wäre, müsste deepSleep() mindestens 999 Millisekunden warten, damit die neue
      // Minute erreicht wäre. deepsleep() würde jedoch warten für: 512+256+128+64+32=992ms
      // Die neue Minute hätte hier also noch nicht angefangen.....
    }

    // ggf. in die neue Stunde eintreten
    if(set_timestamp_at_next_minute==true) {  // wenn eben das Ende einer Stunde verarbeitet wurde
      set_timestamp_at_next_minute=false;     // Kenner dafür zurücksetzen
      clearSensorHour();                      // RAM-Speicher sauber machen
      t=getRTC();                             // aktuelle Zeit holen
      for(i=0; i<sensor_count_g; i++) {
        sensorHourRAM_g[i].timestamp=t;       // aktuelle Zeit im ein-Stunden-Speicher setzen
      }
      minutes_to_full_hour_g=60-t.minute;     // Minuten bis zur nächsten Stunde einstellen
      sensorHourRAM_idx_g=t.minute;           // Start-Index für den ein-Stunden-Speicher setzen (0-59)
    }

Irgendwie hatte ich tatsächlich schon erwartet, dass es an dieser Stelle Probleme geben könnte.....
Angenommen, das Programm tritt erst um 10:00:00.00001 in den Abschnitt ein (warum auch immer). Dann wären t.second=0 und t.minute=0.
In sensorHourRAM_idx_g sollte eine 60 stehen. Damit würde cycles_for_full_minute_g auf (60-0)*1000/4864-1= 60000/4864-1=11 gesetzt werden und alles wäre gut.
Stünde sensorHourRAM_idx_g auf 59, würde die Schleife mit sc=60000 betreten und 60000/4864=12 mal durchlaufen werden. Kein Problem soweit.

Um 59 Minuten per deepSleep() zu warten, müsste die Funktion mit dem Wert 59*60*1000=3.540.000 aufgerufen werden.
Die Variable sc ist vom Typ long, somit vorzeichenbehaftet und kann Werte zwischen -2.147.483.648 und 2.147.483.647 aufnehmen.
Der Parameter von deepSleep() ist vom Typ int, ebenfalls vorzeichenbehaftet und kann damit nur Werte zwischen -32.768 und 32.767 annehmen.
Somit können die fehlenden 59 Minuten nicht über deepSleep() angefallen sein. Der erste deepSleep() wartet maximal 60000/4864=12 mal für 4864ms, der zweite deepSleep() kann maximal 32.767ms=33 Sekunden warten.
Die Uhr könnte Blödsinn liefern. Die Variable t.second hat sechs Bit, kann also Werte zwischen 0 und 63 annehmen. Bei t.second>60 würde sc negativ werden. Damit würde die while(sc>SLEEP_MS){}-Schleife nicht betreten werden. Der anschließend folgende deepSleep(sc+16) bekäme einen negativen sc vom Typ long übergeben. Egal wie das gecastet wird und bei deepSleep() als Parameter vom Typ int landet: mehr als 33 Sekunden sind nicht drin.
Im anderen Zweig wird cycles_for_full_minute_g gesetzt. cycles_for_full_minute_g ist vom Typ signed char - kann also Werte zwischen -128 und 127 annehmen. Auch damit ist es nicht möglich, 59 Minuten zu verpassen.

Denkbar wäre, dass der Fehler durch ein Überbleibsel aus der Zeit vor dem bad-Flag im Python-SocketServer entstanden ist. Dort wird der Timestamp aus dem Header für die Daten einer Stunde nämlich auch dazu verwendet, unbelegte Minuten auszublenden. Wird die Schaltung beispielsweise um 10:11:12 Uhr eingeschaltet, landet die 10:11 im Header des Stunden-Blocks im RAM. Anhand dieser Zeitangabe blendet der SocketServer später die Minuten 0 bis 10 dieser Stunde aus, um keine ungültigen Daten in der Datenbank landen zu lassen.
Käme jetzt hinzu, dass der erste Abschnitt des obigen Codeblocks vor Ende der Stunde verlassen würde, würde der zweite getRTC() sowas liefern wie 10:59:59 (jetzt wieder bezogen auf die verpassten 59 Minuten). Dies würde als Timestamp in den Header geschrieben werden, danach würde in minutes_to_full_hour_g eine 60-59=1 und in sensorHourRAM_idx_g eine 59 landen. Anschließend ginge es wieder oben in loop() los. Um ca. 11:00:04 Uhr würde die Stunde erneut umgeschaltet werden und ab dann alles normal weiterlaufen.
Somit ist das Verhalten vom Python-SocketServer nicht ursächlich Schuld an den fehlenden 59 Minuten. Die Funktion clearSensorHour() hätte ohnehin die Daten überschrieben.
Die notwendige Änderung muss einzig und allein in der Firmware der Schaltung erfolgen.
Aber was kann dazu führen, dass der erste Abschnitt zu früh verlassen wird....???
Wie an der Länge des Kommentars unschwer zu erkennen ist, habe ich mir seinerzeit durchaus einige Gedanken über diesen Codeabschnitt gemacht.

Nochmal überlegen....
Angenommen, die RTC wird exakt um 10:59:59.00000 abgefragt. sc wird zu (60-59)*1000=1000. t.minute ist 59, sensorHourRAM_idx_g ist bereits 60. Folglich wird der else-Zweig betreten und gleich wieder ohne Aktion verlassen, weil nicht gilt, dass sc>4864 ist. Dann wird deepSleep() mit sc+16=1016 aufgerufen. 1016 führt zu der Warte-Sequenz 512+256+128+64+32+16=1008 Millisekunden. Damit sollte die RTC nun eigentlich bereits 11:00:00 liefern.

Wird die RTC exakt um 10:59:55.00000 abgefragt, hat sc den Wert (60-55)*1000=5000. Wieder wird der else-Zweig betreten. Diesmal ist sc>4864, daher wird deepSleep() mit 4864-512=4352 aufgerufen. 4352 führt zur Warte-Sequenz 4096+256=4352ms. Danach wird in blinkAliveLED(4) nochmal 512ms plus 2x100µs LED-Leuchtdauer gewartet. In Summe also 4352+512=4864ms. Von sc wird 4864 subtrahiert. Macht 5000-4864=136. Somit wird deepSleep() nun mit 136+16=152 aufgerufen. 152 führt zu 128+16=144. Gesamtwartezeit also 4864+144=5008. Sollte auch langen.

Es kann also eigentlich nur an einer falschen Wartezeit von sleep_PWR_DOWN() liegen. Zur relevanten Zeit war es im Wohnzimmer durchschnittlich warm. Sowohl um 09:59:00, wie auch um 10:59:00 Uhr wurden 22.6°C gemeldet. Mit abendlich befeuertem Ofen klettert der Höchstwert (seit dem 07.01.) schon mal auf 27.1°C. Bei hoher Temperatur werden die Tiefschlafphasen jedoch länger.
Tiefschlafphasen, die kürzer als geplant sind, würden dazu führen, dass der Codeabschnitt für den Stundenwechsel bereits betreten wird, wenn die neue Stunde real noch gar nicht begonnen hat. Das würde genau zu dem Verhalten mit den 59 fehlenden Minuten passen.

Das sicherste wäre es, nach betreten des Stundenwechsel-Codeabschnittes solange über den getRTC() zu iterieren, bis der Stundenwechsel tatsächlich eingetreten ist. Andererseits kostet jedes Einschalten der RTC ja Akkuleistung....
Oder ganz stumpf zusätzliche 1024ms Wartezeit einbauen. Dann macht der obere Codeabschnitt jedoch keinen Sinn mehr. Warum erst möglichst genau rantasten, um dann doch die erste Sekunde der neuen Stunde zu verpennen.

Am einfachsten wäre es, vor dem while(sc>SLEEP_MS) einen sc+=SLEEP_MS einzubauen und den deepSleep(sc+16) rauszuschmeißen. Dadurch würde der cycles_for_full_minute_g jetzt aber nicht mehr stimmen. Und zwar egal, ob der Minutenwechsel nur ein einfacher Minutenwechsel oder auch gleichzeitig ein Stundenwechsel gewesen ist.
Also bräuchte es nach dem Minutenwechsel noch eine Sequenz aus
t=getRTC();
sc=(60-t.second)*1000l;
cycles_for_full_minute_g=sc/SLEEP_MS;
um das abzufangen. Das hieße aber, die RTC zweimal pro Minute einschalten zu müssen. Das will ich nicht.

Bei einem einfachen Minutenwechsel stört es nicht sonderlich, wenn die neue Minute real noch nicht begonnen hat. Der erste Messwert würde zwar in den letzten Millisekunden der vorigen Minute erfasst werden, nach der folgenden Tiefschlafphase würde aber wieder alles passen.

Nur bei der Verarbeitung eines Stundenwechsels wäre es fatal, wenn die RTC noch die alte Stunde liefern würde.
Weil die RTC dort ohnehin nochmal eingeschaltet wird, würde es keine zusätzliche Akkuleistung kosten, cycles_for_full_minute_g neu zu setzen, nachdem z.B. eine zusätzliche Tiefschlafphase lang gewartet wurde.
Damit sähe der Stundenwechsel-Codeblock dann so aus:
    // ggf. in die neue Stunde eintreten
    if(set_timestamp_at_next_minute==true) {  // wenn eben das Ende einer Stunde verarbeitet wurde
      set_timestamp_at_next_minute=false;     // Kenner dafür zurücksetzen
      deepSleep(SLEEP_MS-512);                // v1.4: vorsichtshalber noch eine Tiefschlaf-Phase schlafen
      blinkAliveLED(4);                       // schläft nochmal 512ms
      clearSensorHour();                      // RAM-Speicher sauber machen
      t=getRTC();                             // aktuelle Zeit holen
      for(i=0; i<sensor_count_g; i++) {
        sensorHourRAM_g[i].timestamp=t;       // aktuelle Zeit im ein-Stunden-Speicher setzen
      }
      sc=(60-t.second)*1000l;                 // v1.4: Millisekunden bis zur nächsten Minute
      cycles_for_full_minute_g=sc/SLEEP_MS-1; // v1.4: Anzahl der Tiefschlaf-Phasen bis zur nächsten Minute neu setzen
      minutes_to_full_hour_g=60-t.minute;     // Minuten bis zur nächsten Stunde einstellen
      sensorHourRAM_idx_g=t.minute;           // Start-Index für den ein-Stunden-Speicher setzen (0-59)
    }


LoggerView ist quasi fertig

Die angedachten Änderungen sind eingebaut. Statt der Multiprocessing-Library habe ich mich für die Multithreading-Library entschieden. Schließlich handelt es sich ja primär um Speicher-IO, der den Programmstart verzögern würde. Und nebenbei ist ein Thread deutlich komfortabler in der Nutzung, wenn größere Datenmengen zurück ans Hauptprogramm übergeben werden müssen.
Damit sehen die Zeiten jetzt folgendermaßen aus, wenn die o.g. Datenbank mit 1.043.628 Sätzen angeklemmt wird:
programm gestartet 2017-01-21 16:06:40.424643
vor DB-connect 2017-01-21 16:06:40.450928
vor numpy create-array
2017-01-21 16:06:41.289372
nach numpy, vor load
2017-01-21 16:06:41.289441
7 tage sind geladen
2017-01-21 16:06:41.552503
7 tage sind einsortiert
2017-01-21 16:06:41.573955
vor paint
2017-01-21 16:06:42.359998
nach paint 2017-01-21 16:06:42.377069
alles ist geladen
2017-01-21 16:06:47.725601
alles ist einsortiert
2017-01-21 16:06:48.946841

Also knapp zwei Sekunden, bis das Fenster dargestellt ist und sich bereits vollständig bedienen lässt. Weitere sieben Sekunden später sind alle Daten verfügbar.

Der Umbau auf asynchrones nachladen der Daten war mit relativ wenigen Änderungen verbunden. Eine Änderung war leider nötig, auf die ich gerne verzichtet hätte. Bei der alten Version wurden nur existente Daten im NumPy-Array abgelegt. Somit konnte das NumPy-Array nicht mehr Zeilen haben, als die Datenbank Sätze in der Tabelle Dump. Jetzt wird das NumPy-Array anhand der Timestamp-Grenzen aus der Datenbank dimensioniert. Solange von allen Sensoren kontinuierlich Daten kommen, macht das keinen Unterschied. Hätte ich aber -warum auch immer- eine längere zeitliche Lücke in den Daten, würde auch dafür Speicherplatz im NumPy-Array reserviert sein.
Aber was solls....der Rechner hat 32GB RAM-Speicher und die Datenbank mit Daten von einem kompletten Jahr bewegt sich im zweistelligen Megabyte-Bereich. Selbst wenn ich endlich mal wieder Lust auf löten haben sollte und die vorgesehenen drei weiteren Einheiten mit je einem Sensor-Paar täglich Daten liefern, wird es noch viele Jahre dauern, bis die Datenbank-Größe in einen kritischen Bereich käme. Außerdem hatte ich eh überlegt, jedes Jahr eine neue Datenbank-Datei anzulegen.

Für die bisher real angefallenen Daten sieht es so aus, wenn nur die Temperatur-Kurven angeschaltet sind (anklicken für volle Größe):
Screenshot nur Temperatur

Das Programm hat ein Kontextmenü bekommen und es gibt ein nicht-modales Fenster, in dem die Kurven an- oder abgeschaltet werden können. Ebenso sind die Farben darin änderbar.

Screenshot Kontextmenue        Screenshot Dialogfenster

Die Funktion "springe zu Datum" ist noch nicht implementiert. Ich werde erstmal abwarten, ob und wie so eine Funktion sinnvoll zu gebrauchen wäre. Das geht aber erst, wenn ich auch sinnvolle Daten von einem größeren Zeitbereich in der Datenbank habe....um dann sehen zu können, wie ich darin navigieren [können] möchte.

Anbei der aktuelle Stand von Firmware und LoggerView. Der Python-SocketServer ist unverändert.
LoggerView braucht:
Library
derzeitige Version in den openSUSE13.2-Repos
wxWidgets python-wxWidgets-2.8.12.1-10.4.1.x86_64
NumPy python-numpy-1.9.0-1.6.x86_64
SQLite3 sqlite3-3.8.6-3.1.x86_64


der Bau weiterer Schaltungen

Jetzt ist alles soweit fertig und ich müsste eigentlich nur noch [mindestens] drei weitere Schaltungen mit je einem Sensor-Paar bauen (für Büro, Küche und Schlafzimmer).
Zwei RTC+EEPROM-Module hätte ich noch liegen, die Bestellung für die nächsten fünf Module ist raus. Zustellung erwartet am 03.02.2017.
Nebenbei habe ich noch drei dieser ESP8266-12-Module bestellt. Und einmal das hier, das nicht erst aus China rangekarrt werden muss bzw. im Laufe der kommenden Woche geliefert werden soll.
Diese ESP's haben USB onboard und sind für die Entwicklung gedacht. Zwecks Einsatz über Batterie habe ich dazu noch ein Fünferset ESP8266-12 ohne USB bestellt.
Mal sehen, wie es damit klappt. Digitale Ports sind reichlich drauf, der eine analog-Port langt für einen LDR.
Der entscheidende Vorteil an den Dingern wäre, dass ich nur das RTC+EEPROM-Modul, zwei Taster, eine LED und die beiden Sensoren samt 3V-Akkupack zusammenschalten müsste.

Daher warte ich mit dem Bau der nächsten Schaltungen (und Gehäuse) erstmal.... bis ich die ESP-12-Module so schön und komfortabel programmieren kann, wie die ArduinoUNOs bzw. ATmega328.
Die Erfahrungen damit werden in einem eigenen Bereich auf der Homepage landen.


eine kleine Korrektur

Bisher hat LoggerView ein Dictionary mit allen Timestamp-Werten erstellt und dazu genutzt, im NumPy-Array die Zeilen auszublenden, in denen keine Daten geladen waren.
Der Inhalt des Dictionaries kam aus folgendem Query: SELECT DISTINCT timestamp FROM Dump.
Also ohne Berücksichtigung der Spalte location und somit des Sensor-Paares.
Das klappt damit auch solange, wie alle Spalten zu einem Timestamp entweder immer komplett belegt oder komplett nicht belegt sind. Sobald es eine zweite Schaltung gäbe, wäre das aber nicht mehr der Fall. Es gäbe dann ein drittes Sensor-Paar und damit gäbe es pro Timestamp drei statt zwei Sätze in der Datenbank - oder auch sechs statt vier Spalten mit Messwerten im NumPy-Array.
Im NumPy-Array befänden sich also Zeilen, bei dem vier Spalten Daten enthielten. Ab dem Timestamp, zu dem das dritte Sensor-Paar ebenfalls Daten liefern würde, enthielten alle sechs Spalten Daten.
Das NumPy-Array wird mit dem Wert 0 initialisiert. Dementsprechend würde für das dritte Sensor-Paar vor seiner Existenz konstant eine Temperatur von 0°C bei völliger Dunkelheit angezeigt werden.
Das hört sich ja geradezu biblisch an....ist aber natürlich unerwünscht.
Folglich braucht es einen Validitäts-Indikator pro Sensor-Paar.
Dieser ist jetzt ebenfalls als NumPy-Array vom Typ boolean implementiert.


Nebenbei konnte durch einen zusätzlichen Index auf der Datenbank-Spalte Dump.timestamp die Laufzeit der Sequenz der Funktionen getColCount(), getRowCount() und getMinMaxTimestamp() von 0,83 Sekunden auf 0,21 Sekunden reduziert werden (beim Laden der o.g. Datenbank mit 1.043.628 Sätzen).

Weiterhin habe ich eingebaut, dass mit dem Mausrad bei gedrückt gehaltener Strg-Taste jetzt auch vertikal gescrollt werden kann.

Mit dem aktualisierten Stand der Programme warte ich noch.... vielleicht finde ich ja noch mehr.
Auch wenn es noch nicht final ist: hier schon mal LoggerViewV1.2.

Und heute LoggerViewV1.3 mit folgenden Erweiterungen:
Eine neuere Version gibt es hier.