Meteostick unter Linux auslesen  (Gelesen 763 mal)

Offline Jürgen

    • http://www.wetterstation-porta.info
    • Männlich
  • Registriert:
    27.07.2001, 02:00:00
  • Beiträge: 4.716
  • Ort:
    Porta Westfalica
  • Station:
    Vantage Pro & WS2500PC
Meteostick unter Linux auslesen
am: 10.04.2016, 23:54:04
Hallo,

Um mir die Programmierung in einer Hochsprache zu ersparen, ist mein erster Ansatz, den Stick rein über Shellscripte auszulesen. Meine angedachte Vorgehensweise ist, daß der Stick einmal initialisiert wird und dann die empfangenen Daten per 'cat' in eine Datei umgeleitet werden. Per cronjob wird dabei alle 5 Minuten der laufende 'cat' gekillt und ein neuer gestartet, um die bisher erhalteten Daten zuwischenzuspeichern und weiter zu verarbeiten.
Das Ganze sieht dann so aus:
Initialisierung
# init
stty 115200 cs8 -parenb -crtscts -cstopb -echo -F /dev/meteostick

echo -e "r" >/dev/meteostick
sleep 10
echo -e "t2" >/dev/meteostick
sleep 1
echo -e "o1" >/dev/meteostick
sleep 1
echo -e "m1" >/dev/meteostick
sleep 1

Aulesen (per cronjob alle 5 Minuten)
# read
datetime=`date "+%Y%m%d%H%M"`
myTTY="/dev/meteostick"
myLOG="meteostick.txt"
myDATA="meteostick.dat"
cd /home/cfjh/projects/wst/meteostick/
killall cat
mv $myDATA meteostick$datetime.txt
cat <$myTTY | awk '{print strftime("%Y%m%d%H%M%S"),$1,$2,$3,$4,$5 | "tee -a $myLOG" ; }' >$myDATA
# cat $myTTY > $myLOG
Anmerkung dazu:
- Per udev-Regel wird der Stick immer als 'meteostick' zugewiesen, egal auf welchem ttyUSBx der erscheint
- das awk bei dem cat versieht jede Zeile mit einem Zeitstempel, damit die Daten später zeitlich zugeordnet werden können.

die Augabe sah dann so aus (es ist nur eine Bodenstation mit 2 Temp-Sensoren dran):

20151103140003 O 2 2 20.9 -50
20151103140011 L 2 1 0 -50
20151103140014 L 2 2 0 -51
20151103140016 O 2 1 20.2 -50
20151103140019 O 2 2 20.9 -49
20151103140027 L 2 1 0 -51
20151103140030 L 2 2 0 -51
20151103140032 O 2 1 20.2 -49
....

Das ganze lief in Probebetrieb ein paar Monate problemlos.
Doch dann war nach einem Onlineupdate ein Rechnerneustart erforderlich und zunächst funktionierte gar nichts mehr. Die Initialisierung des Sticks wurde zwar fehlerfrei gemacht, aber (es sah zumindest so aus), daß das Auslesen mit cat den Stick wieder resettete, warum auch immer(?). Alles was ich dann mit cat ausgelesen bekam, war die Initialisierungsmeldung.

20160328155502 # MeteoStick Version 2.3b1 (Apr
20160328155502
20160328155502 # License Information:
20160328155502
20160328155502 # MeteoStick (c) 2014-2015 by
20160328155502
20160328155502 ?
20160328155502
20160328155502 # Meteostick Version 2.3b1
20160328155502

Dann habe ich das Auslesescript so geändert, daß der Stick nach dem cat neu initialisiert wird (gleiches Script wie bei Init, jedoch ohne stty und dem R)eset). Das fuktionierte dann teilweise, allerdings schlichen sich überall Leerzeilen ein und bei der Initialisierung bekome ich zwischendurch den Fehler "unknown command"

