Modbus: Elsner Wetterstation P03/3 auslesen.

Begonnen von klaus.schauer, 18 Februar 2019, 20:45:47

Vorheriges Thema - Nächstes Thema

klaus.schauer

Die Elsner Wetterstation P03/3 gibt es auch mit Modbus-Schnittstelle (RS485). Zwei Fragenkomplexe zur Integration über das ModebusAttr:

1. Lt. Datenblatt, siehe Anlage, hat das Gerät 18 Register. Das erste Register hat die Adresse "0". Kann das richtig sein? Im Modbus-Wiki werden Register definiert mit [cdih][1-9][0-9]*, also beginnend mit z. B. h1.
Ich bin mir nicht sicher, ob die Beschreibung fehlerhaft ist. In Tabelle 3.2 sind 18 Register aufgeführt. Unter der Tabelle 3.1 ist aber ein Modbus Master-Abfragestring als Beispiel aufgeführt, der alle Register auslesen soll. Dort sind als Anzahl der Register nur 17 (0x11) aufgeführt. Zählt die Anzahl der Register dort schon beginnend mit "0" statt mit "1"?

2. Wenn ich das Modbus-Wiki richtig verstehe, werden die Register jeweils einzeln definiert und ausgelesen. Für das Datum oder die Zeit sind das - bei diesem Gerät - jeweils drei Register. Nun wäre es notwendig, diese zu Readings date und time zusammenzufassen.
Ist dies mit Bordmitteln des Moduls ModebusAttr möglich oder wäre dafür ein neues spezifisches Modul "ModebusP03_3" zu schreiben?
In dem spezifischen Modul würde man dann die drei Basisreadings JJ, MM, TT sowie hh, mm, ss in zwei zusätzliche Readings JJ-MM-TT sowie hh:mm:ss überführen.

Danke schon vorab für ein paar sachdienliche Hinweise und Tipps.

StefanStrobel

Hallo Klaus,

Zitat
Im Modbus-Wiki werden Register definiert mit [cdih][1-9][0-9]*, also beginnend mit z. B. h1
Dann muss das Wiki mal überarbeitet werden. Im Code steht "obj-[cdih][0-9]+-reading" und h0 ist erlaubt.

ZitatWenn ich das Modbus-Wiki richtig verstehe, werden die Register jeweils einzeln definiert und ausgelesen
Das muss nicht so sein. Man kann einerseits Werte, die über mehrere Register gehen als ein Objekt auslesen (len=3) oder aber zwei Werte gleichzeitig lesen und dann zu einem Reading zusammen fassen. Für die SE10k Wechselrichter war das mal nötig.
Beispiel:

define MBETest ModbusAttr ...
attr MBETest obj-h801-reading test1
attr MBETest obj-h801-len 2
attr MBETest obj-h801-unpack s>s>
attr MBETest obj-h801-expr "$val[0] $val[1]"


Gruss
   Stefan

klaus.schauer

#2
Danke für die schnelle Rückmeldung. Also dann so?

'i8'  => {reading => 'date',
  name => 'day.month.year',
  expr => '$val[2] $val[1] $val[0]',
  format => '20%02d-%02d-%02d',
  unpack => 'nnn',
  len => 3,
  poll => 1
},
'i11' => {reading => 'time',
  name => 'hour:minutes:seconds',
  expr => '$val[0] $val[1] $val[2]',
  format => '%02d:%02d:%02d',
  unpack => 'nnn',
  len => 3,
  poll => 1
},


Und noch eine Frage:
- Wie kann ich ermitteln, wenn ein Reading geschrieben wurde? Auf Anhieb fällt mir eine Notify-Routine ein, die ich in das spez. Modul "ModbusP03_3.pm" einbauen würde. Ich würde gerne aus den generierten Attributen weitere ableiten, z. B. ein Reading mit einem Helligkeitsschwellwert zur Rollosteuerung. Gibt es dafür vielleicht auch schon eine andere bessere Lösung im Modbus-Modul?

StefanStrobel

Hallo,

