Idee: Die Ziffern und Rädchen des Wasserzählers über eine Text-/Mustererkennung auslesen und bereitstellen.
Vorteil: Auslesen des definierten Wertes des Wasserzählers mit geringen Kosten
Nachteil: Für die Erkennung benötigt der ESP32 ~3 Minuten Rechenzeit, sodass die Werte nicht kontinuierlich, sondern max. im 3-Minuten-Rhytmus bereitgestellt werden
Folgende Hardware (Gesamtkosten ~15€) wird benötigt:
Die Idee und Software stammt von Jomjol - die Software und eine Anleitung sind hier hinterlegt: https://github.com/jomjol/AI-on-the-edge-device/wiki
Hier der Code für die vzlogger.conf zum Abholen und Verknüpfen der Daten:
{ "enabled" : true, "interval" : 180, "channels" : [{ "uuid": "UUID_FRONTEND", "middleware" : "http://localhost/middleware.php", "identifier" : "" }], "protocol" : "exec", "command" : "curl -s http://IP-ADDRESS_ESP/value.html" }
Folgende Parameter müssen im Code ersetzt werden:
Der Parameter 'interval' ist auf 180s / 3min gesetzt und kann auf die jeweilige Aktualisierungszeit des ESP32 angepasst werden.
Idee: Das kleine Sternrad des Wasserzählers mit einem Laser anvisieren und die Reflexionen mit Photodiode und Arduino-Mikrocontroller auswerten.
Den Laser gibt es im Zehnerpack bei Ebay ab 2,50: http://www.ebay.de/sch/i.html?_odkw=laser+10+st%C3%BCck&_osacat=0&_from=R40&_trksid=p2045573.m570.l1313.TR0.TRC0.H0.Xlaser+10+st%C3%BCck+5mW+Dioden.TRS0&_nkw=laser+10+st%C3%BCck+5mW+Dioden&_sacat=0
Das ganze habe ich mit einer BPW34 http://www.vishay.com/docs/81521/bpw34.pdf Photodiode gekoppelt. Als Schaltung habe ich das hier gewählt:
https://www.mikrocontroller.net/articles/Lichtsensor_/_Helligkeitssensor#Konstantstromquelle_mit_Arbeitswiderstand (Konstantstromquelle mit Arbeitswiderstand)
Halterung ist ein abgesägtes Stück graues PVC-Rohr. Abgesägt ist das Stück, wo normalerweise die Dichtung drin ist. Darauf ist ein Aluminium Flachprofil geschraubt.
So sieht es aus, wenn es fertig ist:
Neben dem Wasserzähler greife ich auch den Gaszähler ab. Auf dem letzten Rädchen ist hier ein silber beschichtetets Stück, das hervoragend reflektiert. Hier musste ich nicht den Aufwand mit dem Laser machen, sondern nutze hier dieses Bauteil http://www.vishay.com/docs/83760/tcrt5000.pdf Die Reflexlichtschranke ist auf einen Einkaufswagenchip geklebt. Dieser ist mit wiederlösbarem Montageband (Aldi, bzw. 3M) auf den Gaszähler geklebt.
Beide Zähler gehen an einen Analog in eines Arduino V3 (Ebay, deutlich unter 10 Euro) http://www.ebay.de/sch/i.html?_odkw=nano+arduino&_osacat=0&_from=R40&_trksid=p2045573.m570.l1313.TR10.TRC1.A0.H0.Xnano+arduino+v3.TRS0&_nkw=nano+arduino+v3&_sacat=0
Es empfiehlt sich, das Analogsignal, bzw. die Variablen des Arduino-Codes, im Testaufbau und während der Justierung von Laser- und Photodiode am Zähler zu beobachten um gut auswertbare Veränderungen herzustellen. Dazu eignet sich z.B. der „Serial Plotter“ der Arduino-Entwicklungsumgebung: Baud-Rate hoch stellen und in der Schleife die Variable (eine oder auch mehrere in einer Zeile) einfach ausgeben.
Für Gas- und Wasserzähler, Eintrag per php direkt in die Datenbank.
Das ist der Code auf dem Arduino. Die Idee ist die Differenz zwischen dem Tal und dem Berg bei dem analogen Sensor als Trigger zu verwenden
//sets ground pin to LOW and input pin to HIGH void setup() { Serial.begin(38400); //digitalWrite(A0, HIGH); digitalWrite(A1, HIGH); } long wasser_counter = 0; long gas_counter = 0; long loops = 0; int wasser_max = 0; int gas_max = 0; long wasser = 0; long gas = 0; int wasser_z = 0; int gas_z = 0; unsigned long lastlog = 0; unsigned long last_gas = 0; unsigned long last_wasser = 0; unsigned long time; void loop() { // Gleitender Durchschnitt anwenden. gas = ((gas *9) + analogRead(1)) /10; wasser = ((wasser *3) + analogRead(0))/4; time = millis(); if (time < lastlog ){ //time ist übergelaufen lastlog = 0; last_gas= 0; last_wasser = 0; } wasser_max = max(wasser_max, wasser ); gas_max = max(gas_max, gas ); loops++; if ((wasser_max - 400) > wasser ) { wasser_z++; } else { wasser_z = 0; } if ((gas_max - 100) > gas ) { gas_z++; } else { gas_z = 0; } if (gas_z > 100 && time - last_gas > 500){ gas_counter++; gas_max= 0; last_gas = time; } //Qn 2.5 =2500 Liter die Stunde = 60000 Markierungen am Raedchen 0,06 Sekunden (=60 ms für einen Durchgang) if (wasser_z > 40 && time - last_wasser > 30){ wasser_counter++; wasser_max= 0; last_wasser = time; } if (time - lastlog > 50){ // Serial.print("Time: "); lastlog = time; long checksum = time + gas + gas_counter + wasser + wasser_counter; Serial.print(time); Serial.print(" - "); Serial.print(gas); Serial.print(" - "); Serial.print(gas_counter); Serial.print(" - "); Serial.print(wasser); Serial.print(" - "); Serial.print(wasser_counter); Serial.print(" - "); Serial.print(checksum); Serial.print(" - "); Serial.print(gas_max); Serial.print(" - "); Serial.print(wasser_max); Serial.print(" - "); Serial.println(loops); loops=0; } }
Der Code auf der PC/Raspberry-Seite: (Aufruf mit Cron regelmäßig, falls etwas abbricht. Job startet sich nur einmal, falls er noch läuft)
#!/usr/bin/perl # Set up the serial port use Proc::PID::File; die "Already running!" if Proc::PID::File->running(); use DBI; #use LWP::UserAgent; use Time::HiRes qw(usleep nanosleep time); my $dbh2 = DBI->connect("DBI:mysql:database=volkszaehler;", "vz", "dfdfdfdf" ) or die $DBI::errstr; my $sth3 = $dbh2->prepare("INSERT INTO `data` (`channel_id`, `timestamp`, `value`) VALUES (?, ?, ?)"); #my $Gas_offset = $dbh2->selectrow_array('SELECT count(*) FROM table WHERE...', undef, @params); #my $wasser_offset = system("stty -F /dev/usb-arduino_nano 1:4:cbf:a30:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"); my $dbh = DBI->connect("DBI:mysql:database=test2;", "test2", "dfdfdfdfdf" ) or die $DBI::errstr; my $sth = $dbh->prepare("INSERT INTO `test`(`timestamp`, `wert`, wert2 ) VALUES (?,?, ?)"); my $sth2 = $dbh->prepare("delete from `test`") ; $sth2->execute() or die "Couldn't execute statement: " . $sth2->errstr; #1014 open(LOGGING, ">>", "/tmp/logging.txt") ; open(SERIAL, "</dev/usb-arduino_nano"); $time_offset = 0; while (1) { # Poll to see if any data is coming in #https://groups.google.com/forum/#!topic/comp.lang.perl.misc/cZwsvIla1cM eval { local $SIG{ALRM} = sub { die "Zeitlimit überschritten $!"}; # alarm(x) muss in den eval()-Block alarm(10); # Maximale Zeit: 9 bis 10 Sekunden $test = <SERIAL>; # Kritische Operation alarm(0); # alarm zurücksetzen }; ## end of eval # Irgendetwas schiefgelaufen? if ($@) { print "timeout\n"; die(); } elsif (length($test)==0){ print "länge 0\n"; sleep 20; die(); } if ( $test =~ /([0-9]*) - ([0-9]*) - ([0-9]*) - ([0-9]*) - ([0-9]*) - ([0-9]*) - ([0-9]*) - ([0-9]*)/ && $1 + $2 + $3 + $4 + $5 == $6){ $time = $1; $time_offset2 = int(time * 1000) - $time; if (abs($time_offset-$time_offset2) >=5000){ $time_offset = $time_offset2; } $gas = $2; $gas_counter = $3; $wasser = $4; $wasser_counter = $5; # $sth->execute ($1,$gas, $wasser); if ($wasser_counter > $wasser_counter_old){ $sth3->execute (12,$time_offset + $time, $wasser_counter - $wasser_counter_old) or die "Couldn't execute statement: " . $sth3->errstr; } $wasser_counter_old = $wasser_counter; if ($gas_counter > $gas_counter_old){ $sth3->execute (9,$time_offset + $time, $gas_counter - $gas_counter_old) or die "Couldn't execute statement: " . $sth3->errstr; } $gas_counter_old = $gas_counter; chop($test); chop($test); print LOGGING $test.gmtime()."\n"; print $test.gmtime()."\n"; } }
Was noch justiert werden muss, ist die Differenz, die zwischen Tal und Berg nötig ist:
<?php //include ("/usr/share/jpgraph/jpgraph.php"); //include ("/usr/share/jpgraph/jpgraph_line.php"); include ("/var/www/z/graph/src/jpgraph.php"); include ("/var/www/z/graph/src/jpgraph_line.php"); include ("/var/www/z/graph/src/jpgraph_date.php"); $graph = new Graph(3000,500); $graph->img->SetMargin(40,40,40,40); //$graph->SetScale("textlin"); //$graph->SetScale('intlin',0,50,0,24); $graph->legend->SetColumns(1); $graph->legend->SetPos(0.05,0.5,'right','center'); #$graph->yaxis->scale->ticks->Set(45,50); //$graph->SetShadow(); $graph->title->Set("Gaskurve"); $graph->title->SetFont(FF_FONT1,FS_BOLD); $graph->SetScale('datlin'); $graph->xaxis->SetLabelAngle(90); $graph->xaxis->scale->SetDateFormat('H:i:s'); $connection = mysql_connect("localhost", "test2", "GEHEIMER"); $db_selected = mysql_select_db("test2"); if (!$connection) { die('Not connected : ' . mysql_error()); } if (!$db_selected) { die('Can\'t use db : ' . mysql_error()); } $query = "SELECT *, UNIX_TIMESTAMP(datum) zeitunix FROM `test` where datum > '".$_GET["a"]."' "; $result = mysql_query($query); $i=0; while ($row = @mysql_fetch_assoc($result)) { $ywert[$i] = $row['wert2']; $stunden[$i] = $row['zeitunix']; $i +=1; } $lineplot=new LinePlot($ywert, $stunden ); $lineplot->SetLegend ("Wert"); $graph->Add($lineplot); $graph->Stroke(); ?>
So sieht so eine Grafik aus:
Für nur einen Wasserzähler, Erfassung der Impulse über USB-Schnittstelle und vzlogger. Datenbank kann ausgelagert sein.
Hier der Code für den Arduino. Verkürzte Version von oben, weil nur eine Diode ausgewertet wird und die Impulse im Arduino nicht gezählt werden.
// Original von mikemiller 2015-2016 // Reduziert auf nur 1 Wasserzähler mit Ausgabe // des Impulses auf die USB-Schnittstelle void setup() { Serial.begin(300); // Open serial communications and wait for port to open: // A baud rate of 115200 is used instead of 9600 for a faster data rate // on non-native USB ports //Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } //digitalWrite(A0, HIGH); pinMode(A0, INPUT); digitalWrite(A1, HIGH); } int wasser_max = 0; long wasser = 0; int wasser_z = 0; unsigned long lastlog = 0; unsigned long last_wasser = 0; unsigned long time; void loop() { // Gleitender Durchschnitt anwenden. wasser = ((wasser *3) + analogRead(0))/4; time = millis(); // time since startup in milliseconds. Overflow after approx. 50 days. if (time < lastlog ){ //time ist übergelaufen lastlog = 0; last_wasser = 0; } wasser_max = max(wasser_max, wasser); if ((wasser_max - 200) > wasser) { wasser_z++; } else { wasser_z = 0; } // Qn 2,5 m³/h = 2.500 Liter pro Stunde // Das Raedchen hat 6 Fluegel und dreht sich 1x je 0,1 Liter --> 60 Impulse je Liter // Bei Maximaldurchfluss 2.500 l/h * 60 Imp/l = 2.500*60 Imp/h = 2.500/60 Imp/s = 41,7 Imp/s // --> Mindestens 24 ms zwischen zwei Impulsen ("Prellen" vermeiden) if (wasser_z > 40 && time - last_wasser > 30) { wasser_max= 0; lastlog = time; last_wasser = time; Serial.print(0x00); // 1 Impuls ausgeben. } }
Die Differenz zwischen Hell und Dunkel muss für dieses Code-Beispiel mindestens 200 betragen, sonst wird kein Impuls erzeugt.
Kanal erstellen als Wassermengenzähler mit einer Auflösung von 60 (Impulse/l, siehe Berechnung im Code) und Stil=steps. UUID notieren.
Exemplarische /etc/vzlogger.conf, Element der Aufzählung „meters“: []:
{ // Water as S0 meter "enabled": true, // disabled meters will be ignored (default) "allowskip": true, // errors when opening meter may be ignored if enabled "protocol": "s0", // meter protocol, see 'vzlogger -h' for full list "device": "/dev/ttyUSB0", // meter device "aggtime": -1, // aggregate meter readings and send middleware update after <aggtime> seconds "aggfixedinterval": true, // round timestamps to nearest <aggtime> before sending to middleware "channel": { "identifier": "Impulse", // s0 meter knows "Impulse" and "Power" "uuid": "<uuid>", "aggmode": "SUM", // aggregation mode: aggregate meter readings during <aggtime> interval // "SUM": add readings (use for s0 impulses) // "MAX": maximum value (use for meters sending absolute readings) // "AVG": average value (use for meters sending current usage) "middleware": "http://localhost/middleware.php" } } // meter
Im Homematic-Forum wird ein Ansatz beschrieben, bei dem man den roten Ein-Liter-Zeiger des Wasserzählers zur Kontrastverstärkung mit einer grünen LED anleuchtet, die Helligkeitsunterschiede mit einem Fototransistor auffängt und mit einem modifizierten TCRT500 (das ist eine fertige, justierbare Baugruppe, eigentlich für Reflexlichtschranken) digital aufbereitet.
Beim Nachbau hat es sich gezeigt, dass es u.a. auf die Helligkeit der verwendeten LED ankommt. Die zuerst beschafft LED war nicht hell genug, um den Fototransistor selbst bei direkter Belichtung durchzusteuern. Mit der nachbeschafften Nichia NSPG300D gelang der Betrieb dann auf Anhieb.
Wie in der Original-Anleitung beschrieben, ist die Justage der Lichtschranke auf dem Wasserzähler sehr fummelig. Die Fixierung mit dem Muttern-und-Epoxidharz-Trick gelang nicht. Nach dem Nachweis der theoretischen Machbarkeit habe ich dann aufgegeben und mir den alternativ genannten Impulsgeber mit S0-Schnittstelle bestellt, der auch einen 3d-gedruckten Montageträger zum Aufsetzen auf den Wasserzähler beinhaltet.
Dieser Impulsgenerator ließ sich dann einfach aufbauen und funktioniert in der Tat viel besser als die einfache Schaltung. Ob das an der Verwendung eines Mikrocontrollers oder an dem mit Linse fokussierten Lichtstrahl der LED liegt, ist unklar. Die Montage auf dem Wasserzähler und die Justage war jedenfalls in wenigen Minuten erledigt. Zum Schutz von Fremdlicht habe ich ein HT-Rohr mit Deckel über den Wasserzähler gestülpt.
Nach Lektüre des Artikels über S0-Schnittstelle am USB ist mir aufgefallen, dass die dort aufgebaute Schaltung dem Ausgangsteil des STALL-Impulsgebers nicht unähnlich ist und habe mir den Aufbau dieser Schaltung gespart. Statt dessen habe ich
Die Idee ist wie bei der Schaltung aus S0-Schnittstelle am USB, dass die Serielle Schnittstelle +5V sieht, wenn der Optokoppler hochohmig ist und von diesem beim Durchschalten des Impulsgebers gegen Masse gezogen wird. Die vzlogger-Konfiguration aus S0-Schnittstelle am USB hat dann auf Anhieb funktioniert. Bei meiner Installation habe ich noch ca 7 Meter KNX-Leitung zwischen dem USB-to-Serial-Adapter und dem Impulsgeber.
"meters" : [{ "protocol" : "s0", "enabled" : true, "device" : "/dev/USBserialWasser", "aggtime" : -1, "aggfixedinterval" : false, "channels": [{ "uuid" : "fa6c4b30-8303-11e9-a746-5fe24f8ae232", "middleware" : "http://middleware.example/", "identifier" : "Impulse", "aggmode" : "none", }] } ]}
So besonders gut scheint die Middleware mit den groben Impulsen (1 Impuls pro Liter) und dem oft stundenlangen Ruhen des Messwertes nicht klarzukommen; besonders bei kurzfristigem Verbrauch (z.B. Toilettenspülung) bleibt oft ein hoher Momentanverbrauch in der Tabelle stehen und der Strich in der Grafik geht erst in einer größeren Zoomstufe oder nach etlicher Zeit bis auf Null zurück. In der langfristigen Betrachtung (z.B. die Tagesgrafik) scheint das aber OK zu sein.
Ich hatte den Laser-Ansatz für Jahre in Gebrauch. Abgesehen von den Sicherheitsproblemen hatte ich nach ein paar Jahren Algen im Wasserzähler. Daher die Idee auch mit grüner LED zu arbeiten. Da 3D-Drucker vorhanden habe ich eine Halterung für eine Linse entworfen, deren Fokuspunkt direkt auf dem Rad liegt. https://www.thingiverse.com/thing:4151977 Impulse werden mit einem OPT101 Chip (intgrierter Verstärker) abgenommen.