20160401162002 # MeteoStick Version 2.3b1 (Apr
20160401162002
20160401162002 # License Information:
20160401162002
20160401162002 # MeteoStick (c) 2014-2015 by
20160401162002
20160401162002 ?
20160401162002
20160401162002 # Meteostick Version 2.3b1
20160401162002
20160401162003 t2
20160401162003
20160401162003 # listening to transmitter 2
20160401162003
20160401162003
20160401162003
20160401162003 # unknown command
20160401162003
20160401162004 o1
20160401162004
20160401162004 # output computed values
20160401162004
20160401162004
20160401162004
20160401162004 # unknown command
20160401162004
20160401162005 m1
20160401162005
20160401162005 # chip authentication ok: 14
20160401162005
20160401162005 # frequency band 868MHz (EU)
20160401162005
20160401162005 B 24.2 1014.30
20160401162005
20160401162005
20160401162005
20160401162005 # unknown command
20160401162005
20160401162005 O 2 2 22.2 -49
20160401162005
20160401162013 L 2 1 0 -51
20160401162013
20160401162016 L 2 2 0 -49
20160401162016
20160401162018 O 2 1 21.7 -49
20160401162018
20160401162021 O 2 2 22.2 -49
....

Nach einem weiteren Recherneustart infolge eines Onlineupdates ist dieser Leerzeichenspuk wieder weg, möglicherweise würde auch mein erster versuch mit einmaler Initialierung wieder funktionieren.

Ich muß auch dazu sagen, daß ich kein Spezialist im Erstellen Linux-Scripten bin, alles habe ich mit der Internetrecherche zusammengesucht.

Meine Frage:
1. Ist das Vorgehen so empfehlenswert? was könnte/sollte man ändern/verbessern?
2. Momentan gefällt mir der 'killall' zu Beenden des cat nicht, da der alle gerade im System laufenden cat's killen würde. Wie kann man das besser machen (wichtig ist aber, daß der für den Stick laufende cat immer gekillt wird)?
Jürgen

Wetterstationen.info Forum

Meteostick unter Linux auslesen
« am: 10.04.2016, 23:54:04 »

Offline falk

    • Wetterstation Scharnhausen
  • Registriert:
    15.04.2015, 07:28:04
  • Beiträge: 702
  • geogr. Position:
    48°42' 9°16'
  • Station:
    Davis Vantage Vue
Re: Meteostick unter Linux auslesen
Antwort #1 am: 11.04.2016, 10:49:12

Meine Frage:
1. Ist das Vorgehen so empfehlenswert? was könnte/sollte man ändern/verbessern?
2. Momentan gefällt mir der 'killall' zu Beenden des cat nicht, da der alle gerade im System laufenden cat's killen würde. Wie kann man das besser machen (wichtig ist aber, daß der für den Stick laufende cat immer gekillt wird)?

Hallo Jürgen,

ich lese ebenfalls über ein Skript den Stick aus. Das funktioniert so schon ein Jahr.

Ich öffne hierzu die serielle Schnittstelle und fange blockierende Anfragen durch einen Timeout von 5 Minuten ab. Wenn in diesem Zeitraum nichts empfangen wurde terminiert das Skript. Das Skript wird jedoch durch die Daemontools überwacht und automatisch neu gestartet. Mit der hierdurch neu angestoßenen Initialisierung klappt dann wieder die Datenübertragung. Bei mir läuft der Prozess also ständig.

Zu den Daemontools: http://www.asconix.com/howtos/debian/daemontools-supervise-debian-howto

Mein Python-Skript loggt die Sensor-Rohdaten (o0) in eine Datenbank, kann aber leicht modifiziert werden

#!/usr/bin/python
# -*- coding: iso-8859-15 -*-
'''
Created on 10.03.2015
@author: micha
'''

import logging
import serial
import time
import MySQLdb as mdb

from util import formatData, description, sensor

# the main procedure

logging.basicConfig(format='%(asctime)s\t%(levelname)s\t%(message)s', level=logging.INFO)
logging.info("Starting weather station sensor logging")

con = None
port = None