unpack und expr mit mehreren Werten lassen sich leider bisher nicht mit format kombinieren.
format verwendet derzeit nur val[0].
Du müsstest daher die Formatierung per sprintf in die expr mit einbauen.

Was Deine zweite Frage angeht, so würde ich das außerhalb des Moduls mit einem Notify machen.

Gruß
    Stefan

klaus.schauer

#4
Nachdem ich jetzt ein Gerät zur Implementierung lauffähig habe, suche ich nach einem optimalen Ansatz.

1. Lösung, mit der sich die Rohdaten auswerten lassen:

my %ElsnerP03_3_ParseInfo = (
'i0'  => {reading => 'temperature',
  name => 'temperature',
  expr => '$val/10',
  format => '%.1f',
  unpack => 's>',
  poll => 1
},
'i1'  => {reading => 'sunSouth',
  name => 'sunSouth',
  expr => '$val*1000',
  format => '%01d',
  unpack => 'n',
  poll => 1
},
'i2'  => {reading => 'sunWest',
  name => 'sunWest',
  expr => '$val*1000',
  format => '%01d',
  unpack => 'n',
  poll => 1
},
'i3'  => {reading => 'sunEast',
  name => 'sunEast',
  expr => '$val*1000',
  format => '%01d',
  unpack => 'n',
  poll => 1
},
'i4'  => {reading => 'brightness',
  name => 'brightness',
  expr => '$val',
  format => '%01d',
  unpack => 'n',
  poll => 1
},
'i5'  => {reading => 'windSpeed',
  name => 'windSpeed',
  expr => '$val/10',
  format => '%.1f',
  unpack => 's>',
  poll => 1
},
'i6'  => {reading => 'GPS_isRaining',
  name => 'GPS/RTC_isRaining',
  map => '0:RTC_no_rain, 1:RTC_rain, 256:GPS_no_rain, 257:GPS_rain',
  poll => 1
},
'i7'  => {reading => 'date',
  name => 'yyyy-mm-tt',
                  expr => 'sprintf("%04d-%02d-%02d",
                   $val[2],
                   $val[1],
                   $val[0])',
  unpack => 'n3',
                  len => 3,
  poll => 1
},
'i10' => {reading => 'time',
  name => 'hh:mm:ss',
                  expr => 'sprintf("%02d:%02d:%02d",
             $val[0],
             $val[1],
             $val[2])',
  unpack => 'n3',
                  len => 3,
  poll => 1
},
'i13' => {reading => 'sunAzimuth',
  name => 'sunAzimuth',
  expr => '$val/10',
  format => '%.1f',
  unpack => 's>',
  poll => 1
},
'i14' => {reading => 'sunElevation',
  name => 'sunElevation',
  expr => '$val/10',
  format => '%.1f',
  unpack => 's>',
  poll => 1
},
'i15' => {reading => 'latitude',
  name => 'latitude',
  expr => '$val/100',
  format => '%.2f',
  unpack => 's>',
  poll => 1
},
'i16' => {reading => 'longitude',
  name => 'longitude',
  expr => '$val/100',
  format => '%.2f',
  unpack => 's>',
  poll => 1
}
);

my %ElsnerP03_3_DeviceInfo = (
'i' => {defShowGet => 1,
combine => 17
       }
);

Das funktioniert grundsätzlich sehr gut. Nachteilig dabei ist allerdings, dass die Rohdaten i. d. R. im Sekundentakt abgeholt werden sollten. Diese lassen die Logdatei sehr schnell wachsen. Auch müssen die Rohdaten noch geglättet bzw. gemittelt werden, was wiederum zu weiteren Readings führt.

2. Lösungsansatz mit ein "raw"-Reading, aus dem dann die eigentlichen Readings abgeleitet werden:

my %ModbusElsnerP03_3_ParseInfo = (
  'i0'  => {reading => 'raw',
            name => 'raw',
            expr => 'sprintf("%.1f %d %d %d %d %.1f %s %s %04d-%02d-%02d %02d:%02d:%02d %.1f %.1f %.2f %.2f",
             $val[0] / 10,
             $val[1] * 1000,
             $val[2] * 1000,
             $val[3] * 1000,
             $val[4],
             $val[5] / 10,
             ($val[6] & 0xFF00) == 256 ? "GPS" : "RTC",
             ($val[6] & 0x00FF) == 1 ? "yes" : "no",
             $val[9],
             $val[8],
             $val[7],
             $val[10],
             $val[11],
             $val[12],
             $val[13] / 10,
             $val[14] / 10,
             $val[15] / 100,
             $val[16] / 100)',
            unpack => 'n17',
            len => 17,
            poll => 1
          }
);

my %ModbusElsnerP03_3_DeviceInfo = (
  'i' => {defShowGet => 1,
          combine => 17
}
);

Das wäre ein etwas kompakterer Ansatz. Führt aber leider immer noch zu einer erheblichen Menge an eigentlich überflüssigen LOG-Einträgen.

Die Readings durch das Modbus-Modul ohne EVENT zu schreiben - falls das bisher überhaupt vorgesehen ist - führt aber wahrscheinlich auch nicht weiter, da das EVENT ja für den Aufruf der Weiterverarbeitungsroutinen benötigt wird.

3. Lösungsansatz mit Modbus-Erweiterung um die Funktion Provider-Routine:
Deshalb könnte ggf. eine andere Lösung sinnvoll sein. Statt durch das Modbus-Modul entsprechende Readings anlegen zu lassen, könnte vom Modbus-Modul eine Provider-Routine im Sub-Modul aufgerufen werden. Die Werte könnten in einem Hash mit Register- oder Readingname und Wert übergeben werden. Die Routine würde vergleichbar mit der Übergabe der seriellen Daten beim DevIo-Modul über

$hash->{ReadFn}  = "ModbusElsnerP03_3_Read"

in

ModbusElsnerP03_3_Initialize($)

definiert.

Wäre das ein gangbarer Ansatz und gesteht Interesse an dieser oder einer ähnlichen Erweiterung des Modbus-Moduls? Eine entsprechende Erweiterung würde mir eine sehr kompakte und komfortable Lösung erlauben.

StefanStrobel

Hallo Klaus,

ich könnte mir eine "ModbusReadingsFn" vorstellen, die ich aus ModbusLD_ParseObj heraus mit Readingsnamen und Value aufrufe bevor ReadingsBulkUpdate aufgerufen wird.
Falls die Funktion definiert ist und z.B. 1 zurückgibt, würde dann ReadingsBulkUpdate nicht aufgerufen und ModbusLD_ParseObj geht davon aus, dass das übergeordnete Modul sich selbst um die Readings kümmert.
Wäre das ok für Dich?

Gruss
    Stefan

klaus.schauer

#6
Na klar passt das. Also etwa so:


sub ModbusElsnerP03_3_Initialize($) {
#...
  $hash->{ModbusReadingsFn} = "ModbusElsnerP03_3_Eval";
#...
}

sub ModbusElsnerP03_3_Eval($$$) {
  my ($hash, $readingName, $readingVal) = @_;
  my $ctrl = 1;
  #...
  readingsSingleUpdate($hash, $readingName, $readingVal, 1);
  return $ctrl;
}

# Alternativ zu ModbusElsnerP03_3_Eval($$$)
#
# Aufrufparameter ($hash, \%readingsHash)
#my %readingsHash = ($readingName0 => $readingVal0, $readingName1 => $readingVal1, $readingName2 => $readingVal2);


sub ModbusElsnerP03_3_EvalHash($$) {
  my ($hash, $readingsHash) = @_;
  my $ctrl = 1;
  my ($readingVal0, $readingVal1, $readingVal2);
  $readingVal0 = $readingsHash->{readingName0}  if (exists $readingsHash->{readingName0});
  $readingVal1 = $readingsHash->{readingName1}  if (exists $readingsHash->{readingName1});
  $readingVal2 = $readingsHash->{readingName2}  if (exists $readingsHash->{readingName2});
  #...
  readingsBeginUpdate($hash);
  readingsBulkUpdateIfChanged($hash, 'readingName0', $readingVal0) if (defined $readingVal0);
  readingsBulkUpdateIfChanged($hash, 'readingName1', $readingVal0) if (defined $readingVal1);
  readingsBulkUpdateIfChanged($hash, 'readingName2', $readingVal0) if (defined $readingVal2);
  readingsEndUpdate($hash, 1);
  return $ctrl;
}

