• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[gelöst] bash: Reed Kontakt überwachen

Hallo Forum,

ich suche eine Möglichkeit, mit einem Shell-Skript den Zustand eines Reed-Kontakts/Schalters/Tasters zu überwachen. Das folgende Skript zeigt zwei Möglichkeiten, die ich gefunden habe, die aber nur im Dialog funktionieren, d. h., je nach Schalterstellung bekomme ich angezeigt, was ich eingetippt habe oder nicht.

Code:
#!/bin/sh

# Skript zur Überwachung eines Reed-Kontakts
#
# Anschluss:
# RS232 Sub-D 9pol DB9
# Pins 2 und 3 Reed-Kontakt   : Tx  -> Rx
# Pins 4-1-6 brücken          : DTR -> CD und DSR
# Pins 7-8 brücken            : RTS -> CTS
#
#
# Der geschlossene Reed-Kontakt erzeugt ein loop-back-Kabel
# Alles, was an /dev/ttySx geschickt wird, kommt wieder zurück.
# Ist der Kontakt geöffnet, kommt nichts zurück.
# --------------------------------------------------------------

initialise ()
{
DEVICE="/dev/ttyS0"
BAUDRATE=19200
}

setDevice ()
{
# ############################
# moserial (Terminal-Programm)
#
# der loop back test mit moserial          :
# funktioniert wenn:                       : stty option
# -------------------------------------------------------
# soft- noch hardware handshake aus        : -crtscts
# modus read/write                         : cread (?)
# kein lokales echo                        : -echo
#
# Schnittstellen-Settings auslesen:
# Wenn moserial läuft, liefert ein gleichzeitiges "stty -F /dev/ttyS0 -a" in einem Terminal:
# 
# speed 19200 baud; rows 0; columns 0; line = 0;
# intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; eol = <undef>; eol2 = <undef>; 
# swtch = <undef>;
# start = <undef>; stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; discard = <undef>;
# min = 1; time = 1;
# -parenb -parodd -cmspar cs8 -hupcl -cstopb cread clocal -crtscts
# ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
# -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
# -isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke -flusho -extproc
#
# daraus wird das folgende Kommando abgeleitet:

stty -F $DEVICE $BAUDRATE -parenb -parodd -cmspar cs8 -hupcl -cstopb cread clocal -crtscts \
ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany \
-imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 \
ff0 -isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl \
-echoke -flusho -extproc

}

# Lesen im Hintergrund
# https://unix.stackexchange.com/questions/22545/how-to-connect-to-a-serial-port-as-simple-as-using-ssh
# leicht modifiziert
bg ()
{
echo "bg()"
echo "zum Beenden: CTRL-C"
	# Let cat read the device in the background
	cat $DEVICE & bgPid=$!

	# Read commands from user, send them to device
	while read cmd; do
		echo $cmd
	done > $DEVICE

	# Terminate background read process
	kill $bgPid
}

# in einer Zeile
# https://unix.stackexchange.com/questions/117037/how-to-send-data-to-a-serial-port-and-see-any-answer
eineZeile ()
{
echo "eineZeile()"
echo "zum Beenden: CTRL-C"
cat $DEVICE & cat > $DEVICE
}


# ################################

echo "Start"
initialise
setDevice
# eine der beiden folgenden Zeilen auskommentieren
#bg 		# funktioniert nur im Terminal mit Tastatureingaben
eineZeile	# funktioniert nur im Terminal mit Tastatureingaben

echo "Ende"

Das Skript zeigt mir bisher nur, dass mein Kabel funktioniert. Ich habe es bisher nicht geschafft, durch das Skript etwas auf /dev/ttyS0 schreiben und wieder lesen zu lassen, um nach Vergleich und Gleichheit das weitere Programm zu steuern. Siehe folgenden Pseudo-Code:

Code:
Pseudo-Code
raus="A"
rein="B"
reed="x"
echo $raus > /dev/ttyS0
rein=cat /dev/ttyS0
if $raus=$rein
	reed="geschlossen"
else
	reed="offen"
fi

