Modbus: Unterschied zwischen den Versionen
(first draft describing the module 98_Modbus.pm for people that want to create modules for modbus devices) |
K (Links ergänzt) |
||
Zeile 11: | Zeile 11: | ||
Modbus defines a physical modbus interface and library functions to be called from other logical modules / devices. | Modbus defines a physical modbus interface and library functions to be called from other logical modules / devices. | ||
This low level module takes care of the communication with modbus devices and provides Get, Set and cyclic polling of Readings as well as formatting and input validation functions. | This low level module takes care of the communication with modbus devices and provides Get, Set and cyclic polling of Readings as well as formatting and input validation functions. | ||
The logical device modules for individual machines only need to define the supported modbus function codes and objects of the machine with the modbus interface in data structures. These data structures are then used by this low level module to implement Set, Get and automatic updateing of readings in a given interval. | The logical device modules for individual machines only need to define the supported modbus function codes and objects of the machine with the modbus interface in data structures. These data structures are then used by this low level module to implement Set, Get and automatic updateing of readings in a given interval. | ||
The Modbus module supports Modbus RTU over serial / RS485 lines as well as Modbus TCP and Modbus RTU over TCP. It defines read / write functions for Modbus holding registers, input registers, coils and discrete inputs. | The Modbus module supports Modbus RTU over serial / RS485 lines as well as Modbus TCP and Modbus RTU over TCP. It defines read / write functions for Modbus holding registers, input registers, coils and discrete inputs. | ||
See [[ModbusAttr]] if you don't want to use a library to develop your own module and if you are looking for a generic Modbus Module instead that can be configured with attributes. | |||
== Availability == | == Availability == | ||
Zeile 34: | Zeile 37: | ||
</pre> | </pre> | ||
In this example the module opens the given serial interface and other logical modules can access several Modbus devices connected to this bus concurrently. | In this example the module opens the given serial interface and other logical modules like [[ModbusAttr]] or [[ModbusSET]] can access several Modbus devices connected to this bus concurrently. | ||
== Set-Commands == | == Set-Commands == | ||
Zeile 363: | Zeile 366: | ||
} | } | ||
</pre> | </pre> | ||
== Examples for logical device modules that use this base module == | |||
;[http://forum.fhem.de/index.php/topic,25315.60.html SDM220M] | |||
;[http://forum.fhem.de/index.php/topic,25315.60.html SDM630M] | |||
:modules for energy meters from B+G E-Tech & EASTON written by Roger | |||
;[http://forum.fhem.de/index.php/topic,25315.45.html UMG103] | |||
;[http://forum.fhem.de/index.php/topic,25315.45.html UMG604] | |||
:modules for the UMG103 and UMG604 meters from Janitza | |||
;[[ModbusSET]] | |||
:module for the set silent heat pumps from Schmidt Energie Technik | |||
;[[ModbusAttr]] | |||
:generic modbus device module where the data objects, addresses, display formats, function codes and other things can be configured using Fhem attributes similar to HTTPMOD | |||
[[Kategorie:Interfaces]] |
Version vom 26. März 2015, 21:15 Uhr
Modbus | |
---|---|
Zweck / Funktion | |
Library or physical device to extract information from devices with a Modbus interface or send information to such devices | |
Allgemein | |
Typ | Gerätemodul |
Details | |
Dokumentation | EN / DE |
Support (Forum) | Sonstiges |
Modulname | 98_Modbus.pm |
Ersteller | StefanStrobel (Forum / Wiki) |
Wichtig: sofern vorhanden, gilt im Zweifel immer die (englische) Beschreibung in der commandref! |
Modbus defines a physical modbus interface and library functions to be called from other logical modules / devices. This low level module takes care of the communication with modbus devices and provides Get, Set and cyclic polling of Readings as well as formatting and input validation functions.
The logical device modules for individual machines only need to define the supported modbus function codes and objects of the machine with the modbus interface in data structures. These data structures are then used by this low level module to implement Set, Get and automatic updateing of readings in a given interval.
The Modbus module supports Modbus RTU over serial / RS485 lines as well as Modbus TCP and Modbus RTU over TCP. It defines read / write functions for Modbus holding registers, input registers, coils and discrete inputs.
See ModbusAttr if you don't want to use a library to develop your own module and if you are looking for a generic Modbus Module instead that can be configured with attributes.
Availability
The module has been checked in.
Prerequisites
This module requires the Device::SerialPort or Win32::SerialPort module if you want to communicate with modbus devices over a serial line.
Define of a modbus interface device for serial communication
define <name> Modbus <device>
A define of a physical device based on this module is only necessary if a shared physical device like a RS485 USB adapter is used. In the case Modbus TCP this module will be used as a library for other modules that define all the data objects and no define of the base module is needed.
Example:
define ModBusLine Modbus /dev/ttyUSB1@9600
In this example the module opens the given serial interface and other logical modules like ModbusAttr or ModbusSET can access several Modbus devices connected to this bus concurrently.
Set-Commands
this low level device module doesn't provide set commands for itself but implements set for logical device modules that make use of this module as a library. See ModbusSET for example.
Get-Commands
this low level device module doesn't provide get commands for itself but implements get for logical device modules that make use of this module as a library.
Attributes
- do_not_notify
- readingFnAttributes
- queueDelay
- modify the delay used when sending requests to the device from the internal queue, defaults to 1 second
- queueMax
- max length of the send queue, defaults to 100
Writing modules for devices using this module as a library
Writing a module for a physical device with modbus interface is easy when you use the 98_Modbus.pm module as a library.
To use this module as a library for other fhem modules you only have to define a data structure that defines the mapping between modbus data objects (holding registers, input registers, coils or discrete inputs) and fhem readings.
Additionally the module needs to contain a few package
and use
statements and an initialize function at the beginning, that assigns a few special variables to point to functions of the Modbus base module.
Example for a module that is called ModbusSET:
package main; use strict; use warnings; sub ModbusSET_Initialize($) { my ($modHash) = @_; require "$attr{global}{modpath}/FHEM/98_Modbus.pm"; $modHash->{parseInfo} = \%SET10parseInfo; # defines registers, inputs, coils etc. for this Modbus Defice $modHash->{deviceInfo} = \%SET10deviceInfo; # defines properties of the device, 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 }
The name of the initialize-Function has to match the name of the module. In the above example this is ModbusSET_Initialize
. Most of the steps needed in an initialize function are provided by the library function ModbusLD_Initialize
. This function tells fhem to use the library functions for define
, set
, get
and other typical functions in a module. See DevelopmentModuleIntro for more background information on writing fhem modules if you are curious.
Introduction to the parseInfo structure
Typically the data structure to map between data objects of the modbus device and fhem readings is named parseInfo
with a part of the name of the module itself as prefix. In the example of the module 98_ModbusSET.pm which uses Modbus.pm to implement a module for SET Silent 10 heat pumps, the structure is called SET10parseInfo
.
As an example a very simple definition of a parseInfo structure for a heat pump could look like this:
my %XYparseInfo = ( "h256" => { reading => "Temp_Wasser_Ein", # name of the reading for this value }, "h258" => { reading => "Temp_Wasser_Aus", }, "h770" => { reading => "Temp_Soll", min => 10, # input validation for set: min value max => 32, # input validation for set: max value set => 1, # this value can be set } );
This would be the main part of the module and map from holding register 256 to a fhem reading named Temp_Wasser_Ein, holding register 258 to Temp_Wasser_Aus and 770 to Temp_Soll.
All readings will be read from the device in an interval that the user can specify when he issues the define command for your module.
The meaning of set => 1
is that the holding register 770 can also be written to with a set command. Fhem will check that the value written is not smaller than 10 and not bigger than 32 as specified above.
More complex example:
my %SET10parseInfo = ( "h256" => { reading => "Temp_Wasser_Ein", # name of the reading for this value name => "Pb1", # internal name of this register in the hardware doc expr => '$val / 10', # conversion of raw value to visible value len => 1, }, "h770" => { reading => "Temp_Soll", name => "ST03", expr => '$val / 10', # convert raw value to readable temp setexpr => '$val * 10', # expression to convert a set value to the internal value min => 10, # input validation for set: min value max => 32, # input validation for set: max value hint => "8,10,20,25,28,29,30,30.5,31,31.5,32", set => 1, # this value can be set }, "h771" => { reading => "Hysterese", # Hex Adr 303 name => "ST04", expr => '$val / 10', setexpr => '$val * 10', poll => "x10", # only poll every 10th iteration. min => 0.5, max => 3, set => 1, }, "h777" => { reading => "Hyst_Mode", # Hex Adr 0309 name => "ST10", map => "0:mittig, 1:oberhalb, 2:unterhalb", poll => "once", # only poll once (or after a set) set => 1, }, "i800" => { reading => "Voltage", # Input register unpack => "f>", # this value is a float len => 2, # the float occupies two input registers, 800 and 801 }, );
There are many more options that can be specified for each data object / reading. If these options are not specified, the base module assumes defaults that typically make sense. However if you want to modify the defaults, you can either define explicit values in the parseInfo structure or you can define another data structure typically called deviceInfo.
Introduction to the deviceInfo structure
The deviceInfo structure is completely optional. If you don't define it in your module, the base module takes default values that work in ost cases. If you only want to override a few of the defaults, you can just define them and leave other options or sections out. A simple device info structure that modifies some defaults could look like this:
my %SET10deviceInfo = ( "timing" => { timeout => 3, # timeout is 3 seconds /default would be 2 commDelay => 0.7, # wait 0.7 seconds before sending after receiving sendDelay => 0.7, # wait at least 0.7 seconds for another send }, "c" => { read => 1, # function code 1 to read coils (this could be omitted because it is the default anyways write => 5, # dito }, "h" => { read => 3, write => 6, defLen => 1, # default legth is 1 object combine => 5, defShowGet => 1, defUnpack => "s>", # default data format is a signed 16 bit integer for holding registers }, );
The deviceInfo structure contains five optional parts. Timing defines timing values and the remaining parts define settings or defaults for coils (c), discrete inputs (d), input registers (i) and holding registers (h).
for each modbus object type you can change what function code should be used to read or write to the object. This is completely optional and if nothing is specified, the base module assumes function codes 1,2,3 and 4 for reading as well as 5 and 6 for writing which works for many modbus devices. If you prefer to use function code 16 for writing to holding registers, you can specify "write => 16" in the "h" part.
usage of a module created this way
a logical module written this way will have a define command that can work in two ways. If your module would be called ModbusSET and it is using a serial line connection (Modbus RTU over RS485 oer over RS232):
define <iodevice> Modbus /dev/device@baudrate define <name> ModbusSET <Id> <Interval> </code>
In this case, a physical serial interface device is defined first using the Modbus module. Then a device based on your module (ModbusSET in the example) is defined for each physical modbus device connected to the serial line. For a RS485 bus, several devices with different Ids can be connected to the same bus.
Example:
define ModbusRS485 Modbus /dev/rs485@9600 define PWP ModbusAttr 5 60
this defines the device and it will use the readings that you coded in the parseInfo data structure.
Alternatively your module would also support Modbus TCP or Modbus RTU over TCP with the following define syntax:
define <name> ModbusAttr <Id> <Interval> <Address:Port> <RTU|TCP>
In this case no serial interface device is necessary and your module connects to the modbus device directly via TCP using either Modbus TCP or Modbus RTU over TCP.
Example:
define PWP ModbusAttr 1 30 192.168.1.115:502 TCP
General information about data objects
Modbus devices can use many different ways to encode values in their data objects. A temperature might be stored multiplied with 10 as a 16 bit integer value in one holding register so you have to read the integer and divide it by 10 to get the real temperature value back. It might also be stored as a 32 bit float data type that spans two adjacent input registers. The modbus base module implements a very generic way to handle different encodings without real programming: It lets you define the Perl unpack code to convert a raw data string to a Perl value, a Perl expression to do further computation and a length in data objects.
This way a temperature stored in a 16 bit signed integer as the value multiplied by 10 can be described with the unpack code "s>" and the expression "$val / 10". A float value spanning 2 registers would be described with an unpack code "f>" and a len of 2. No expression is needed in this case. See Perldoc on the pack function for a detailed explation of pack and unpack codes.
The idea here is that you should be able to define any mapping, encoding, transformation or formatting of data objects without programming by simpy describing them.
All options in parseInfo
Most options here are optional and can be used if there is a need but they can also be omitted. If most readings require the same options and the option is different from the default, it is also possible to define a different default per modbus data object type in another data structure (see deviceInfo).
- reading
- name of the reading to be used in Fhem e.g. Temp_Wasser_ein
- expr
- perl expression to convert a string after it has been read. The original value is in $val e.g. $val / 10
- 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"
- format
- a format string for sprintf to format a value read, e.g. %.1f
- len
- number of Registers this value spans, can be 2 for a 32 bit float which is stored in 2 registers
- 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 or "s>" for signed 16 bit integer in big endian format
- showget
- can be set to 1 to allow a Fhem get command to read this value from the device. All defined objects can be used in a get command that is issued on the command line. This parameter only controls if fhemweb will offer a get command for the object.
- 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
- polldelay
- if a value should not be read in each iteration (after interval has passed), this value can be set to an explicit time in seconds. The update function will then verify if this delay has elapsed since the last read of this object. If not, the read is skipped.
- set
- can be set to 1 to allow writing this value with a Fhem set-command
- min
- min value for input validation in a set command. If the user issues e.g. set Device Temp_Soll 10, Fhem will check if the given value 10 is bigger or equal the defined min and smaller or equal the defined max.
- max
- max value for input validation in a set command
- hint
- string for fhemweb to create a selection or slider
- setexpr
- per expression to convert an input string to the machine format before writing this is typically the reverse of the above expr, e.g. $val * 10
- name
- optional internal name of the value in the modbus documentation of the physical device, e.g. pb1
All options in deviceInfo
Keys in the timing section:
- timeout
- how long to wait for a response from the device, can be overwritten by attribute timeout in logical device. Defaults to two seconds if this is not specified
- commDelay
- minimal delay in secounds between two communications e.g. a read a the next write, can be overwritten with attribute commDelay if added to AttrList in _Initialize below defaults to 0.1 seconds if not specified
- sendDelay
- minimal delay in seconds between two sends, can be overwritten with the attribute sendDelay if added to AttrList in _Initialize function below. Defaults to 0.1 seconds if not specified
Keys per object type (c = coil, d = discrete input, i = input register, h = holding register)
- read
- function code to use for reading this object type (e.g. 3 for holding registers) defaults to function codes 1-4 depending on the object types if nothing else is specified (3 to read holding register, 1 to read coils and so on)
- write
- function code to use for writing this object type (e.g. 6 or 16 for holding registers) defaults to function codes 5 and 6 depending on the object types if nothing else is specified (6 to read holding register, 5 to write coils and so on)
- defLen
- default len for objects using this type (e.g. can be set to 2 if the device mainly provides float values that span 2 registers (2 times 16 Bit) can be overwritten in parseInfo per reading by specifying the key "len" defaults to 1 if not specified
- defFormat
- format string to do sprintf with the value can be overwritten in parseInfo per reading by specifying the key "format" if no format is specified here and none in parseInfo, the the reading is set without further formatting (which is typically fine)
- defUnpack
- default pack / unpack code to convert raw values, e.g. "n" for a 16 bit integer or "f>" for a big endian float can be overwritten in parseInfo per reading by specifying the key "unpack" if not specified here and not in parseInfo, then the raw value is interpreted as "n" which is 16 bit unsigned integer in big endian format
- defPoll
- defines that objects of this type should be polled by default unless specified otherwise in parseInfo or by attributes can be overwritten in parseInfo per reading by specifying the key defaultpoll if not specified here or in parseInfo, the object is not polled
- defShowGet
- defines that Fhemweb shows a Get option (by returning it as reslut to get ?) for objects of this type can be overwritten in parseInfo per reading by specifying the key showget defaults to 0.
- combine
- max number of registers that the device is willing to deliver in one read request. The modbus application layer protocol specification allows for more than 100 but most devices limit this to 5, 10 or some other number. This option defaults to 1 if not specified.
For an example of a full module that is based on the mechanisms described here see 98_ModbusSET.pm.
Attributes of your module
a module based on the base module / library 98_Modbus.pm can also allow the end user to modify properties of each reading if you want to allow it. All you have to do is to offer an attribute by adding its name to the variable $modHash->{AttrList} in your initialize function.
If for example you want to allow the user to modify the maximum value for the reading Temp_Soll, you can add "Temp_Soll-max " to this variable and the use can the set this attribute. The attribute takes precedence over the max potentially already defined in your parseInfo structure.
there are two ways that the base module accepts such readings. One is the reading name followed by "-" and the option to override, the alternative syntax is "obj-" followed by the first letter of an object type (c/d/h/i) and a decimal address just like the main key of an object in the parseInfo structure.
Instaed of allowing the attribute Temp_Soll-max for the max value of reading Temp_Soll which corresponds to holding register 770, you can alternatively add the attribute name "obj-h770-min " to $modHash->{AttrList}.
If the user is allowd to specify such attributes solely depends on the contents of the $modHash->{AttrList} variable. All the processing is already built into the base module.
If you want to allow the user the override the formatting of readings then you can add "obj-[cdih][1-9][0-9]*-format " as a regular expression that allows format specifications for all possible data objects.
The module 98_ModbusAttr for example is also based on 98_Modbus.pm and allows all possible attributes so the user can completely define his device with attributes and without a parseInfo or deviceInfo structure.
In the same way you can allow the user to override the device specific options and defaults with attributes that start with "dev-", followed by the section of the deviceInfo and the name of the option. If you want to allow the user to modify the function code to be used for writing holding registers, you can add the attribute "obj-h-write " and the user can then set this attribute to 6 or 16 as he prefers. It is up to the module author to decide if this makes sense.
An assignment that allows most options to the user could be:
$modHash->{AttrList} = $modHash->{AttrList} . " " . "obj-[cdih][1-9][0-9]*-reading " . "obj-[cdih][1-9][0-9]*-name " . "obj-[cdih][1-9][0-9]*-set " . "obj-[cdih][1-9][0-9]*-min " . "obj-[cdih][1-9][0-9]*-max " . "obj-[cdih][1-9][0-9]*-hint " . "obj-[cdih][1-9][0-9]*-expr " . "obj-[cdih][1-9][0-9]*-map " . "obj-[cdih][1-9][0-9]*-setexpr " . "obj-[cdih][1-9][0-9]*-format " . "obj-[cdih][1-9][0-9]*-len " . "obj-[cdih][1-9][0-9]*-unpack " . "obj-[cdih][1-9][0-9]*-showget " . "obj-[cdih][1-9][0-9]*-poll " . "obj-[cdih][1-9][0-9]*-polldelay " . "poll-.* " . "polldelay-.* " . "dev-([cdih]-)*read " . "dev-([cdih]-)*write " . "dev-([cdih]-)*combine " . "dev-([cdih]-)*defLen " . "dev-([cdih]-)*defFormat " . "dev-([cdih]-)*defUnpack " . "dev-([cdih]-)*defPoll " . "dev-([cdih]-)*defShowGet " . "dev-timing-timeout " . "dev-timing-sendDelay " . "dev-timing-commDelay "; }
Examples for logical device modules that use this base module
- SDM220M
- SDM630M
- modules for energy meters from B+G E-Tech & EASTON written by Roger
- UMG103
- UMG604
- modules for the UMG103 and UMG604 meters from Janitza
- ModbusSET
- module for the set silent heat pumps from Schmidt Energie Technik
- ModbusAttr
- generic modbus device module where the data objects, addresses, display formats, function codes and other things can be configured using Fhem attributes similar to HTTPMOD