try:

    con = mdb.connect('localhost', 'davis', 'davis', 'davis');
    cur = con.cursor()

    port = serial.Serial(
        port='/dev/ttyUSB0',\
        baudrate=115200,\
        parity=serial.PARITY_NONE,\
        stopbits=serial.STOPBITS_ONE,\
        bytesize=serial.EIGHTBITS,\
        timeout=310)

    stage = 0;

    # main loop
    while True:
        ts_old = int(time.time())
        line = port.readline().strip()
        ts = int(time.time())
        if ts - ts_old > 300:
            logging.critical("Timeout!")
            break

        if stage == 0 and line[0] == '?':
            stage = 1
        elif stage == 1 and line[0] == '#':
            port.write("x200\n") # Threshold set to -100db
            stage = 2
        elif stage == 2 and line[0] == '#':
            port.write("t1\n") # Tansmitter 1
            stage = 3
        elif stage == 3 and line[0] == '#':
            port.write("f1\n") # Filter on
            stage = 4
        elif stage == 4 and line[0] == '#':
            port.write("o0\n") # Output original data
            stage = 5
        elif stage == 5 and line[0] == '#':
            port.write("m1\n") # Frequency band 1
            stage = 6
        elif stage == 6 and len(line) > 3:
            sid = line[0]
            if sid == 'B' or sid == 'I' or sid == 'W' or sid == 'T' or sid == 'R' or sid == 'P':
                cur.execute("INSERT INTO logger(dateTime,sensor,data,description) VALUES(%s,%s,%s,%s)", (ts,sensor(line),formatData(line),description(line)))
                con.commit()

except Exception, e:
    logging.critical(str(e))

finally:
    if con:
        con.close()
    if port:
        port.close()

Die Utility-Funktionen nur zur Vollständigkeit

# -*- coding: iso-8859-15 -*-
'''
Created on 12.03.2015
@author: buchfink
'''

import math

POLYNOMIAL = 0x1021
PRESET = 0

def _initial(c):
    crc = 0
    c = c << 8
    for _ in range(8):
        if (crc ^ c) & 0x8000:
            crc = (crc << 1) ^ POLYNOMIAL
        else:
            crc = crc << 1
        c = c << 1
    return crc

_tab = [ _initial(i) for i in range(256) ]

def _update_crc(crc, c):
    cc = 0xff & c

    tmp = (crc >> 8) ^ cc
    crc = (crc << 8) ^ _tab[tmp & 0xff]
    crc = crc & 0xffff

    return crc

# Calculates the crc
def crc(data):
    crc = PRESET
    for idx in range(2, 10):
        crc = _update_crc(crc, int(data[idx], 16))
    return crc

# Check line for crc
def check(line):
    return crc(line.split()) == 0

# formats the line
def formatData(line):
    data = line.split()
    if len(data) == 0 or data[0] != 'I':
        return line

    value = ""
    for idx in range(len(data)):
        if idx > 0:
            value += " "
        if idx >= 2 and idx < 10:
            value += ("0" + data[idx]) if len(data[idx]) == 1 else data[idx]
        elif idx == 10:
            value += (" " + data[idx])
        else:
            value += data[idx]
    return value

# returns the sensor id
def sensor(line):
    if len(line) == 0:
        return ' '
    elif line[0] <> 'I':
        return line[0]
    elif len(line) > 6:
        if line[6] == '2':
            return 'V'
        elif line[6] == '5':
            return 'R'
        elif line[6] == '7':
            return 'S'
        elif line[6] == '8':
            return 'T'
        elif line[6] == '9':
            return 'G'
        elif line[6] == 'A':
            return 'H'
        elif line[6] == 'E':
            return 'N'
        else:
            return 'I'
    else:
        return 'I'