Wie bekomme ich die Ein- und Ausgaben im Shell-Skript, oben ( bg() oder eineZeile() ), so umgeleitet, dass es so funktioniert, wie im Pseudo-Code angedeutet?

Danke für Tipps!

Gruß,

radiergummi
 
Code:
# RS232 Sub-D 9pol DB9
# Pins 2 und 3 Reed-Kontakt   : Tx  -> Rx
# Pins 4-1-6 brücken          : DTR -> CD und DSR
# Pins 7-8 brücken            : RTS -> CTS
#
#
# Der geschlossene Reed-Kontakt erzeugt ein loop-back-Kabel
# Alles, was an /dev/ttySx geschickt wird, kommt wieder zurück.
# Ist der Kontakt geöffnet, kommt nichts zurück.

Wozu soll dieser loopback gut sein? Deine Lösung ist ein overkill und unsicher obendrein.
Schalte den Kontakt zwischen:
RTS [7] out <----> in CTS [8]
oder
DTR [4] out <--> in DCD [1]

Schalte RTS bzw. DTR ein und überwache CTS bzw. DCD.
Eine Kommunikation über die Datenleitungen (TxD/RxD) ist unnötig.
Außerdem kannst du damit:
a) 2 Kontakte auf einer tty überwachen (wenn gewollt)
b) wirst nicht mit irgendwelchen Daten konfrontiert, die beim Schließen des Reed auftreten
c) erreichst damit eine viel größere Streckenlänge (30m wären durchaus möglich)

Gruß
Gräfin Klara
 
OP
R

radiergummi

Member
Frohe Weihnachten Gräfin Klara,

danke für die Rückmeldung. Meine Weisheiten habe ich von hier:

https://www.lammertbies.nl/comm/cable/de_RS-232.html#loop bzw. hier http://www.ni.com/tutorial/3450/en/#toc2 oder hier https://www.instructables.com/id/DB-9-RS-232-Loopback-Plug-Windows-and-OS-X/

Ich habe auch Vorschläge gefunden, nach denen nur die Pins 2 und 3 gebrückt werden. Meine Versuche damit haben aber nicht funktioniert.
Für meine ursprüngliche Idee ist das Kabel nicht mein Problem. Ich bekomme das Skript dazu nicht hin.


Gräfin Klara schrieb:
Schalte den Kontakt zwischen:
RTS [7] out <----> in CTS [8]
oder
DTR [4] out <--> in DCD [1]

Schalte RTS bzw. DTR ein und überwache CTS bzw. DCD.

Deinen Vorschlag verstehe ich im gegebenen Zusammenhang nicht, bzw. ich weiss nicht, wie ich ihn in einem Shell-Skript umsetzen kann.

Mit den stty-Optionen crtscts und cdtrdsr kann ich RTS- und DTR-Handshakes einschalten, okay. Aber wie überwache ich CTS oder DCD?

Meinst Du vielleicht so was:
- versuchen crtscts bzw. cdtrdsr einzuschalten und
- ĺese Zustand über stty -a aus

Bei geschlossenem Kontakt hat das Einschalten funktioniert, ansonsten nicht?

Oder gibt es ein Kommandozeilenprogramm, das ich in einem Skript nutzen kann?

Einzelne Pins abzufragen wäre natürlich traumhaft - aber ich weiss nicht wie (im Shell-Skript).
Daher wollte ich den Umweg über den Loop-Back gehen: wenn zurückkommt, was ich hinschicke, dann ist der Kontakt geschlossen.

Gräfin Klara schrieb:
Außerdem kannst du damit 2 Kontakte auf einer tty überwachen (wenn gewollt)
Das wäre dann schon genial.

Verrätst Du mir wie das geht? Oder wo es steht?

Frohe Weihnachten,

Radiergummi
 
Mit stty kommst du nicht weiter.
stty verwendet die Leitungen nur als Kontrolle für die Kommunikation (handshake) hin zu einem z.B. Modem.
Eine Steuerung dieser Leitungen als I/Os ist damit nicht möglich. stty verwendet Automatismen, wie sie vor 20 Jahren gebraucht wurden.
Doch ist im Grunde ist eine solche Verwendung der RS232 als I/O eine simple und auch interessante Angelegenheit.
Vier Eingänge stehen auf der seriellen zur Verfügung, die auf ihren Zustand in einem script abgefragt werden können.
Wenn du willst, schreibe ich dir ein solches Programm. In 30 min sollte das machbar sein.

