VitoWiFi
Communicate with Viessmann boilers using the optolink for ESP8266 and ESP32
Install / Use
/learn @bertmelis/VitoWiFiREADME
VitoWiFi
Library for ESP32, ESP8266 and Linux to communicate with Viessmann systems using a (DIY) serial optolink.
Based on the fantastic work on openv.
Features
- VS1 (KW) and VS2 (P300) support. The older GWG protocol is also supported.
- Non-blocking API calls
- For the Arduino framework and POSIX systems (Linux, tested on a Raspberry Pi 1B)
- Maximum flexibility for communication by supporting standard UART interfaces (HardwareSerial,
SoftwareSerialon ESP8266) as well as a custom user-created interface.
Contents
Installation
- For Arduino IDE: see the Arduino Guide
- For Platformio: see the Platfomio registry page for VitoWifi
Hardware
The optolink hardware can be really simple. Using the circuit below you can build your own optolink. Please also check the openv wiki, in German for more implementations.
3.3V
O
|
+-----+-----+
| |
--- ---
| | | |
| | 180Ohm | | 10kOhm
| | | |
--- ---
| |
--- |
SFH487-2 \ / -> |
V -> |
--- |
| |
TX O-------+ |
RX O-------------------+
|
|/ c
-> | SFH309FA
-> |> e
|
-----
---
-
Usage
The canonical way to use this library is simple and straightforward. A few steps are involved:
- define your VitoWiFi object and specify the protocol and interface
- define all needed datapoints
- create callback for when data or errors are returned (std::function supported)
- in
void setup()- attach the callbacks
- start VitoWiFi
- in
void loop():- call
loop()regularly. It keeps VitoWiFi running. Ideally it is called more than once every 10ms.
- call
A simple program for ESP32 to test and query your devicetype looks like this:
#include <Arduino.h>
#include <VitoWiFi.h>
VitoWiFi::VitoWiFi<VitoWiFi::VS2> myHeater(&Serial1);
VitoWiFi::Datapoint deviceId("device id", 0x00F8, 2, VitoWiFi::noconv);
void onResponse(const VitoWiFi::PacketVS2& response, const VitoWiFi::Datapoint& request) {
Serial.print("Raw data received:");
const uint8_t* data = response.data();
for (uint8_t i = 0; i < response.dataLength(); ++i) {
Serial.printf(" %02x", data[i]);
}
Serial.print("\n");
}
void onError(VitoWiFi::OptolinkResult error, const VitoWiFi::Datapoint& request) {
Serial.printf("Datapoint \"%s\" error: ", request.name());
if (error == VitoWiFi::OptolinkResult::TIMEOUT) {
Serial.print("timeout\n");
} else if (error == VitoWiFi::OptolinkResult::LENGTH) {
Serial.print("length\n");
} else if (error == VitoWiFi::OptolinkResult::NACK) {
Serial.print("nack\n");
} else if (error == VitoWiFi::OptolinkResult::CRC) {
Serial.print("crc\n");
} else if (error == VitoWiFi::OptolinkResult::ERROR) {
Serial.print("error\n");
}
}
void setup() {
delay(1000);
Serial.begin(115200);
Serial.print("Setting up vitoWiFi\n");
myHeater.onResponse(onResponse);
myHeater.onError(onError);
myHeater.begin();
Serial.print("Setup finished\n");
}
void loop() {
static uint32_t lastReadTime = 0;
if (millis() - lastReadTime > 30000) {
lastReadTime = millis();
if (myHeater.read(deviceId)) {
Serial.printf("reading \"%s\"\n", deviceId.name());
} else {
Serial.printf("error reading \"%s\"\n", deviceId.name());
}
}
myHeater.loop();
}
Most users will have a collection of datapoints thay want to read. A possible technique to query a large number of datapoints is by simply iterating over them:
// create a collection (array) of datapoints:
VitoWiFi::Datapoint datapoints[] = {
VitoWiFi::Datapoint("outside temp", 0x5525, 2, VitoWiFi::div10),
VitoWiFi::Datapoint("boiler temp", 0x0810, 2, VitoWiFi::div10),
VitoWiFi::Datapoint("pump status", 0x2906, 1, VitoWiFi::noconv)
};
int numberDatapoints = 3;
int currentIndex = -1;
// to start reading, set currentIndex to 0
if (currentIndex >= 0) {
// reading will return `true` when successful.
// as long as VitoWiFi is busy it will return `false`
if (myVitoWiFi.read(datapoints[currentIndex])) {
++currentIndex;
if (currentIndex == numberDatapoints) currentIndex = -1;
}
}
More examples
You can find more examples in the examples directory in this repo.
Datapoints
When defining your datapoints, you need to specify the name, address, length and conversion type. Datapoints in C++ looks like this:
VitoWiFi::Datapoint datapoint1("outside temp", 0x5525, 2, VitoWiFi::div10);
VitoWiFi::Datapoint datapoint2("boiler temp", 0x0810, 2, VitoWiFi::div10);
VitoWiFi::Datapoint datapoint3("pump status", 0x2906, 1, VitoWiFi::noconv);
It is not possible for me to give you a list of available datapoints for your device. Please consult the openv wiki or the InsideViessmannVitosoft repo.
While name, address and length are self-explanatory, conversion type is a bit more complicated.
Conversion types
Data is stored in binary and often needs a conversion function to transform into a more usable type. This is specified by the conversion type, the last argument in the datapoint definition.
C++ is a strongly typed programming language so using the right type is important (read: mandatory). Each conversion type corresponds to a certain type. Reading or writing has to be done using this specific type and failure to do so will not work or will lead to undefined results.
In the table below you can find how to define your datapoints:
|name|size|converter|return type|remarks|
|---|---|---|---|---|
|Temperature|2|div10|float||
|Temperature short|1|noconv|uint8_t|Equivalent to Mode|
|Power|1|div2|float|Also used for temperature in GWG|
|Status|1|noconv|bool|This is the same as 'Temperature short' and 'Mode'. The uint8_t value will be implicitely converted to bool.|
|Hours|4|div3600|float|This is in fact a Count datapoint (seconds) converted to hours.|
|Count|4|noconv|uint32_t||
|Count short|2|noconv|uint16_t||
|Mode|1|noconv|uint8_t|Possibly castable to ENUM|
|CoP|1|div10|float|Also used for heating curve slope|
To use schedules, helper functions are available
std::size_t encodeSchedule(const char* schedule, std::size_t len, uint8_t* output);
std::size_t encodeSchedule(const char* schedule, uint8_t* output);
Mind that the converters are declared within the VitoWiFi namespace.
Bugs and feature requests
Please use Github's facilities to get in touch. While the issue template is not mandatory to use, please use it at least as a starting point to supply the needed info for bughunting.
API reference
Below is an overview of all commonly used methods. For extra functions you can consult the source code.
VitoWiFi::Datapoint
VitoWiFi::Datapoint(const char* name, uint16_t address, uint8_t length, const Converter& converter)
Constructor for datapoints.
const char* name() const
uint16_t address() const
uint8_t length() const
Self-explanatory
const Converter& converter() const
Returns the associated converter class. Can be used to select code flow in the callbacks.
VariantValue decode(const PacketVS2& packet) const
Decodes the data in packet using the converter class attached.
Returns VariantValue which is implicitely castable to the correct datatype. Consult the table above.
VariantValue decode(const uint8_t* data, uint8_t length) const
Decodes the data in the supplied data-buffer using the Converter class attached.
Returns VariantValue which is implicitely castable to the correct datatype. Consult the table above.
void encode(uint8_t* buf, uint8_t len, const VariantValue& value) const
Encodes value into the supplied buf with maximum size len. The size must be at least the length of the datapoint.
VariantValue is a type to implicitely convert datatypes for use in VitoWiFi. Make sure to use the type that matches your Converter type.
VitoWiFi::PacketVS2
Only used in VS2. This type is used in the onResponse callback and contains the returned data.
Most users will only use the following two methods and only if they want to access the raw data. Otherwise, the data can be decoded using the corresponding Datapoint.
uint8_t dataLength() const
Returns the number of bytes in the payload.