# Parses values from line
def description(line):
    description = "unknown"
    data = line.split()

    # Pressure
    if data[0] == 'A' and len(data) == 7:
        p = round(float(data[4]) / 100.0, 2)
        description = "p=" + str(p) + "hPa"

    elif data[0] == 'B' and len(data) == 7:
        p = round(math.pow(math.pow(float(data[4])/100.0, 0.1902614) + 8.417168e-05 * 310.17, 5.255927), 1)
        description = "p=" + str(p) + "hPa"

    elif data[0] == 'I' and crc(data) != 0:
        return "invalid crc"

    elif data[0] == 'I':
        header = int(data[2], 16) >> 4
        windSpeed = int(round(int(data[3], 16) * 1.609344, 0))
        windDirections = (int(data[4], 16) << 2) | (int(data[6], 16) & 0x02)
        windDirections = 360 if windDirections > 1024 or windDirections <= 0 else int(round(windDirections * 360.0 / 1024.0)) 
        windData = " w(" + str(windSpeed) + "km/h, " + str(windDirections) + "°)"

        # akku voltage
        if header == 0x2:
            voltage = round(((int(data[5], 16) << 2) + ((int(data[6], 16) & 0xC0) >> 6)) / 100.0, 1)
            description = "v=" + str(voltage) + "V"

        elif header == 0x3:
            description = "unknown" # not implemented

        # rain rate
        elif header == 0x5:
            rr = 0
            rr1 = int(data[5], 16)
            rr2 = int(data[6], 16)
            if rr1 != 0xff:
                if (rr2 & 0x40) == 0:
                    rr = round(11520.0/(((rr2 & 0x30) << 4) | rr1), 1)
                elif (rr2 & 0x40) == 0x40:
                    rr = round(11520.0/(((rr2 & 0x30) << 8) | (rr1 << 4)), 1)
            description = "rr=" + str(rr) + "mm/h"

        # solar radiation
        elif header == 0x7:
            sol = (int(data[5], 16) << 2) + ((int(data[6], 16) & 0xC0) >> 6)
            description = "sol=" + str(sol)

        # temperature
        elif header == 0x8:
            value = int(data[5], 16) * 256 + int(data[6], 16)
            value = value - 65536 if value > 32767 else value
            temperature = round((value/160.0 - 32.0)*5.0/9.0, 1)
            description = "t=" + str(temperature) + "°C"

        # gust speed
        elif header == 0x9:
            gustSpeed = int(round(int(data[5], 16) * 1.609344, 0))
            description = "g=" + str(gustSpeed) + "km/h"

        # humidity
        elif header == 0xA:
            value = ((int(data[6], 16) >> 4) << 8) + int(data[5], 16)
            humidity = int(round(value * 1.01 / 10.0, 0))
            humidity = 100 if humidity > 100 else humidity
            description = "h=" + str(humidity) + "%"

        # rain ticks
        elif header == 0xE:
            ticks = value = int(data[5], 16) & 0x7f
            description = "r=" + str(ticks)

        description = "%-12s%s" % (description,windData)

    return description

Gruß falk

« Letzte Änderung: 11.04.2016, 10:58:46 von falk »

Offline Jürgen

    • http://www.wetterstation-porta.info
    • Männlich
  • Registriert:
    27.07.2001, 02:00:00
  • Beiträge: 4.716
  • Ort:
    Porta Westfalica
  • Station:
    Vantage Pro & WS2500PC
Re: Meteostick unter Linux auslesen
Antwort #2 am: 12.04.2016, 00:06:52
Hallo,

danke für die Info.
Mein Vorhaben ist, die Daten mit php weiter zu verarbeiten. Mit phyton kenne ich mich überhaupt nicht aus.

Mich würde vor allem interessieren, warum ich nach dem ersten Neustart das 'Problem' mit den Leerzeichen hatte bzw. wo die herkamen. In der späteren Datenverarbeitung ist das allerdings kein Problem, die kann ich dann einfach wegfiltern.
Jürgen

Offline falk

    • Wetterstation Scharnhausen
  • Registriert:
    15.04.2015, 07:28:04
  • Beiträge: 702
  • geogr. Position:
    48°42' 9°16'
  • Station:
    Davis Vantage Vue
Re: Meteostick unter Linux auslesen
Antwort #3 am: 12.04.2016, 10:55:36
Mich würde vor allem interessieren, warum ich nach dem ersten Neustart das 'Problem' mit den Leerzeichen hatte bzw. wo die herkamen. In der späteren Datenverarbeitung ist das allerdings kein Problem, die kann ich dann einfach wegfiltern.

Ich kann nur vermuten, dass hier vielleicht das serielle Device nicht richtig konfiguriert war  oder es liegt an einem Meteostick-Update. Mit der V2.3b hatte ich Probleme und bin deshalb auf die 2.2 zurück. Übrigens gibt es jetzt schon 2.4 und 2.5. Infos hierzu habe ich keine gefunden tau.meteobridge.com/files/

Um noch auf das killall zurückzukommen: Die Unix-Init-Skripte schreiben die Prozessnummer nach /var/run und nutzen dann beim Stoppen der Dienste diese Information. Vielleicht kannst du das nachbilden. Ist wesentlich eleganter als ein killall

Es gibt aber immer mehrere Wege und wenn es funktioniert...