Gruß
Gräfin Klara
 
OP
R

radiergummi

Member
Gräfin Klara schrieb:
Doch ist im Grunde ist eine solche Verwendung der RS232 als I/O eine simple und auch interessante Angelegenheit.
Vier Eingänge stehen auf der seriellen zur Verfügung, die auf ihren Zustand in einem script abgefragt werden können.
Das klingt nach genau dem (und noch mehr), was ich hier gebrauchen könnte.

Gräfin Klara schrieb:
Wenn du willst, schreibe ich dir ein solches Programm. In 30 min sollte das machbar sein.
Damit machst Du mich jetzt wirkklich sprachlos, wirst es Dir aber gut überlegt haben.
Ich kann Dein Angebot gar nicht ausschlagen, zum Dank jedoch nur mit anderen Fragen dienen.

Wenn Du das machen möchtest, nehme ich es gerne an. Vielleicht würde es ja auch noch anderen weiterhelfen.


In der Zwischenzeit habe ich mich bis zum syscall ioctl durchgeschlagen und bin dann im Linux Journal auf dieses C-Programm gestossen:

Code:
#include <stdio.h>
#include <termios.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

main()
{
  int fd, status;

   fd = open("/dev/ttyS0", O_RDONLY);
   if (ioctl(fd, TIOCMGET, &status) == -1)
      printf("TIOCMGET failed: %s\n",
             strerror(errno));
   else {
      if (status & TIOCM_DTR)
         puts("TIOCM_DTR is not set");
      else
         puts("TIOCM_DTR is set");
   }
   close(fd);
}

Ich habe es durch gcc geschickt und ausser ein paar Warnungen, ein ausführbares a.out (./a.out) bekommen (auch von C-Programmierung habe ich keine Ahnung).

Als nächsten Schritt wollte ich mit diesen tty-spezifischen ioctls weiterexperimentieren. Ich bin aber nicht sicher, ob ich damit weitergekommen wäre.

Gruß und Dank,

Radiergummi
 
Hier sollte es nun verfügbar sein

https://ufile.io/4yow3

Name des files: 2018-12-26_devio.tar.bz2
Beinhaltet den sourcecode, das Makefile, eine kurze Beschreibung und ein test,sh, das den Umgang mit script zeigt.

Gruß
Gräfin Klara
 
OP
R

radiergummi

Member
Ich habe mir Dein Programm eben heruntergeladen. Kompilieren hat funktioniert und Dein Beispielskript läuft auch.

Vielen, vielen Dank für Deine Hilfe!
Ist es Dir recht, hier über das Programm (dessen Einsatz) weiter zu posten?

Jetzt muss der Lötkolben ran! Ich komme aber erst am WE dazu.

Gruß und nochmal danke,

Radiergummi
 
>> Ist es Dir recht, hier über das Programm (dessen Einsatz) weiter zu posten?

Das hoffe ich doch!
Ich habe das Programm nur zwischen Weihnachtskekse und Glühwein heruntergeklopft.
Perfekt geht anders.

Mögliche Probleme:
Reed Kontakte schließen, wenn sie von einem Magnetfeld umgeben werden.
Das bedeutet, dass der Schließdruck sehr gering ist und deshalb die Kontakte prellen.
Wir nehmen den Schaltzustand in einem simplen one shot Verfahren auf.
Passiert diese Aufnahme im Moment des Schließens, kann das wegen des Prellens zu einem falschen Resultat führen.
Sollte dieses Problem entstehen, (was nicht unbedingt der Fall sein muß), dann müssen wir in die Software eine Entprellung einbauen.

Du hast 2 Ausgänge und 4 Eingänge.
Es ist sicher von Vorteil, wenn du die Eingänge im Falle von 4 Kontakten paarweise ausführst.
D.h. RTS für 2 und DTR für 2 Kontakte. Schaltest du nur einen Ausgang auf alle 4 Kontakte,
kann das die Reichweite (Kabellänge) drastisch reduzieren.

