Library for ESP8266 to communicate with Viessmann systems using a (DIY) serial optolink.
Based on the fantastic work on openv.
- VS1 (KW) and VS2 (P300) support. Older systems using the GWG protocol are not supported
- Non-blocking API calls
- For the Arduino framework and POSIX systems (Linux, tested on a Raspberry Pi 1B)
- Possible to use
SoftwareSerial
on ESP8266
- For Arduino IDE: see the Arduino Guide
- For Platformio: see the Platfomio registry page for VitoWifi
The optolink hardware for ESP8266 and ESP32 is 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
|
-----
---
-
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.
- 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;
}
}
you can find more examples in the examples
directory in this repo.
When defining your datapoints, you need to specify the name, address, length and converion 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.
Data is stored in binary and often needs a conversion function to transform into a more readable type. This is specified by the conversion type, the last argument in the datapoint definition.
Since C++ is a strongly typed programming language so using the right type is important (read: mandatory). Each conversion type corresponds with 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 |
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.
Below is an overview of all commonly used methods. For extra functions you can consult the source code.
Constructor for datapoints.
Self-explanatory
Returns the associated converter class. Can be used to select code flow in the callbacks.
Decodes the data in packet
using the converter class attached.
Returns VariantValue
which is implicitely castable to the correct datatype. Consult the table above.
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.
Encodes value
into the supplied buf
with maximum size len
. The size should obviously 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.
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
.
Returns the number of bytes in the payload.
Returns a pointer to the payload.
Constructor of the VitoWiFi class. PROTOCOL_VERSION
can be GWG
, VS1
or VS2
. If your Viessmann device is somewhat modern, you should use VS2
.
interface
can be any of the HardwareSerial
interfaces (Serial
, Serial1
...) on Arduino boards, SoftwareSerial
(on ESP8266) or if you are on Linux, pass the c-string depicting your device (for example "/dev/ttyUSB0"
).
Attach an onResponse callback. You can only attack one and will overwrite the previously attached callback.
The callback has the following signature:
VS1
: void (const uint8_t*, uint8_t, const VitoWiFi::Datapoint&)
VS2
: void (const VitoWiFi::PacketVS2&, const VitoWiFi::Datapoint&)
Attach an onError callback. You can only attack one and will overwrite the previously attached callback.
The callback has the following signature:
void (VitoWiFi::OptolinkResult, const VitoWiFi::Datapoint&)
Start the optolink serial interface. Returns bool on success.
Stop the optolink serial interface
Worker function, must be called regularly. This is the method that polls the serial interface. Callbacks are dispatched from this method so they will run in the same thread/task.
Read datapoint
. Returns true
on success.
Write value
with type T
to datapoint
. Make sure to use the correct type. consult the table with types in the "Datapoints" section.
Write the raw data
with length
to datapoint
. Returns true
on success. length
has to match the length of the datapoint.
Used in the onError callback. Possible returned values are:
- TIMEOUT
- LENGTH
- NACK
- CRC
- ERROR
This macro sets the initial payload (data) length VitoWiFi allocates for incoming packets. If you know beforehand the maximum data length you are going to request, you can set this to that value to prevent reallocation of dynamic memory. The default is 10 bytes.
Please use Github's facilities, issues and discussions, to get in touch. When creating a bug report, please use the provided template. In any case, better to include too much info than too little.
Copyright (c) 2017, 2023 Bert Melis
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- Hex print: 2011, robtillaart @ Arduino.cc forum (not used in v3 and above)
- Logger/Blinker: MIT 2015, marvinroger @ Github (not used in v3 and above)
- Serial Protocol @ ~~http://openv.wikispaces.com~~https://github.com/openv/openv/wiki
- @tolw for implementing the writing
- @Empor-co for testing the KW-protocol
- and many others for code and inspiration