Die Variante ModbusElsnerP03_3_Eval($$$) wäre für die Wetterstation ok. Ich würde dann ein Reading 'raw' verwenden. Die zweite Variante ModbusElsnerP03_3_EvalHash($$) ist wohl universellen und ausbaufähiger. Ich tendiere deshalb eher zur zweiten Variante.

Ich hoffe ich habe Deine Vorstellungen so richtig interpretiert. Herzlichen Dank für die Unterstützung schon in voraus.

P. S.: Wäre es möglich und sinnvoll die empfangenen Registerinhalte ohne weitere Verarbeitung als Rohdaten (HEX-String) zu übergeben? Die Daten müssen ohnehin i. d. R. nachbearbeitet werden. Das würde häufig eine zweifache Formatierung (Übergabestring / Readingformatierung) ersparen.

StefanStrobel

Hallo Klaus,

ich habe mal eine erste Variante hier https://forum.fhem.de/index.php/topic,75638.285.html gepostet.
Die Variante mit mehreren Readings wäre ein größerer Eingriff gewesen.
Bezügl. Hex-String: meinst Du den Datenstring bevor er in die einzelnen Register zerlegt wurde? (falls ein Request mehrere Register kombiniert)
Im Prinzip könnte ich auch eine User-Funktion statt ModbusLD_ParseObj aufrufen, aber in den allermeisten Fällen wird man das nicht wollen ...

Gruss
   Stefan

klaus.schauer

#8
Herzlichen Dank, ich werde mich umgehend an die Tests machen.

Bei der Übergabe im HEX-Format meinte ich das bezogen auf das einzelne jeweils in x_ParseInfo definierte Reading. Das Reading kann ja mehrere nacheinander folgende Register umfassen.

my %ModbusElsnerP03_3_ParseInfo = (
  'i0'  => {reading => 'raw',
            name => 'raw',
            expr => 'sprintf("%.1f %d %d %d %d %.1f %s %s %04d-%02d-%02d %02d:%02d:%02d %.1f %.1f %.2f %.2f",
             $val[0] / 10,
             $val[1] * 1000,
             $val[2] * 1000,
             $val[3] * 1000,
             $val[4],
             $val[5] / 10,
             $val[6] == 1 ? "GPS" : "RTC",
             $val[7] == 1 ? "yes" : "no",
             $val[10],
             $val[9],
             $val[8],
             $val[11],
             $val[12],
             $val[13],
             $val[14] / 10,
             $val[15] / 10,
             $val[16] / 100,
             $val[17] / 100)',
            unpack => 's>nnnnnCCnnnnnnns>s>s>',
            len => 17,
            poll => 1
          }
);

Man könnte dann notwendige Rechen- und Formatierungsoperationen erst nach der Übergabe an die Modul-Routine ModbusReadingsFn dort selbst ausführen. Das spart u. U. mehrfaches pack/unpack und andere Formatierungsbefehle. Aber bitte keine großen Klimmzüge machen, das ist eher ein nachgelegtes Problem. Es wird auch mit den bestehenden Funktionen gehen.

P. S. Test ist ok. Danke für die Unterstützung. Damit komme ich jetzt weiter.

klaus.schauer

Das Modul ist jetzt soweit, um es zu veröffentlichen. Die Änderungen am Modbus-Modul passen gut. Wann ist geplant, diese Änderungen einzuchecken? Ich würde mich dann mit dem neuen Modul 98_ModbusElsnerWS anschließen.

StefanStrobel

Habs gerade eingecheckt.

Gruss
   Stefan

klaus.schauer