Benutzer-Werkzeuge

Webseiten-Werkzeuge


hardware:channels:meters:water:wasserzaehler_ohne_s0

Mechanischen Wasserzähler über Impulse auslesen

Idee: Das kleine Sternrad des Wasserzählers mit einem Laser anvisieren und die Reflexionen mit Photodiode und Arduino-Mikrocontroller auswerten.

Hardware

Ein roter Laser mit einer Strahlungsleistung von 5mW entspricht der Laserklasse 3R. Die zugängliche Laserstrahlung liegt im Wellenlängenbereich von 302,5 nm bis 10^6 nm und ist gefährlich für das Auge.
Sicherheitsmaßnahmen:
Niemals direkt in den Strahlengang blicken.
Im Laserbereich sind gut reflektierende Flächen zu vermeiden.
Vorgeschriebene Laserschutzbrillen unbedingt tragen.


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.

Externer Link

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.

Fremdlicht beeinflusst das Signal der Photodiode enorm. Sobald die Justierung einigermaßen passt, den Aufbau am besten abdecken.

Über Script in die Datenbank

Für Gas- und Wasserzähler, Eintrag per php direkt in die Datenbank.

Arduino

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;
 
	}
 
}

Script zur Datenübernahme

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";
	}
 
 
 
}
Dieser Code erwartet die Volkszähler-Datenbank auf demselben Raspberry Pi, an dem der Arduino angeschlossen ist.
Die Kanäle (channel_id) sind hart codiert: 12 und 9.

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:

gas.png

Oder den „Serial Plotter“ der Entwicklungsumgebung verwenden.

Über vzlogger an die Middleware

Für nur einen Wasserzähler, Erfassung der Impulse über USB-Schnittstelle und vzlogger. Datenbank kann ausgelagert sein.

Arduino

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.

Frontend

Kanal erstellen als Wassermengenzähler mit einer Auflösung von 60 (Impulse/l, siehe Berechnung im Code) und Stil=steps. UUID notieren.

Beispielkonfiguration

Exemplarische /etc/vzlogger.conf, Element der Aufzählung „meters“: []:

vzlogger.conf
    {
            // Water as S0 meter
 
            "enabled": true,                // disabled meters will be ignored (default)
            "skip": 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 
hardware/channels/meters/water/wasserzaehler_ohne_s0.txt · Zuletzt geändert: 2018/01/25 15:55 von jau