Warning
This project is still in early alpha stage.
There is still a lack of many things that make good code - error handling, logging, input sanitation, optimization (around 3% CPU load on a RPi4), you name it.
vbus2mqtt is a bridge for VBus-enabled devices made by Resol (and some branded devices such as Viessmann, Wagner, ...) to MQTT implemented im Python.
The project basically consists of 3 parts:
- VBusReader.py: Extraction of VBus Messages, Datagrams or Telegrams from a bytearray buffer and from a serial interface. Not all message types are supported/tested yet. Also, only reading is currently supported
- VBusSpecReader.py: VBus specification reader for *.vsf files according to its specification and interpreter for Messages
- MqttDispatcher.py: Aggregator/converter of transfer specification to the actual MQTT communication
- Download the code here to a directory
- Get vbus_specification.vsf from the resul-vbus repository
- Install the required pip packages:
pip install -r requirements.txt
- Run
vbus2console.py
to explore supported messages/fields (see below) - Configure your
vbus2mqtt.json
- Run
vbus2mqtt.py
and cross your fingers ;)
This tool can be used to inspect live data from the given serial port.
usage: vbus2console.py [-h] -p PORT [-b BAUDRATE] [-v VSF] [-l {EN,DE,FR}]
vbus2console.py: error: the following arguments are required: -p/--port
The serial port must be provided, baudrate will default to 9600 and the script will try to use vbus_specification.vsf and English descriptions. If the given file can not be found, only raw values will be shown.
Using a Raspberry Pi with my hardware, the command usually looks as following:
python3 vbus2console.py -p /dev/serial0
All read values are printed until the script is terminated.
Reader started
-----------------
SRC: 0x7321 - Vitosolic 200 [Controller]
DST: 0x0010 - DFA
RAW:
AA 10 00 21 73 10 00 01 12 38 0F 00 2E 01 05 3C
62 01 38 22 04 3E 38 22 54 01 05 4B 4B 01 3F 02
01 71 38 22 49 01 05 56 11 01 11 01 00 5B 00 00
00 00 00 7F 00 00 00 00 00 7F 00 00 00 00 00 7F
18 01 00 00 00 66 47 00 00 00 00 38 00 00 00 00
00 7F 00 00 00 00 00 7F 00 00 00 00 00 7F 00 00
53 00 00 2C 00 00 00 00 00 7F 01 03 14 05 00 62
02 00 00 00 00 7D
Checksum ok
VER: v1.0 message
CMD: 0x0100
Fields:
00_0010_7321_10_0100_000_2_0 Temperature sensor 1 14.30 °C
00_0010_7321_10_0100_002_2_0 Temperature sensor 2 43.00 °C
00_0010_7321_10_0100_004_2_0 Temperature sensor 3 35.40 °C
00_0010_7321_10_0100_006_2_0 Temperature sensor 4 888.80 °C
<snip />
00_0010_7321_10_0100_044_1_0 Pump speed relay 1 0 %
00_0010_7321_10_0100_045_1_0 Pump speed relay 2 0 %
<snip />
00_0010_7321_10_0100_058_2_0 Relay usage mask 83
00_0010_7321_10_0100_060_2_0 Error mask 0
00_0010_7321_10_0100_062_2_0 Warning mask 0
00_0010_7321_10_0100_064_2_0 Controller version 769
00_0010_7321_10_0100_066_2_0 System time 1300
[...]
The field identifiers (e. g. 00_0010_7321_10_0100_000_2_0
for Temperature sensor 1) are used for the vbus2mqtt configuration (see below) and can also be looked up in the the VBus Specification
In simple words: VBus in, MQTT out.
The usage (currently) doesn't even need a section. Just run the file, all the configuration is read from hard coded vbus2mqtt.json
The configuration is read with a json5 parser, therefore comments, trailing commas etc. are supported for easier testing and documentation.
The file currently consists of 4 sections:
- mqtt:
- plugins: plugins to do pre-processing of the data provided by VBus
- transfers: mqtt topic content definitions
Configuration to the VBus interface. This should be pretty much self-explaining:
Example:
"vbus": {
"serialport": "/dev/serial0",
"baudrate": 9600,
"vsf": "vbus_specification.vsf"
}
Connection information to the server, topic prefix and last will configuration
Example:
"mqtt": {
"host": "localhost",
"port": 1883,
"user": "myuser",
"pass": "mypass123",
"topic_prefix": "vbus2mqtt/house/",
"last_will": {
"topic": "LWT",
"online": "Online",
"offline": "Offline"
}
}
topic_prefix
is valid for all topics defined in the transfers
section, leave it empty if you want to use full qualified names in that section. Mind the trailing slash if you want to have a sub-topic for the transfers.
last_will
should be self-explaining. If you don't know what this is about: When the script starts, the topic vbus2mqtt/house/LWT
will be published with Online
as value in the above example. If the connection to the broker gets lost, it will publish Offline
to the said topic. That's all. Currently, only one last will ist supported.
Not only raw values can be published, but also processing of is possible.
Example:
"plugins": [
{
"name": "powercalc",
"module": "VBusReaderPlugins:VrpSolarPower",
"config": {
"field_tin": "00_0010_7321_10_0100_004_2_0",
"field_tout": "00_0010_7321_10_0100_010_2_0",
"field_pump": "00_0010_7321_10_0100_044_1_0",
"pump_flow": [0, 0, 0, 3.5, 4.5, 5, 6, 6.5, 7, 7.5, 8],
"medium": "tyfoclor_g-ls"
}
}
]
The name
given to the plugin can basically be everything - this is used for reference in the assignment in the field transfers.
module
contains the python module being loaded as well as the class to be instantiated, separated by a colon. In the example, class VrpSolarPower
from VBusReaderPlugins.py
is imported.
The config
item is passed to the class' object when constructed. This data is completely dependent on the plugin. See below for more info.
This section is an array of objects that describe the data transferred to the MQTT broker, it contains multiple sub-sections.
"transfers": [
{
"mqtt": {
"topic": "panel_temp",
"retain": false, // optional
"qos": 0 // optional
},
"trigger": {
"type": "update"
},
"type": "direct",
"field": {
"name": null,
"item": "00_0010_7321_10_0100_000_2_0" // 0x7321 - Vitosolic 200 [Controller] -> 0x0010 - DFA - Temperature sensor 1
}
},
[...]
]
The item topic
in the mqtt
sub-section should be quite clear - as mentioned above, this value will be concatenated to the topic_prefix. In the given example, the topic would be vbus2mqtt/house/panel_temp
.
retain
can be true
or false
and will default to false
if not provided. qos
can be 0, 1, or 2 and will default to 0 if not provided.
The trigger
type
can be either update
or interval
.
- With
update
, an update is sent if any of the items associated to the transfer is updated via a VBus message. - With
interval
, a second item with the keyinterval
and a time value in seconds is expected in the sub-section. - Planned:
change
and a combination ofchange
andinterval
to reduce traffic.
Example for an interval:
"trigger": {
"type": "interval",
"interval": 5
},
This publishes the data of the transfer every 5 seconds, regardless whether there was an update or change of the data.
There are 2 different types of transfers, either direct or json. First only allows the value of one item, latter allows multipe items including nesting.
When type
is set to direct
, the item field
contains the reference to a single field that is published. See below for the different field types.
Please note that name
must be provided, yet its content is ignored. Therefore it is recommended to set it to null
. (I may come up with a better implementation)
Please also note that the key is field
and not fields
like for the json transfer type.
See above for an example.
When type
is set to json
, the item fields
is getting a bit more complex.
Best to start with an example:
"type": "json",
"fields": [
{
"group": "temperatures",
"fields": [
{
"name": "panel",
"item": "00_0010_7321_10_0100_000_2_0" // Temperature sensor 1
},
{
"name": "heatx_in",
"item": "00_0010_7321_10_0100_004_2_0" // Temperature sensor 3
},
]
},
{
"group": "pumps",
"fields": [
{
"name": "pump1",
"item": "00_0010_7321_10_0100_044_1_0" // Pump speed relay 1
},
]
}
]
With the data from way above, this will publish the following data:
{"temperatures": {"panel": 14.3, "heatx_in": 34.0}, "pumps": {"pump1": 0}}
The examples above only show item
references which will read fields from VBus messages.
In case the trigger type is set to interval
, the key value pair max_age
can be used to set the published data to null
if the last received value is older than the given time in seconds. E. g.:
{
"name": "panel",
"item": "00_0010_7321_10_0100_000_2_0",
"max_age": 10
}
will publish
{"panel": null}
if the last received value for vbus field 00_0010_7321_10_0100_000_2_0
is older than 10 seconds at the time of publishing.
Besides item
, also the following keys can be used:
meta
: Meta information of the software and communications, with the item valuescomm:rxerr_cnt
- Count of receive errors from VBuscomm:rxerr_last
- Timestamp of last receive error (ISO8601)comm:rxmsg_cnt
- Count of received messages from VBuscomm:rxmsg_last
- Timestamp of last received message (ISO8601)sw:pid
- Process ID of the scriptsw:ramuse
- RAM usage in bytes, not including the runtime environment overheadsw:uptime
- Uptime of the script in secondstime:now
- Current time (ISO8601), can be used to mark publishing date
plugin
: Value from a plugin, see below
the plugin
item references to the name of a plugin defined in the plugins
section.
The item function
contains the method name prefixed with plugin_
of the plugin that's called to retreive the published value.
Example:
"field": {
"name": "solar_power",
"plugin": "powercalc",
"function": "power"
}
This publishes the result when calling the method plugin_
power
of the object instanciated in the plugins
section named powercalc
.
As a mockup:
#VBusReaderPlugins.py
class VrpSolarPower():
...
def plugin_power(self):
return 1234
would publish
{"solar_power": 1234}
This plugin calculates the power received from the collector(s) using the input and output temperature at the heat exchangers "primary" side, the pump power resp. its flow rate and the thermal properties of the medium.
field_tin
, field_tout
and field_pump
, contain the VBus field identifiers where the temperatures resp. pump speed is read from.
As a side note, these references must be put into self.subscriptions to hint the dispatcher to store the values.
pump_flow
is an array of 11 elemnts that represent the flow rate for the medium in l/min for pump speeds between 0 ... 100%
the item medium
can contain either a name (for now, only tyfoclor_g-ls
) or an object with the characteristics of the heat transfer medium, to be precise the parameters for the point-slope form for both thermal capacity (in kJ / (kg * K)) as well as the medium's density (in kg/m³)
c_m
: slope of thermal capacity (defaults to 0 if not provided; multiplied with average oftin
andtout
)c_t
: y-intercept of thermal capacityrho_m
: slope of density (defaults to 0 if not provided; multiplied with average oftin
andtout
)rho_t
: y-intercept of density
- Add trigger type
change
and the combination withinterval
, also with threshold for values - Reconsider the implementation of transfer field names. Maybe change the type of
fields
from array to object which would also make the config a bit more compact but will likely break compatibility with the current config scheme. - CPU load is a bit high, profiling and code optimization is needed
- Proper packaging of the components
- Proper error handling & logging