Deine Lösung gefällt mir sehr gut.
I/Os am PC sind unüblich aber sehr praktisch. Die Leitungen auf der RS232 sind hervorragend geschützt gegen Überspannungen,
sie sind Kurzschlussfest und besitzen auf Grund der positiven/negativen Schaltspannungen über eine hohe Reichweite.
Gekaufte I/O Karten erfüllen solche Anforderungen nur selten und wenn doch, dann sind sie teuer.

Viel Erfolg
Gräfin Klara
 
OP
R

radiergummi

Member
Gräfin Klara schrieb:
radiergummi schrieb:
Ist es Dir recht, hier über das Programm (dessen Einsatz) weiter zu posten?
Das hoffe ich doch!

Ich muss Dich fast enttäuschen, denn viel wird es nicht werden. Die Sache läuft seit heute Nachmittag wie erhofft und völlig ohne Probleme.

Den Reed-Kontakt habe ich mit den Pins 7 und 8 einer 9-poligen Sub-D-Buchse an /dev/ttyS0 meines Igels H710C angeschlossen.

Das folgende Shell-Skript schreibt die Zählerstände des Gaszählers seitdem fehlerfrei in eine MySQL-Datenbank.
Code:
#!/bin/bash
#
# Skript zum Auslesen der Zaehlimpulse eines Gaszaehlers
# Die letzte Stelle des Zaehlwerks _____,__x [m³] ist mit einem Magneten versehen,
# der bei jeder Umdrehung einmal einen Reed-Kontakt ueberstreicht und diesen schliesst.
# Der Kontakt ist nur für etwa 30° (weniger als 1/10) je Umdrehung geschlossen.
# Extremfaelle:
# kein Gasverbrauch - Kontakt kann dann dauerhaft offen oder geschlossen sein.
# Maximalverbrauch - Kontakt ist nur fuer 1/x s geschlossen x>3
# Ein Zyklus des Reed-Kontakts entspricht einem Gasverbrauch von 10 Litern.
#
# Ein C-Programm, devio (Graefin Klara, linux-club.de), stellt die Zustandswechsel des 
# Reed-Kontakts über die serielle Schnittstelle (RS232) fest.

init ()
{
# Schnittstelle
DEVICE="/dev/ttyS0"

# #------------------------------------------------------------------------
# # Verkabelung
# # 
# Sowohl Pin 4-DTR als auch Pin 7-RTS koennen ein- (-d1 bzw. -r1) oder 
# ausgeschaltet (-d0 bzw. -r0) werden
# Je nach Verkabelung koennen diese Signale unabhaengig auf den Pins 
# 1-DCD , 6-DSR, 8-CTS und 9-RI empfangen werden.
# 
# Signal-Pin bzw. Sender aus (0) oder an (1)
# 4-DTR = -d0 oder d1
# 7-RTS = -r0 oder r1
SND="-r1"

# ueberwachter Pin bzw. Receiver
# 1 DCD = 2
# 6 DSR = 1
# 8 CTS = 0
# 9 RI  = 3
REC=0

# SND -r1 und REC 0 => Reed-Kontakt zwischen Pin 7-RTS und Pin 8-CTS
#
# #------------------------------------------------------------------------

# Verzoegerung zwischen den Lesevoraengen
DELAY=0.22

# Zaehlerstand im Ausgangszustand. Ab hier wird hochgezaehlt.
# Zaehlerstand in Kubikmetern mit zwei Nachkommastellen angeben 
Zaehlerstand=42687.07

# Impulsvolumen in Kubikmetern (1.000 Liter)
IMPVOL=0.01 # 1 Impuls entspr. 10 Litern = 0.01 Kubikmeter

# Zustand Bereitschaft (0|1)
ALERT=0

# MySQL-Datenbank
HOST="myHost"
USER="myUser"
PASSWORD="myPassword"
DATABASE="myDatabase"
}


