Gavazzi EM24 mit Modbus Modul auslesen

Begonnen von stefan.apetz, 10 Oktober 2017, 11:39:36

Vorheriges Thema - Nächstes Thema

stefan.apetz

Hallo liebe fhem Gemeinde.

Zunächst das Szenario:

Ich habe einen Gavazzi EM24 Stromzähler mit Modbus Anschluss. (Protokolldokumentation: https://www.ccontrols.com/support/dp/CarloGavazziEM24.pdf

Ich möchte, wenn an einer Phase ein negativer Wert anliegt (entspricht Einspeisung durch meine Solaranlage ins Netz) einen Verbraucher in fhem dazuschalten.

Ich habe zwei Raspberrys am Start (da der Modus-Anschluss in einem anderen Raum ist als mein fhem Raspi nutze ich den einen um das Modbus Signal mit USB Adapter auf RS485 zu bringen und mit mit ser2eth ins Lan zu speisen). Mein fhem Raspi hört nun brav auf das Modbus Signal vom ersten Raspberry.

Dazu habe ich in der fhem.cfg eingetragen:

# Modbus
define PWP_Modbus Modbus sma-control:4001 RTU
attr PWP_Modbus room Modbus


define Gavazzi ModbusSDM630M 1
attr Gavazzi userattr IODev
attr Gavazzi IODev PWP_Modbus
attr Gavazzi room Modbus


--> Klappt prima und ich kann Werte auslesen. Diese sehen soweit auch korrekt aus. (angefügtes Bild gavazzi_watt_auf_l2, der Wert muss durch 10 geteilt werden, dann ist das die korrekte Wattzahl auf der Phase L2).

Das funktioniert bei allen Registern. Auch bei der Abfrage des Wertes Watt auf L1. Aber: dies ist die Einspeisephase meiner Solaranlage. Solange der Wert positiv ist (also Netzbezug erfolgt) bekomme ich einen korrekten Wert. Sobald die Solaranlage produziert und ein negativer Netzbezug auf dem Zähler auf der Phase ist (=Einspeisung) liefert mir fhem nur Fehlermeldungen zurück.

Meine angepasste Version vom Modul 98_ModbusSDM630.pm sieht wie folgt aus:


##############################################
# $Id: 98_ModbusSDM630M.pm
#
# fhem Modul für Stromzähler SDM630M von B+G E-Tech & EASTON
# verwendet Modbus.pm als Basismodul für die eigentliche Implementation des Protokolls.
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Changelog:
# 2015-01-15 initial release
# 2015-01-29 mit register-len, neue Namen für readings
# 2015-01-31 command reference angepasst
# 2015-02-01 fCodeMap -> devicveInfo Hash, Defaults für: timeouts, delays, Längen, Unpack und Format
# 2015-02-14 führende Nullen entfernt; defaultpolldelay, hint, map eingebaut
# 2015-02-17 defPoll, defShowGet Standards in deviceInfo eingebaut, showget und defaultpoll aus parseInfo entfernt
# defaultpoll=once verwendet, x[0-9] Format für defaultpolldelay verwendet
# 2015-02-23 ModbusSDM630M_Define & ModbusSDM630M_Undef entfernt
# ModbusSDM630M_Initialize angepasst
# %SDM630MparseInfo --> %parseInfo
# 2015-02-27 alle Register vom SDM630M eingebaut, Zyklenzeiten überarbeitet
# 2015-03-14 Anpassungan an neues 98_Modbus.pm, defaultpoll --> poll, defaultpolldelay --> polldelay,
# attribute für timing umbenannt,
# parseInfo --> SDM630MparseInfo, deviceInfo --> SDM630MdeviceInfo
# 2015-06-01 Register 160-164 für Erzeugung L1-L3 hinzugefügt
# 2015-09-15 alle h-Register nun mit format; %-Zeichen im format --> %%; Änderung format string zu '%s', wenn map verwendet wird
# 2015-11-19 alle Readings nun ohne Einheiten
# 2015-11-21 Timings (polldelay) verändert, um öfteres Auslesen wichtiger Werte (Leistung) zu ermöglichen
# Power__W 1* 10s
# Current__A 3* 30s
# Energy__kWh 5* 50s
# Power__VA__VAr 7* 1min 10s
# Voltage__V 11* 1min 50s
# Frequency__Hz 13* 2min 10s
# PowerFactor 17* 2min 50s
# CosPhi__grd 21* 3min 30s
# 2016-02-28 __% --> __prz (da % in ReadingName nicht mehr erlaubt)
# Phasenverschiebung Einheit __grd

package main;

use strict;
use warnings;
use Time::HiRes qw( time );

sub ModbusSDM630M_Initialize($);

# deviceInfo defines properties of the device.
# some values can be overwritten in parseInfo, some defaults can even be overwritten by the user with attributes if a corresponding attribute is added to AttrList in _Initialize.
#
my %SDM630MdeviceInfo = (
"timing" => {
timeout => 2, # 2 seconds timeout when waiting for a response
commDelay => 0.7, # 0.7 seconds minimal delay between two communications e.g. a read a the next write,
# can be overwritten with attribute commDelay if added to AttrList in _Initialize below
sendDelay => 0.7, # 0.7 seconds minimal delay between two sends, can be overwritten with the attribute
# sendDelay if added to AttrList in _Initialize function below
},
"i" => { # details for "input registers" if the device offers them
read => 4, # use function code 4 to read discrete inputs. They can not be read by definition.
defLen => 2, # default length (number of registers) per value ((e.g. 2 for a float of 4 bytes that spans 2 registers)
# can be overwritten in parseInfo per reading by specifying the key "len"
combine => 40, # allow combined read of up to 10 adjacent registers during getUpdate
# combine => 1, # no combined read (read more than one registers with one read command) during getUpdate
defFormat => "%.1f", # default format string to use after reading a value in sprintf
# can be overwritten in parseInfo per reading by specifying the key "format"
defUnpack => "n", # "f>", # default pack / unpack code to convert raw values, e.g. "n" for a 16 bit integer oder
# "f>" for a big endian float IEEE 754 floating-point numbers
# can be overwritten in parseInfo per reading by specifying the key "unpack"
defPoll => 1, # All defined Input Registers should be polled by default unless specified otherwise in parseInfo or by attributes
defShowGet => 1, # default für showget Key in parseInfo
},
"h" => { # details for "holding registers" if the device offers them
read => 3, # use function code 3 to read holding registers.
write => 16, # use function code 6 to write holding registers (alternative could be 16)
defLen => 2, # default length (number of registers) per value (e.g. 2 for a float of 4 bytes that spans 2 registers)
# can be overwritten in parseInfo per reading by specifying the key "len"
combine => 10, # allow combined read of up to 10 adjacent registers during getUpdate
defUnpack => "n", #"f>", # default pack / unpack code to convert raw values, e.g. "n" for a 16 bit integer oder
# "f>" for a big endian float IEEE 754 floating-point numbers
# can be overwritten in parseInfo per reading by specifying the key "unpack"
defShowGet => 1, # default für showget Key in parseInfo
},
);

# %parseInfo:
# r/c/i+adress => objHashRef (h = holding register, c = coil, i = input register, d = discrete input)
# the address is a decimal number without leading 0
#
# Explanation of the parseInfo hash sub-keys:
# name internal name of the value in the modbus documentation of the physical device
# reading name of the reading to be used in Fhem
# set can be set to 1 to allow writing this value with a Fhem set-command
# setmin min value for input validation in a set command
# setmax max value for input validation in a set command
# hint string for fhemweb to create a selection or slider
# expr perl expression to convert a string after it has bee read
# map a map string to convert an value from the device to a more readable output string
# or to convert a user input to the machine representation
# e.g. "0:mittig, 1:oberhalb, 2:unterhalb"
# setexpr per expression to convert an input string to the machine format before writing
# this is typically the reverse of the above expr
# format a format string for sprintf to format a value read
# len number of Registers this value spans
# poll defines if this value is included in the read that the module does every defined interval
# this can be changed by a user with an attribute
# unpack defines the translation between data in the module and in the communication frame
# see the documentation of the perl pack function for details.
# example: "n" for an unsigned 16 bit value or "f>" for a float that is stored in two registers
# showget can be set to 1 to allow a Fhem get command to read this value from the device
# polldelay if a value should not be read in each iteration after interval has passed,
# this value can be set to a multiple of interval

my %SDM630MparseInfo = (
# Spannung der Phasen, nur bei jedem 11. Zyklus
"i0" => { # input register 0x0000
name => "V L1 - N", # internal name of this register in the hardware doc
reading => "V_L1", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i2" => { # input register 0x0002
name => "V L2 - N", # internal name of this register in the hardware doc
reading => "V_L2", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i4" => { # input register 0x0004
name => "V L3 - N", # internal name of this register in the hardware doc
reading => "V_L3", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},

# Spannung zwischen den Phasen
"i6" => { # input register 0x0006
name => "V L1-L2", # internal name of this register in the hardware doc
reading => "V_L1_L2", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it

},
"i8" => { # input register 0x0008
name => "V L2 - L3", # internal name of this register in the hardware doc
reading => "V_L2_L3", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i10" => { # input register 0x000A
name => "V L3 - L1", # internal name of this register in the hardware doc
reading => "V_L3_L1", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x11", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},

# Strom der einzelnen Phasen
"i12" => { # input register 0x000C, Phase 1:Strom
name => "A L1", # internal name of this register in the hardware doc
reading => "A_L1", # name of the reading for this value
format => '%.f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
# unpack => "f>",
},
"i14" => { # input register 0x000E, Phase 2: Strom
name => "A L2", # internal name of this register in the hardware doc
reading => "A_L2", # name of the reading for this value
format => '%.f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i16" => { # input register 0x0010, Phase 3 Strom
name => "A L3", # internal name of this register in the hardware doc
reading => "A_L3", # name of the reading for this value
format => '%.f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},

# Leistung Phasen 1 bis 3
"i18" => { # input register 0x0012, Phase 1 Watt
# read            => 4,
# len => 2,
# unpack => "i",
name => "W L1", # internal name of this register in the hardware doc
reading => "W_L1", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i20" => { # input register 0x0014, Phase 2: Watt
name => "W L2", # internal name of this register in the hardware doc
reading => "W_L2", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i22" => { # input register 0x0016, Phase 3: Watt
name => "W L3", # internal name of this register in the hardware doc
reading => "W_L3", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x3", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},

# Blindleistung in VAr, nur bei jedem 17. Zyklus
"i24" => { # input register 0x0018
name => "VA L1", # internal name of this register in the hardware doc
reading => "VA_L1", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x17", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i26" => { # input register 0x001A
name => "VA L2", # internal name of this register in the hardware doc
reading => "VA_L2", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x17", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},
"i28" => { # input register 0x001C
name => "VA L3", # internal name of this register in the hardware doc
reading => "VA_L3", # name of the reading for this value
format => '%.1f', # format string for sprintf
polldelay => "x17", # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
},



"i70"   =>      {       # input register 0x046C
                                        name            => "KWh (+) L1", # internal name of this register in the hardware doc
                                        reading         => "KWh_L1",            # name of the reading for this value
                                        format          => '%.1f',              # format string for sprintf
                                        polldelay       => "x17",               # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
                                },

"i72"   =>      {       # input register 0x048C
                                        name            => "KWh (+) L2",        # internal name of this register in the hardware doc
                                        reading         => "KWh_L2",            # name of the reading for this value
                                        format          => '%.1f',              # format string for sprintf
                                        polldelay       => "x17",               # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
                                },


"i74"   =>      {       # input register 0x04AC
                                        name            => "KWh (+) L3",        # internal name of this register in the hardware doc
                                        reading         => "KWh_L3",            # name of the reading for this value
                                        format          => '%.1f',              # format string for sprintf
                                        polldelay       => "x17",               # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
                                },

         "i92"   =>      {       # input register 0x05CC
                                        name            => "KWh (-) Total",        # internal name of this register in the hardware doc
                                        reading         => "KWh_negativ_Total",            # name of the reading for this value
                                        format          => '%.1f',              # format string for sprintf
                                        polldelay       => "x17",               # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
                                },






###############################################################################################################
# Holding Register
###############################################################################################################
"h0" => { # holding register 0x0000
# Read minutes into first demand calculation.
# When the Demand Time reaches the Demand Period then the demand values are valid.
name => "Demand Time", # internal name of this register in the hardware doc
reading => "Demand_Time__minutes", # name of the reading for this value
# format => '%.f min', # format string for sprintf
format => '%.f', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h2" => { # holding register 0x0002
# Write demand period: 0,5,8,10,15,20,30 or 60 minutes, default 60
# Setting the period to 0 will cause the demand to show the current parameter value,
# and demand max to show the maximum parameter value since last demand reset.
name => "Demand Period", # internal name of this register in the hardware doc
reading => "Demand_Period__minutes",# name of the reading for this value
# format => '%.f min', # format string for sprintf
format => '%.f', # format string for sprintf
hint => "0,5,8,10,15,20,30,60", # string for fhemweb to create a selection or slider
min => 0, # input validation for set: min value
max => 60, # input validation for set: max value
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h6" => { # holding register 0x0006
name => "system voltage", # internal name of this register in the hardware doc
reading => "System_Voltage__V", # name of the reading for this value
# format => '%.1f V', # format string for sprintf
format => '%.1f', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h8" => { # holding register 0x0008
name => "system current", # internal name of this register in the hardware doc
reading => "System_Current__A", # name of the reading for this value
# format => '%.2f A', # format string for sprintf
format => '%.2f', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h10" => { # holding register 0x000A
# Write system type: 1=1p2w, 2=3p3w, 3=3p4w
# Requires password, see register Password 0x0018
name => "System Type", # internal name of this register in the hardware doc
reading => "System_Type", # name of the reading for this value
map => "1:1p2w, 2:3p3w, 3:3p4w",# map to convert visible values to internal numbers (for reading and writing)
hint => "1,2,3", # string for fhemweb to create a selection or slider
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h12" => { # holding register 0x000C
# Write relay on period in milliseconds: 60, 100 or 200, default 200
name => "Relay 1 Pulse Width", # internal name of this register in the hardware doc
reading => "System_Pulse_Width__ms",# name of the reading for this value
# format => '%.f ms', # format string for sprintf
format => '%.f', # format string for sprintf
hint => "60,100,200", # string for fhemweb to create a selection or slider
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h14" => { # holding register 0x000E
# Write any value to password lock protected registers.
# Read password lock status: 0=locked, 1=unlocked
# Reading will also reset the password timeout back to one minute.
name => "Password Lock", # internal name of this register in the hardware doc
reading => "System_Password_Lock", # name of the reading for this value
map => "0:locked, 1:unlocked", # map to convert visible values to internal numbers (for reading and writing)
hint => "0,1", # string for fhemweb to create a selection or slider
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h18" => { # holding register 0x0012
# Write the network port parity/stop bits for MODBUS Protocol, where:
# 0 = One stop bit and no parity, default.
# 1 = One stop bit and even parity.
# 2 = One stop bit and odd parity.
# 3 = Two stop bits and no parity.
# Requires a restart to become effective.
name => "Network Parity Stop", # internal name of this register in the hardware doc
reading => "Modbus_Parity_Stop", # name of the reading for this value
map => "0:1stop.bit_no.parity, 1:1stop.bit_even.parity, 2:1stop.bit_odd.parity, 3:2stop.bits_no.parity", # map to convert vi
sible values to internal numbers (for reading and writing)
hint => "0,1,2,3", # string for fhemweb to create a selection or slider
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h20" => { # holding register 0x0014
# Write the network port node address: 1 to 247 for MODBUS Protocol, default 1.
# Requires a restart to become effective.
name => "Network Node", # internal name of this register in the hardware doc
reading => "Modbus_Node_adr", # name of the reading for this value
min => 1, # input validation for set: min value
max => 247, # input validation for set: max value
format => '%u', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h22" => { # holding register 0x0016
# Write pulse divisor index: n= 1 to 5
# 1=0.01kw/imp; 2=0.1kw/imp; 3=1kw/imp; 4=10kw/imp; 5=100kw/imp
name => "Pulse Divisor1", # internal name of this register in the hardware doc
reading => "Pulse_Divisor_1", # name of the reading for this value
map => "1:0.01kw/imp, 2:0.1kw/imp, 3:1kw/imp, 4:10kw/imp, 5:100kw/imp", # map to convert visible values to internal numbers
(for reading and writing)
hint => "1,2,3,4,5", # string for fhemweb to create a selection or slider
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h24" => { # holding register 0x0018
# Write password for access to protected registers.
name => "Password", # internal name of this register in the hardware doc
reading => "System_Password", # name of the reading for this value
set => 1, # this value can be set
format => '%u', # format string for sprintf
},

"h28" => { # holding register 0x001C
# Write the network port baud rate for MODBUS Protocol, where:
# 0=2400; 1=4800; 2=9600; 3=19200; 4=38400;
# Requires no restart, wird sofort active!
name => "Network Baud Rate", # internal name of this register in the hardware doc
reading => "Modbus_Speed__baud", # name of the reading for this value
map => "0:2400, 1:4800, 2:9600, 3:19200, 4:38400", # map to convert visible values to internal numbers (for reading and writin
g)
hint => "0,1,2,3,4", # string for fhemweb to create a selection or slider
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
set => 1, # this value can be set
},

"h36" => { # holding register 0x0024
# Read the total system power, e.g. for 3p4w returns System Volts x System Amps x 3.
name => "System Power", # internal name of this register in the hardware doc
reading => "System_Power__W", # name of the reading for this value
# format => '%.f W', # format string for sprintf
format => '%.f', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h42" => { # holding register 0x002A
name => "Serial Number", # internal name of this register in the hardware doc
reading => "System_Serial_Nr", # name of the reading for this value
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

"h86" => { # holding register 0x0056
# Write MODBUS Protocol input parameter for pulse relay 1:
# 37 = Total Wh or 39 = Total VArh, default 39
name => "Relay l Energy Type", # internal name of this register in the hardware doc
reading => "Relay1_Energy_Type", # name of the reading for this value
map => "37:Total Wh, 39:Total VArh", # map to convert visible values to internal numbers (for reading and writing)
format => '%s', # format string for sprintf
poll => 0, # not be polled by default, unless specified otherwise by attributes
set => 1, # this value can be set
},

"h88" => { # holding register 0x0058
name => "Relay 2 Energy Type", # internal name of this register in the hardware doc
reading => "Relay2_Energy_Type", # name of the reading for this value
format => '%s', # format string for sprintf
poll => "once", # only poll once after define (or after a set)
},

# Ende parseInfo
);


#####################################
sub
ModbusSDM630M_Initialize($)
{
    my ($modHash) = @_;

require "$attr{global}{modpath}/FHEM/98_Modbus.pm";

$modHash->{parseInfo}  = \%SDM630MparseInfo; # defines registers, inputs, coils etc. for this Modbus Defive

$modHash->{deviceInfo} = \%SDM630MdeviceInfo; # defines properties of the device like
# defaults and supported function codes

ModbusLD_Initialize($modHash); # Generic function of the Modbus module does the rest

$modHash->{AttrList} = $modHash->{AttrList} . " " . # Standard Attributes like IODEv etc
$modHash->{ObjAttrList} . " " . # Attributes to add or overwrite parseInfo definitions
$modHash->{DevAttrList} . " " . # Attributes to add or overwrite devInfo definitions
"poll-.* " . # overwrite poll with poll-ReadingName
"polldelay-.* "; # overwrite polldelay with polldelay-ReadingName
}


1;

=pod
=begin html

<a name="ModbusSDM630M"></a>
<h3>ModbusSDM630M</h3>
<ul>
    ModbusSDM630M uses the low level Modbus module to provide a way to communicate with SDM630M smart electrical meter from B+G E-Tech & EASTON.
It defines the modbus input and holding registers and reads them in a defined interval.

<br>
    <b>Prerequisites</b>
    <ul>
        <li>
          This module requires the basic Modbus module which itsef requires Device::SerialPort or Win32::SerialPort module.
        </li>
    </ul>
    <br>

    <a name="ModbusSDM630MDefine"></a>
    <b>Define</b>
    <ul>
        <code>define &lt;name&gt; ModbusSDM630M &lt;Id&gt; &lt;Interval&gt;</code>
        <br><br>
        The module connects to the smart electrical meter with Modbus Id &lt;Id&gt; through an already defined modbus device and actively requests data from the
        smart electrical meter every &lt;Interval&gt; seconds <br>
        <br>
        Example:<br>
        <br>
        <ul><code>define SDM630M ModbusSDM630M 1 60</code></ul>
    </ul>
    <br>

    <a name="ModbusSDM630MConfiguration"></a>
    <b>Configuration of the module</b><br><br>
    <ul>
        apart from the modbus id and the interval which both are specified in the define command there is nothing that needs to be defined.
However there are some attributes that can optionally be used to modify the behavior of the module. <br><br>
       
        The attributes that control which messages are sent / which data is requested every &lt;Interval&gt; seconds are:

        <pre>
poll-Energy_total__kWh
poll-Energy_import__kWh
poll-Energy_L1_total__kWh
poll-Energy_L2_total__kWh
poll-Energy_L3_total__kWh
</pre>
       
        if the attribute is set to 1, the corresponding data is requested every &lt;Interval&gt; seconds. If it is set to 0, then the data is not requested.
        by default the temperatures are requested if no attributes are set.
        <br><br>
        Example:
        <pre>
        define SDM630M ModbusSDM630M 1 60
        attr SDM630M poll-Energy_total__kWh 0
        </pre>
    </ul>

    <a name="ModbusSDM630M"></a>
    <b>Set-Commands</b><br>
    <ul>
        The following set options are available:
        <pre>
        </pre>
    </ul>
<br>
    <a name="ModbusSDM630MGet"></a>
    <b>Get-Commands</b><br>
    <ul>
        All readings are also available as Get commands. Internally a Get command triggers the corresponding
        request to the device and then interprets the data and returns the right field value. To avoid huge option lists in FHEMWEB, only the most important Get options
        are visible in FHEMWEB. However this can easily be changed since all the readings and protocol messages are internally defined in the modue in a data structure
        and to make a Reading visible as Get option only a little option (e.g. <code>showget => 1</code> has to be added to this data structure
    </ul>
<br>
    <a name="ModbusSDM630Mattr"></a>
    <b>Attributes</b><br><br>
    <ul>
<li><a href="#do_not_notify">do_not_notify</a></li>
        <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
        <br>
<li><b>poll-Energy_total__kWh</b></li>
<li><b>poll-Energy_import__kWh</b></li>
<li><b>poll-Energy_L1_total__kWh</b></li>
<li><b>poll-Energy_L2_total__kWh</b></li>
<li><b>poll-Energy_L3_total__kWh</b></li>
            include a read request for the corresponding registers when sending requests every interval seconds <br>
        <li><b>timeout</b></li>
            set the timeout for reads, defaults to 2 seconds <br>
<li><b>minSendDelay</b></li>
minimal delay between two requests sent to this device
<li><b>minCommDelay</b></li> 
minimal delay between requests or receptions to/from this device
    </ul>
    <br>
</ul>

=end html
=cut


Ich habe mehrere Forumseinträge besucht und nicht so richtig etwas gefunden, was mir hilft. Unter anderem hier:

https://forum.fhem.de/index.php/topic,57257.msg672500.html#msg672500

Da spricht Heinz von revRegs. Was ist damit gemeint?

Ich stehe nur noch vor der Herausforderung, dass ich die negativen Werte (INT32, signed) aus dem Register bekommen muss.

Wo habe ich den Knoten im Kopf?

Wenn das gelungen ist würde ich gerne auch noch die zurückgelieferten Werte korrigieren. In der Protokolldokumentation steht immer ein Faktor mit angegeben (z.B. Value weight Watt * 10). Diese Werte möchte ich dann gleich beim Auslesen wieder durch die vom Zähler erhöhten Werte reduzieren (also hier gleich Watt / 10) um die korrekte Wattzahl in den Readings zu haben.

Danke für jede Unterstützung!
Stefan

StefanStrobel

#1
Hallo Stefan,

der Schlüssel zum Erfolg sind die richtigen pack- / unpack-codes.
Siehe http://perldoc.perl.org/functions/pack.html

Du verwendest bei Input-Registern per Default "n", das ist ein unsigned integer aus 16 Bit.
Folglich kann es bei Input Register 18 bzw. 20 mit negativen Werten nicht klappen. Das "i" hast Du zwar bei 18 schon mal hingeschrieben, aber auskommentiert.

Das mit RevRegs ist nur relevant, wenn Du Objekte mit mehr als 16 Bits (also mehr als einem Register) verarbeitest. Dann kannst Di mit RevRegs die Reihenfolge der 16-Bit-Wörter vertauschen.
Bei 16-Bit-Werten benötigt man das nicht, da gibt es ja schon die verschiedenen Unpack-Codes wie zum Beispiel "n" versus "v".

Übrigens musst Du zum Testen nicht den Code eines Moduls ändern, sondern Du kannst die Attribute von ModbusAttr verwenden. Die funktionieren auch bei anderen Modulen, die auf Modbus.pm basieren, haben aber eine höhere Priorität als die im parseInfo definierten Informationen und "überstimmen" diese sozusagen.
Aus den Attributen von ModbusAttr kannst Du Dir dann mit set saveAsModule ein Modul erzeugen lassen.

Gruss
   Stefan

stefan.apetz

#2
Hallo Stefan,

das auskommentierte "i" kommt von den Versuchen die ich gemacht hatte. Die Dokumentation für Unpack hatte ich auch schon gefunden, aber leider auch nur mit halben Erfolg.

Die Lösung ist wirklich die Byteorder. Die Wattzahlen liegen als int32 vor.

So geht es jetzt (zumindest bei positiven Zahlen):


"i18"    =>    {    # input register 0x0014, Phase 1: Watt
                    len         =>  2,          # lenth words according to manual 2
                    unpack      =>  "I>",         # format INT32
                    revRegs     =>  '1',            # format of the INT word: INT32, UINT32 and UINT64 formats, the word order is LSW-> MSW.
                    expr        => '$val / 10',    # Wert durch 10 teilen
                    name        => "W L1",       # internal name of this register in the hardware doc
                    reading        => "W_L1",   # name of the reading for this value
                    format        => '%.1f',      # format string for sprintf
                    polldelay    => "x3",         # only poll this Value if last read is older than x*Iteration, otherwiese getUpdate will skip it
                },


Allerdings: auch wieder nur bei positiven Zahlen.

Ich hatte gerade 0,8 W auf der Leitung. Dann wurde es jetzt immer heller und somit muss das ins Negative gefallen sein.

Jetzt bekomme ich wieder keine Werte und immer nur Parse Error oder CRC Error:

ParseFrames got wrong Checksum (expect 4288, got 35889)

und ab und an Meldungen wie

unexpected function code 3 from 1, expecting fc 4 from 1 for device Gavazzi

Die hatte ich aber teilweise vorher auch schon. Das liegt - so vermute ich mal - daran, dass der Raspi nicht alleine auf dem Bus arbeitet sondern parallel zum Wechselrichter und dann "Kollisionen" auf dem Bus auftreten.


Was ich nicht verstanden habe war der zweite Teil deiner Antwort mit den Attributen. Ich sehe solche Attributauflistungen in der Oberfläche von fhem. Wenn ich es aber richtig verstanden habe kann ich damit über die fhem Eingabeoberfläche nichts anfangen, sondern muss das direkt in der fhem.cfg eintragen?

Wie würde dort eine konkrete Definition für obiges Registerauslesen aussehen?

Zur Info noch folgendes aus der Doku:

The variables are represented by integers or floating numbers, with 2's complement notation in case of "signed" format, using the following:

Format        IEC data type     Description                    Bits             Range
INT16          INT                   Integer                         16               -32768 .. 32767
UINT16        UINT                 Unsigned integer           16                0 .. 65535
INT32          DINT                 Double integer               32                -2^31 .. 2^31
UINT32        UDINT               Unsigned double int       32                 0 .. 2^32-1
UINT64        ULINT                Unsigned long integer    64                 0 .. 2^64-1
IEEE754                               SP Single-precision        32                  -(1+[1 –2^-23])x2^127 .. 2^128
                                           floatingpoint

For all the formats the byte order (inside the single word) is MSB->LSB. In INT32, UINT32 and UINT64
formats, the word order is LSW-> MSW.

Meine Wattzahlen haben Wortlänge von zwei und sind als INT32 angegeben.


Ergänzend hier auch nocheinmal Logeinträge (habe auf Loglevel 5 verbose gesetzt)

2018.01.21 12:24:57 5: Gavazzi: Get: Called with W_L2 (i20)
2018.01.21 12:24:57 4: Gavazzi: Send called with i20, objLen 2 / reqLen - to id 1, op read, qlen 0
2018.01.21 12:24:57 4: Gavazzi: Send adds fc 4 to 1, for i20 (W_L2), reqLen 2 at beginning of queue for immediate sending
2018.01.21 12:24:57 5: Gavazzi: ReadAnswer called and remaining timeout is 1.99834513664246 requested reading is W_L2
2018.01.21 12:24:57 5: Gavazzi: ReadAnswer got: 010404017d00006a60
2018.01.21 12:24:57 5: Gavazzi: ParseObj called with 017d0000 and start 20, op read
2018.01.21 12:24:57 5: Gavazzi: RevRegs: reversing order of up to 2 registers
2018.01.21 12:24:57 5: Gavazzi: RevRegs: string before is 017d0000
2018.01.21 12:24:57 5: Gavazzi: RevRegs: string after  is 0000017d
2018.01.21 12:24:57 5: Gavazzi: ParseObj ObjInfo for i20: reading=W_L2, unpack=I>, expr=$val / 10, format=%.1f, map=
2018.01.21 12:24:57 5: Gavazzi: ParseObj unpacked 0000017d with I> to hex 333831 (381)
2018.01.21 12:24:57 5: Gavazzi: ParseObj evaluates expr for W_L2, val=381, expr $val / 10
2018.01.21 12:24:57 5: Gavazzi: ParseObj eval result is 38.1
2018.01.21 12:24:57 5: Gavazzi: ParseObj for W_L2 does sprintf with format %.1f value is 38.1
2018.01.21 12:24:57 5: Gavazzi: ParseObj for W_L2 sprintf result is 38.1
2018.01.21 12:24:57 4: Gavazzi: ParseObj for W_L2 assigns 38.1
2018.01.21 12:24:57 5: Gavazzi: ReadAnswer done, reading is W_L2, value: 38.1
2018.01.21 12:25:03 5: Gavazzi: Get: Called with W_L1 (i18)
2018.01.21 12:25:03 4: Gavazzi: Send called with i18, objLen 2 / reqLen - to id 1, op read, qlen 0
2018.01.21 12:25:03 4: Gavazzi: Send adds fc 4 to 1, for i18 (W_L1), reqLen 2 at beginning of queue for immediate sending
2018.01.21 12:25:03 5: Gavazzi: ReadAnswer called and remaining timeout is 1.99769401550293 requested reading is W_L1
2018.01.21 12:25:03 5: Gavazzi: ReadAnswer got: 88
2018.01.21 12:25:03 5: Gavazzi: ReadAnswer got: 886af00104
2018.01.21 12:25:03 5: Gavazzi: ReadAnswer: ParseFrames returned error: recieved frame from unexpected Modbus Id 136, expecting fc 4 from 1 for device Gavazzi
2018.01.21 12:25:05 5: Gavazzi: Get: Called with W_L1 (i18)
2018.01.21 12:25:05 4: Gavazzi: Send called with i18, objLen 2 / reqLen - to id 1, op read, qlen 0
2018.01.21 12:25:05 4: Gavazzi: Send adds fc 4 to 1, for i18 (W_L1), reqLen 2 at beginning of queue for immediate sending
2018.01.21 12:25:05 5: Gavazzi: ReadAnswer called and remaining timeout is 1.99814391136169 requested reading is W_L1
2018.01.21 12:25:05 5: Gavazzi: ReadAnswer got: 01030400c10000
2018.01.21 12:25:05 5: Gavazzi: ReadAnswer: ParseFrames returned error: unexpected function code 3 from 1, expecting fc 4 from 1 for device Gavazzi
2018.01.21 12:25:09 5: Gavazzi: Get: Called with W_L1 (i18)
2018.01.21 12:25:09 4: Gavazzi: Send called with i18, objLen 2 / reqLen - to id 1, op read, qlen 0
2018.01.21 12:25:09 4: Gavazzi: Send adds fc 4 to 1, for i18 (W_L1), reqLen 2 at beginning of queue for immediate sending
2018.01.21 12:25:09 5: Gavazzi: ReadAnswer called and remaining timeout is 1.99826312065125 requested reading is W_L1
2018.01.21 12:25:09 5: Gavazzi: ReadAnswer got: 010404f59effffffffa816
2018.01.21 12:25:09 5: Gavazzi: ReadAnswer: ParseFrames returned error: ParseFrames got wrong Checksum (expect 5800, got 48895)

Man sieht bei W_L2 ist alles in Ordnung. W_L1 ist die Phase, die gerade negativ ist.

Die Konfiguration / Settings für Phasen W_L1, W_L2 und W_L3 sind alle gleich (so wie oben aufgeführt).

Danke und liebe Grüße,
Stefan

StefanStrobel

Hallo Stefan,

die Fehler, die Du bekommst, haben nichts mit dem Parsen der Werte zu tun sondern mit Kollisionen auf der Leitung, die zu ungültigen Frames führen.
Modbus sieht maximal einen Master vor. Wenn Du nun parallel zu einem existierenden Master Werte abfragen möchtest, dann kann das im Einzelfall mal gut gehen, in Deinem Fall klappt es aber offensichtlich nicht. Dass L1 noch funktioniert hat, würde ich als Glück bezeichnen...

Ich bin gerade dabei das Modul so zu erweitern, dass man auch Modbus-Slaves und Gateways definieren kann. In diesem Zug könnte man dann auch passiv mitlesen, was ein anderer Master so mit seinen Slaves redet. Bis das fertig ist, wird es aber noch Monate dauern, da ich momentan kaum Zeit zum Programmieren finde.

Gruss
   Stefan

stefan.apetz

Hallo Stefan,

Danke für die Ausführung.

Ich bekomme eigentlich zu 90% der Abfragen Werte zurück. Siehe Bild im Anhang. Das sieht alles top aus. Solange, bis die negativen Werte kommen.

Modbus Slave / Gateway würde bedeuten, es setzt auf dein vorhandenes Modul auf und horcht dann immer nur in den Kanal und würde somit dann nur die Daten in dem Rhythmus abfragen, wie der Master Anfragen absetzt?

Liebe Grüße
Stefan

StefanStrobel

Hallo Stefan,

ich will nicht ausschließen, dass die negativen Werte bei Dir ein Problem machen. Nur muss das dann an einer anderen Stelle im Log sichtbar werden. Die falsche Checksumme bzw. die ungültigen Frames haben sicher eine andere Ursache. Vielleicht kannst Du ja nochmal suchen bzw. einen größeren Ausschnitt aus dem Log posten, in dem man die Verarbeitung der negativen Werte sieht.

Die geplante Erweiterung des Modbus-Moduls würde es erlauben einfach passiv zuzuhören, wie ein anderer Master mit einem Slave spricht und dabei die übertragenen Werte selbst darzustellen. Eigene Abfragen gäbe es dabei gar nicht. Die Gateway-Funktion soll es erlauben, Fhem per Modbus-TCP anzusprechen und die Requests direkt z.B. per Modbus RTU über RS485 weiter zu reichen.

Gruss
   Stefan

Matthias76

Hallo zusammen
Das Thema ruht nun seit 2018 und ich wollte mal fragen, ob die Weiterentwicklung noch ein Thema ist.
Ich kann mir auch die "Gespräche" auf dem Modbus zwischen AEDL-UH und EM24 am COM3 (RS485-USB-Konverter) mittels Tools sichtbar machen.
Also ein Mitlauschen würde reichen.
Auch mir geht es nur darum, Verbrauchswerte +/- xxxx Watt per nutzbarer Variabel ins FHEM zu holen.


Matthias76

Wenn ich es auf dem Windows PC mit Hersteller´s Software "nur anzeigen" lassen möchte, geht das immer zwischen den Frames vom anderen Modul.
Also wenn sich das mastermäßig einmischt, dann gibt es einen CRC-Fehler und die Software stoppt die Live-Anzeige.
Aber dazwischen gibt es immer Daten. Macht also nichts, stünde FHEM mit einer Abfrage (Wert übernehmen so lange CRC ok sonst überspringen und letzten Wert annehmen).

Wenn also FHEM also alle plausiblen Daten auf gleicher Art auslesen würde, wäre das ideal.
Ich denke, genau so etwas war hier einst auch schon gefragt. Hat das jemand hinbekommen?

Hier eine Darstellung:
[Anhang]

StefanStrobel

Hallo,

der passive Modus ist ebenso wie ein Gateway/Relay-Modus seit einiger Zeit im Modul drin.
Doku ist in der Commandref bzw. im Wiki.
https://wiki.fhem.de/wiki/ModbusAttr#Define_as_Modbus_passive_listener

Gruss
    Stefan