Projekt PiUS - Raspberry Pi UltraSonic
Gemeinschaftsprojekt von Dirk Weyand (Software) und Neueinsteiger (Hardware, weil ich Software nicht kann)
Alle Neuerungen werden durch Ergänzungen am Ende dieses Beitrages zu sehen sein.
Ziel des Projektes ist es, den Pegel des Wasserstandes einer Regenwasserzisterne grafisch darzustellen, um langfristige Verläufe erkennen zu können. Abgewandelt kann dies wahrscheinlich auch für den Pegel von Öltanks genutzt werden.
Zuerst musste eine Möglichkeit gefunden werden, den Pegel zu messen. Einen guten Ansatz fand ich hier: Entfernungsmessung mit Ultraschall
Um den Sensor und die Steckverbindung bestmöglich vor der Nässe zu schützen, habe ich die Steckverbindung in ein entsprechend abdichtendes Gehäuse gebaut, dass im Inneren der Zisterne befestigt wurde.
Bei der Verbindung zum RasPi habe ich mich für eine Key-Stone Cat 6a Buchse und ein Patchkabel entschieden. Der Grund liegt in der langen Kabelstrecke. Das Kabel bis zum RasPi ist 20 m lang. Daher habe ich jeweils ein Adernpaar (ein Patchkabel hat 4x2 Adern) miteinander verbunden, so dass sich der Querschnitt der Verbindung verdoppelt und somit der Leitungswiderstand verringert. Obwohl bei mir auf der Seite des RasPi nochmals eine RJ45 Einbaubuchse einen weiteren Widerstand darstellt, wird das Signal sauber übertragen. Die Pegelanpassung mit den zwei Widerständen wird direkt an den GPIOs des RasPi vorgenommen, nicht in der Zisterne.
In den Deckel der Dose habe ich passend vier Löcher gebohrt, um die Pins des Sensors dort hindurch in die Dose stecken zu können. Das Kabel, dass am Key-Stone befestigt ist, wurde über eine Lochrasterplatine an eine Buchsenleiste gelötet, die dann auf die Pins des Sensors gesteckt wird, die in das Innere der Dose ragen. Die Kabelenden wurden mit Heisskleber an der Lochrasterplatine befestigt, um einem Kabelbruch bei der Montage vorzubeugen.
Damit zumindest die Elektronik des Sensors möglichst gut vor der Nässe geschützt ist, wurde die Sensorplatine am Rand mit Isolierband umwickelt und der entstandene Raum dann beidseitig mit Heisskleber gefüllt. Nachdem dieser abgekühlt war, wurde das Isolierband entfernt und nochmals eine Wulst Heisskleber über die Außenkante der Platine gezogen. Somit sollte die Elektronik bestmöglich geschützt sein.
Damit war der Hardwareteil erledigt. Nun folgte noch das Problem mit der Software. Leider habe ich es nicht geschafft, das hier genutzte Python-Script so zu verändern, dass die Ausgabe in eine Datei erfolgt. Hilfe kam dann von unerwarteter Stelle.
Dirk Weyand von https://forum-raspberrypi.de/www.tgd-consulting.de, wo ich vor einigen Wochen eine sehr geniale Raspberry Pi Cam Software gefunden habe, bot mir seine Hilfe bei der Lösung des Problems an, die ich gerne annahm.
Herausgekommen ist nach vielen Tagen und Nächten mit zahllosen Testläufen und Updates das beigefügte Script.
Das Script wird im entsprechenden Verzeichnis (z. B. /home/pi/zisterne/ ) mit dem Befehl sudo ./r3 PiUltraSonic.r aufgerufen. Zuvor muss der Interpreter r3 von der Seite des Autors heruntergeladen und in das gleiche Verzeichnis entpackt werden.
Die "Globals" sollten selbsterklärend sein.
In den kommenden Tagen und Wochen wird das Projekt weiter entwickelt. Es soll mit Hilfe von gnuplot automatisch eine Tages-, Wochen-, Monats- und Jahresgrafik erzeugt werden, die über die Homepage zugänglich ist. Die ersten Tests sehen vielversprechend aus.
REBOL [
Title: "Ultrasonic Distance Meter"
Name: PiUS
Version: 1.0.3
Date: 24-May-2014
File: %PiUltraSonic.r
Author: "Dirk Weyand"
Copyright: "TGD-Consulting"
Homepage: http://rebol.tgd-consulting.de
E-Mail: [info AT tgd-consulting DOT de]
History: [ 05-May-2014 0.1.0 "inital release"
05-May-2014 0.1.1 "fixed gpio"
05-May-2014 0.1.2 "fixed Entfernungsmessung"
06-May-2014 0.2.0 "added debug info"
06-May-2014 0.3.0 "added Fehlertoleranz"
06-May-2014 0.4.0 "added Low-Level message"
06-May-2014 0.4.1 "enhanced timing"
07-May-2014 0.5.0 "added TimeOut fix"
08-May-2014 0.5.1 "cleaned up code"
08-May-2014 0.6.0 "added time synchronisation"
08-May-2014 0.7.0 "added Log-File Trigger"
08-May-2014 0.7.1 "fixed sleep time drift"
08-May-2014 0.7.2 "changed log format 4 gnuplot compliance"
08-May-2014 0.7.3 "fixed time synchronisation"
08-May-2014 0.8.0 "added optional script argument"
10-May-2014 0.8.1 "fixed timing"
12-May-2014 0.9.0 "added optional rate script argument"
13-May-2014 0.9.1 "fixed script arguments"
17-May-2014 0.9.2 "enhanced logging 2 RAM-disk"
19-May-2014 1.0.0 "added moving average"
22-May-2014 1.0.1 "fixed moving average"
23-May-2014 1.0.2 "fixed drift"
24-May-2014 1.0.3 "fixed logging"
]
License: { Donationware, visit http://download.tgd-consulting.de#Donate to make a donation. }
Purpose: { This script demonstrates a smart solution to use the HC-SR04 ultrasonic sensor on the Raspberry Pi with Rebol3.
Before you can use this script, you need to built the R3-binary from the mainline R3 sources on GitHub (https://github.com/rebol/rebol)
or download a ready-made binary for Raspbian at http://www.tgd-consulting.de/REBOL/r3-RPi.tar .}
Usage: [ sudo ./r3 PiUltraSonic.r [logfile] [-r abtastrate] [-l lograte] ]
]
;###############
;### ###
;### Globals ###
;### ###
;###############
Tiefe: 1.81 ; maximale Tiefe (Nullwert) der Zisterne in Metern
Offset: 11 ; Abstand Sensor zur Wasseroberflaeche in cm bei 100% Fuellstand
rate: 0:05:00 ; Abtastrate (jede Minute)
lograte: 0:30:00 ; spaetestens nach 30 Minuten aktuellen Wasserstand loggen, ansonsten nur bei veraendertem Wasserstand
file: %wasserstand.txt ; Log-File der Messergebnisse
Toleranz: 3% ; gueltige Messwerte muessen innerhalb der Fehlertoleranz liegen
Low-Level: 20% ; bei erreichen des Low-Levels erfolgt eine Alert-Meldung
GPIO-Trigger: 24 ; benutzter Trigger-GPIO zur Distanzmessung
GPIO-Echo: 23 ; verwendeter GPIO fuer Echo-Impuls
verbose: false ; bei true erfolgt Bildschirmausgabe (Ausgabe nach STDOUT)
gnu: true ; bei true erfolgt die Ausgabe der Messergebnisse ins Log-File im gnuplot kompatiblen Format
Trigger: %/run/shm/.rm_PiUSlog ; Falls Trigger-Datei vorhanden -> wird das Log-File ueberschrieben
TimeOut: to time! 100 * Tiefe + Offset * 200% / 17150 ; 2x Wert zur Vermeidung von Deadlocks
alert: args: level: old: prev: r: m: none
attempt [all [system/script/args not equal? 'dir exists? foo: to file! first args: parse system/script/args " " file: foo]] ;checks 4 logfile as optional script argument
attempt [all [args foo: select args "-r" rate: to time! foo]] ;checks 4 rate as optional script argument
attempt [all [args foo: select args "-l" lograte: to time! foo]] ;checks 4 optional lograte script argument
sync: func [ {Zeitsynchronisation zur Abtastrate}
/local t
][
t: now
all [verbose prin ["PiUS @" t ": Time synchronisation"]]
t: t/time
either rate > 0:00:59 [
until [ ; sync auf volle Minute
all [verbose prin "."]
wait 1 ; 1 Sekunde warten
t: now/time
zero? t/second
]
; sync auf Minuten der Abtastrate
all [positive? rate/minute positive? t/minute // rate/minute
until [
all [verbose prin "m"]
wait 60 ; 60 Sekunden warten
t: now/time
zero? t/minute // rate/minute
]
]
; sync auf Stunden der Abtastrate
all [positive? rate/hour positive? t/hour // rate/hour
until [
all [verbose prin "h"]
wait 3600 ; 1h warten
t: now/time
zero? t/hour // rate/hour
]
]
][
all [positive? rate/second positive? t/second // rate/second
until [
all [verbose prin "."]
wait 1 ; 1 Sekunde warten
t: now/time
zero? t/second // rate/second
]
]
]
all [verbose prin newline]
]
mittelwert: func [ {gleitender Mittelwert der letzten 5 Werte oder exponetiell gewichteter Mittelwert.}
i
/EMA {Exponentiel Moving Average}
/local buf sum SF
][
either EMA [
SF: divide 2 1 + to integer! rate / 60 ;Smooth-Factor/Glättungsfaktor in Abh‰ngigkeit Minuten der Abtastrate
either none? buf [buf: i][buf: buf + multiply SF i - buf]
][
default 'buf []
remove/part buf max 0 subtract length? buf 5
insert tail buf i
sum: 0
foreach element buf [sum: sum + element]
sum / length? buf
]
]
Entfernungsmessung: func [ {Ultraschall-Messung der Entfernung in cm}
/relativ {oder in Prozent %}
/average {Mittelwertbildung (EMA)}
/local start stop delta abstand gpio23 gpio24
][
gpio23: open/read %/sys/class/gpio/gpio23/value ; oeffne Echo GPIO
gpio24: open/write %/sys/class/gpio/gpio24/value ; oeffne Trigger GPIO
write gpio24 #{310A} ; setzt Trigger auf High = 1
stop: stats/timer ; Statement benoetigt Rechenzeit von ca. 0.00002 s
write gpio24 #{300A} ; setzt Trigger auf Low = 0
; while [equal? read head gpio23 #{300A}] [start: stats/timer] ; Beginn der Messung #{300A} = "0^/" while kann zu deadlocks fuehren daher until
until [ ; Beginn der Messung #{300A} = "0^/"
start: stats/timer
any [equal? read head gpio23 #{310A} greater? start - stop TimeOut]
]
while [equal? read head gpio23 #{310A}] [stop: stats/timer] ; Ende der Messung (Empfang des Ultraschall-Echos) #{310A} = "1^/"
close gpio23 ; Echo GPIO schliessen
close gpio24 ; Trigger GPIO schliessen
delta: abs stop - start ; Laufzeit des Ultraschall-Signals
abstand: delta/second * 17150 ; Entfernung in cm (Schallgeschwindigkeit 34300 cm/s / 2)
if average [
all [none? prev prev: abstand]
all [equal? n: n + 1 3 prev: abstand]
; prin [now "Messwert:" abstand "vorher:" prev]
all [lesser? abs prev - abstand 2 * abstand * Toleranz abstand: mittelwert abstand prev: abstand n: 0] ; Mittelwert aus den letzten gültigen Messungen bestimmen
; all [lesser? abs prev - abstand 2 * abstand * Toleranz abstand: mittelwert/EMA abstand prev: abstand n: 0] ; Mittelwert als Exponentiel Moving Average bestimmen
; prin [" Mittelwert:" abstand newline]
]
either relativ [
abstand: max 0 abstand - offset
min 100 max 0 subtract 100 abstand / Tiefe ; 1.81m Tiefe = minimaler Wasserstand der Zisterne
][
abstand
]
]
; Init GPIOs
all [verbose print ["PiUS @" now ": Initializing GPIOs..."]]
call/wait {echo "24" > /sys/class/gpio/export} ; Trigger GPIO aktivieren
write %/sys/class/gpio/gpio24/direction #{6F75740A} ; Trigger GPIO ist Ausgang #{6F75740A} = "out^/"
call/wait {echo "23" > /sys/class/gpio/export} ; Echo GPIO aktivieren
write %/sys/class/gpio/gpio23/direction #{696E0A} ; Echo GPIO ist Eingang #{696E0A} = "in^/"
; Synchronization auf Abtastrate
sync
n: i: 0
lastlog: now - lograte
all [verbose print ["PiUS @" now ": Starting meassurement... ( rate:" rate "/ lograte:" lograte ")"]]
forever [
t1: stats/timer
t: now ; aktueller Zeitpunkt
drift: t/time
m: drift / rate ; Drift
r: m - to integer! m
level: to integer! Entfernungsmessung/relativ/average ; aktueller Wasserstand der Zisterne
all [none? old old: level] ; erforderlicher init von old bei Programstart
all [equal? i: i + 1 4 old: level] ; vier aufeinander folgende Messwerte auflerhalb der Fehlertoleranz -> gueltiger Messwert
if lesser-or-equal? abs old - level level * Toleranz [ ; Messwert innerhalb der Fehlertoleranz
; Logging-Format
foo: either gnu [ajoin [t/day "-" t/month "-" t/year "_" drift " " level newline]] ; gnuplot format
[ajoin [t " " level newline]] ; REBOL format
; Messwert in RAM-Disk speichern
write %/run/shm/current.data foo ; aktueller Messwert
i: 0
if any [not equal? old level lesser-or-equal? drift // lograte 0:00:01 greater-or-equal? difference t lastlog lograte] [ ; logging bei unterschiedlichen Werten, bei sync zur lograte oder nach lograte Dauer
either exists? Trigger [
write file foo ; Log-Eintrag fuer Entfernungsmessung in neue Datei schreiben
delete Trigger ; Trigger loeschen
][
write/append file foo ; Log-Eintrag fuer Entfernungsmessung ans Ende der Datei schreiben
]
all [verbose prin foo] ; Ausgabe nach STDOUT
lastlog: t
old: level
]
all [alert
greater? level multiply low-level 1 + Toleranz ; Hysterese = Fehlertoleranz
alert: false] ; Wasserstand hat low-level ueberschritten -> alert-Stufe aufgehoben
all [not alert
lesser-or-equal? level low-level ; Low-Level Niveau erreicht -> einmalige alert-Meldung absenden
alert: true ; alert ist aktiv
print [{E-Mail Alert "Nicht mehr so viel giessen!!! Fuellstand der Zisterne betraegt nur} level {%."}] ; Trigger fuer E-Mail ALert
]
]
t2: stats/timer
elapsed: t2 - t1
wait rate - elapsed - either zero? drift/minute // rate/minute [r][negate 1 - r] ; Sleep-Time
]
; Reset GPIOs
call/wait {echo "24" > /sys/class/gpio/unexport} ; Trigger deaktivieren
call/wait {echo "23" > /sys/class/gpio/unexport} ; Echo deaktivieren
halt
;#EOF
Alles anzeigen
edit:
13.05.2014 aktuelle Version 0.9.0 hinzugefügt und Code eingestellt
14.05.2014 aktuelle Version 0.9.1 hinzugefügt und Code eingestellt
15.05.2014 erste Tagesgrafik manuell aus den gesammelten Daten erstellt und angefügt
20.05.2014 aktuelle Version 1.0.0 hinzugefügt und Code eingestellt
28.05.2014 aktuelle Version 1.0.3 hinzugefügt und Code eingestellt
20.01.2014 aktuelle Version 1.6.2 erschienen. Der Code ist inzwischen so umfangreich, dass der Autor ihn nicht mehr kostenlos zur Verfügung stellen möchte. Ich halte den Preis für sehr fair und kann daher einen Kauf uneingeschränkt empfehlen.
24.01.2014 aktuelle Version 1.7.1 erschienen - Änderungen siehe Beitrag #17