loop ()
{
while ( true ); do
	IFS=$','
	arr=( $(./devio/devio -i $DEVICE $SND -s) )
	# Bereitschaft, wenn der Kontakt geschlossen ist
	if [ ${arr[$REC]} -eq 1 ]
            then
            ALERT=1
	fi
	# wenn in Bereitschaft, dann auf Oeffnen des Kontakts achten
	if [ $ALERT -eq 1 ] 
            then
            if [ ${arr[$REC]} -eq 0 ]
                    then
                    # wenn Kontakt geoeffnet wurde
                    # Zaehlerstand um IMPVOL erhoehen
                    Zaehlerstand=$( echo "$Zaehlerstand+$IMPVOL" | bc -l )
                    echo "Zählerstand: " `date +%d.%m.%Y" "%H:%M:%S` $Zaehlerstand "m³"
                    mysql --host="$HOST" --user="$USER" --password="$PASSWORD" --database="$DATABASE" --batch --execute="insert into gasverbrauch (zaehlerstand) values ('$Zaehlerstand');" 2>/dev/null
                    # und Bereitschaft zuruecksetzen
                    ALERT=0
            fi
	fi
	# bis zum naechsten Zyklus warten
	sleep $DELAY
done
}

echo "ReadGasMeter v0.1"
init
loop

Das Timing ist erstaunlicherweise unkritisch. Momentan durchläuft das Programm 3-4 Zyklen bei geschlossenem Kontakt und 0,22s Pause. Ein nicht richtig erkannter Zustandswechsel - wobei nur das Öffnen wichtig ist - würde zu einem Fehler im Zeitstempel von etwa 1/4 Sekunde Führen - irrelevant.
Top meldet eine Prozessorbelastung zwischen 0,7 und 1%.

Problem bzw. Fakt:
wenn der Rechner stehenbleibt, muss das Skript zur Synchronisation des Zählerstandes angepasst werden.
EDIT 1: ich baue das noch um, da mich letztlich nur der momentane Gasverbrauch, also die Anzahl der Zählerimpulse (10l Gas) pro Zeiteinheit (5 min) interessieren.

Ausblick:
Das Skript schreibt auf, wie lange es dauert, bis 10l Gas verbrannt sind. Das nachgelagerte rrdtool, braucht aber Zählerstände in konstanten Zeitabständen. Daher werde ich rrdtool mit einem separaten Skript (select auf die Datenbank) füttern.
Dein Programm liefert mir ja vier überwachbare Leitungen je serieller Schnittstelle. Um die einfach nutzbar zu machen, werde ich mir ein "Patchfeld" bauen (RS232-Buchse mit angelöteter Lochrasterplatine) auf dem ich die zwei Aus- und vier Eingänge jumpern kann.

Das ist eine SUPER Lösung geworden, ich bin absolut happy. Vielen Dank für Deine Hilfe!

EDIT 2: Dein Programm wäre sicherlich auch für andere Anwender interessant.

Gruß,

Radiergummi
 
Du machst das sehr gut!
Dein script ist strukturiert und übersichtlich, die Dokumentation professionell.
Mein Programm ist bei dir in guten Händen. Das freut mich.

Wenn du was brauchst, z.B. eine bessere Synchronisation zwischen script und devio
oder ein exaktes Timing, das dein script triggert, dann gib mir Bescheid.
Möglich ist vieles, weil Hexen hexen können.

Gruß
Gräfin Klara
 
OP
R

radiergummi

Member
Ich wollte noch ein kurzes Feedback geben:

C-Programm und Skript zählen jetzt seit ca. 3-1/2 Wochen die Schaltwechsel am Reed-Kontakt und ich behaupte, daß noch kein softwareseitiger Fehler aufgetreten ist.

Es gab eine einzige Doppelzählung mit zeitlichem Versatz von 6 Sekunden. Die sind m. E. für Prellen viel zu lang und ich gehe von einem mechanischen Problem am Zählwerk aus. Für meine Belange ist das irrelevant.

Ja, ich könnte noch einbauen, dass Zählerimpulse mindestens x Sekunden auseinanderliegen müssen ...
(Therme auf Volllast hochfahren und mit der Stoppuhr die kleinste Umlaufzeit x des Zählerrädchens ermitteln)

Gruß,

Radiergummi
 
Oben