bertmelis / VitoWiFi

Communicate with Viessmann boilers using the optolink for ESP8266 and ESP32
MIT License
123 stars 39 forks source link

VitoWifi ESP8266 with Blynk #22

Closed JacquesBB closed 6 years ago

JacquesBB commented 6 years ago

Installation specifics

VitoWifi ESP8266 with Blynk

This thread is to follow the evolution of a project I want to realise, that is be able to monitor my heater remotely with Blynk https://www.blynk.cc/ Bert, If you dont mind, I will ask here all the necessary questions for the implementation of my project and provide the details of its evolution.

First questions :

I see in your example

VitoWifi.addDatapoint("outsidetemp", "boiler", 0x5525, TEMP); VitoWifi.addDatapoint("boilertemp", "boiler", 0x0810, TEMP); But it is not clear to me where is the table of all adresses for the different functions ? for the KW protocol.

I would have expect to see a file with all this information, and I dont find it. DO you have it ?

Thanks

bertmelis commented 6 years ago

I have to redirect you to the openv-wiki: https://github.com/openv/openv/wiki/Adressen I don't have this information but there may be some ways to get to the needed info.

First you'll have to find out the type (ID) of your heater. The easiets is to use voIdent (https://github.com/openv/openv/wiki/voIdent). This should give you the ID by which we can look further.

Now probably a lot of the DPs have a similar address so you can always try. By just reading a DP you won't destroy your installation. The worst that can happen is your heater returning an error value or no connection at all.

JacquesBB commented 6 years ago

Thanks, I got the file and extracted the only part that concerns V200KW2 . I will start from there.

By the way. I have the impression that your schematics "circuit.png" is incorrect. The phototransistor has only two pins. The RX of the ESP8266 should be connected to the collector of the SFH309FA, so in your case, between the SFH309FA and the 10 k resistor. as in [https://github.com/openv/openv/wiki/Bauanleitung-ESP8266].

bertmelis commented 6 years ago

The circuit is wrong, I know. I've drawn the line just a little bit too low :smile:

Anyway, I installed the demo version of the Viessmann diagnostic program. It comes with a large xml file with a lot of datapoints but you'll have to know the ID of your device. I can easily write a sample to have VitoWifi check the ID of your device. From then on it's a fairly simple but tedious task to copy all the DPs. The protocol is simple: if 300 doesn't work you have to chose KW.

JacquesBB commented 6 years ago

Dear Bert,

I think I found most of the codes I need in the link you mentioned above. For a start, I need only two codes :

For the M2 setting (KW2) Room temperature setpoint |   |   |   | 0x3306 | 3..37 | 1 Red. Room temperature setpoint |   |   |   | 0x3307 | 3..37 | 1

My phone app will display these two values and will allow to change them.

In this case, what would be the minimal programming on the ESP8266, using your library that allows to read and write these two values ?

Thanks

bertmelis commented 6 years ago

Can you help me a bit? Where did you find 0x3306? I assme 3..37 is the allowed range? (I didn't implement this kind of check) What is the 1? Is the datapoint 1 byte long? Is 1 a conversion factor?

Note: I don't think anyone has already tried to write with the KW protocol using VitoWifi. You'll be the first. But I'll be glad to help! Note 2: for ESP8266 I stronly suggest to also use my https://github.com/bertmelis/WifiPrinter lib or https://github.com/JoaoLopesF/RemoteDebug as the ESP only has 1 serial port which will be occupied by the optolink. That way you can print debug messages via telnet/webpage.

JacquesBB commented 6 years ago

I found everything in https://github.com/openv/openv/wiki/Adressen

In the legend (first column), you find Operation (read write) A1M1: 0x23 .. (M2: 0x33 .., M3: 0x43 .., where available)

Room temperature setpoint is written 0x2306, so for M2, it becomes 0x3306 The 3..37 is the range, but I will not put limits in a first try. 1 is the number of bytes that is read or written.

For the debugging, I intend to use some virtual terminal on the Android phone through blynk http://docs.blynk.cc/#widgets-displays-terminal

I will post the results when done.

So the only thing I need on your side is a routine that read a value from an address or write a value to an address.

As I say, I will only start tomorrow, but will try to to it steadily to have rapidly a working device.

bertmelis commented 6 years ago

OK, I know what to do. Writing will be almost a shot in the dark though.

I've made a TEMP_S branch to test this one out. Be sure to switch to that version!

For the sketch try this (and adapt to your needs offcourse). The functions to write the temperature have to be called somewhere obviously.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <VitoWifi.h>
#include <WifiPrinter.h>

const char* ssid     = "your-ssid";
const char* password = "your-password";

WifiPrinter wifiPrinter;
VitoWifi_setProtocol(KW);

void handleRoomTemp(const char* name, const char* group, uint8_t value) {
  // do something with roomtemp
  wifiPrinter.printf("room temperature: %d", value);
}

void handleRoomTempRed(const char* name, const char* group, uint8_t value) {
  // do something with roomtemp
  wifiPrinter.printf("room temperature reduced: %d", value);
}

void setup() {
  // setup WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  // setup WifiPrinter
   wifiPrinter.begin();
  // setup VitoWifi
  VitoWifi.addDatapoint("roomtemp", "temp", 0x3306, TEMPS).setCallback(handleRoomTemp);
  VitoWifi.addDatapoint("roomtempred", "temp", 0x3307, TEMPS).setCallback(handleRoomTempRed);
  VitoWifi.setup(&Serial);
}

void loop() {
  static unsigned long lastMillis = 0;
  if (millis() - lastMillis > 60 * 1000UL) {  // read all values every 60 seconds
    lastMillis = millis();
    VitoWifi.readAll();
  }
  VitoWifi.loop();
  wifiPrinter.loop();

  // to write a value, call VitoWifi.writeDatapoint("roomtemp", 12); for example
  // method is VitoWifi.writeDatapoint(const char*, uint8_t);
}
JacquesBB commented 6 years ago

Thanks, I will try this now. First I had to make another optolink, and it took me longer than expected because I put the SFH309FA in the wrong side. The datasheet was not that explicit. Now its working. I can start the programming stage.

JacquesBB commented 6 years ago

Hi Bert,

It is great, it works !! I have even made it OTA, and started to integrate it with blynk ! Still need some polishing, but it is very promising. I have just the output in the terminal on my phone, which is already impressive. Would you be so kind to send me as well the order to change the temperature values ?

Thanks PS: I will post the full code when ready

bertmelis commented 6 years ago

Writing to DPs (= changing the temp) is done by VitoWifi.writeDatapoint(const char*, uint8_t);

So for instance: VitoWifi.writeDatapoint("roomtemp", 21);

Keep in mind that writing is completely untested!

JacquesBB commented 6 years ago

Right now, I have already set some way to get the desired temp to the ESP and I am testing by displaying the values in the terminal with

void handleRoomTemp(const char* name, const char* group, uint8_t value) {
  // do something with roomtemp
  TERM.printf("room temperature: Set : %d Actual %d\n", VTemp,value);
  Blynk.virtualWrite(V1, value);
}

void handleRoomTempRed(const char* name, const char* group, uint8_t value) {
  // do something with roomtemp
  TERM.printf("room temperature reduced: Set : %d Actual %d\n", VTempRed,value);
  Blynk.virtualWrite(V2, value);
}

Where would you put the VitoWifi.writeDatapoint("roomtemp", Vtemp);

line. Should I write

void handleRoomTemp(const char* name, const char* group, uint8_t value) {
  // do something with roomtemp
  VitoWifi.writeDatapoint("roomtemp", Vtemp)
  TERM.printf("room temperature: Set : %d Actual %d\n", VTemp,value);
  Blynk.virtualWrite(V1, value);
}

I can do that with a security flag as

void handleRoomTemp(const char* name, const char* group, uint8_t value) { // do something with roomtemp if (flag_temp) {VitoWifi.writeDatapoint("roomtemp", Vtemp);} TERM.printf("room temperature: Set : %d Actual %d\n", VTemp,value); Blynk.virtualWrite(V1, value); }

Where flag_temp is set by a button on my phone;

What do you think ?

bertmelis commented 6 years ago

I don't know anything about Blynk. How does your ESP receives events from Blynk?

Anyway, just put it somewhere in your loop(). If I understand correctly:

void loop() {
  if (flag_temp) {
    VitoWifi.writeDatapoint("roomtemp", Vtemp);
  }
  // here the rest of your loop-code
}
JacquesBB commented 6 years ago

Thanks Bert, but this time it does not work.

Blynk transmits data from the app ( the andoid phone) to the hardware (the ESP8266) with what they call "virtual pins". A virtual pin can contain anything. The transmission is done through a server.

Blynk is quite extraordinary in term of results with respect to programming effort. On the android side, everything is done with widgets, without coding, and on the ESP, the coding is minimal.

Here is my complete code. It handles both the OTA features, and the blynk interface. For blynk, it is better to have a very simple loop, and use a Timer for the different events.

/*
jxl_Vito_OTA
9/3/2018 Communicate with my Vitoladens Vitotronic 200  (KW)  with blynk
Thanks for openv (https://github.com/openv/openv/wiki)
and especially to Bert Melis for his ESP VitoWifi library ( https://github.com/bertmelis/VitoWifi)
 */
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <ArduinoOTA.h>
#include <VitoWifi.h>

// Go to the Project Settings (nut icon).
char auth[] = "xxxxxxxxxx"; // jxl_Vito_OTA 
char ssid[] = "xxxxxxx";
char pass[] = "xxxxxxxx";

// RTC de blynk
#include <Time.h>
#include <TimeLib.h>
#include <WidgetRTC.h>

BlynkTimer timer;

// attach terminal 
WidgetTerminal TERM(V20);
// RTC
WidgetRTC rtc;

// temp  values
uint8_t Temp, TempRed,VTemp, VTempRed;
// security flag
uint8_t flag_temp;

BLYNK_CONNECTED() {
  // Synchronize time on connection
  rtc.begin();
}

BLYNK_WRITE(V3) //temp change
{
  VTemp = (uint8_t)param.asInt(); 
}

BLYNK_WRITE(V4) //temp reduite change
{
  VTempRed = (uint8_t)param.asInt(); 
}
// flag de securite 
BLYNK_WRITE(V5){
    flag_temp=param.asInt();
    Blynk.virtualWrite(V6, flag_temp*255); // led de controle
}

VitoWifi_setProtocol(KW);

void handleRoomTemp(const char* name, const char* group, uint8_t Temp) {
  // do something with roomtemp
  TERM.printf("room temperature: Set : %d Actual %d\n", VTemp,Temp);
  Blynk.virtualWrite(V1, Temp);
}

void handleRoomTempRed(const char* name, const char* group, uint8_t TempRed) {
  // do something with roomtemp
  TERM.printf("room temperature reduced: Set : %d Actual %d\n", VTempRed,TempRed);
  Blynk.virtualWrite(V2, TempRed);
}

// the setup function runs once when you press reset or power the board
void setup() {

  TERM.flush();
  WiFi.mode(WIFI_STA);
  Blynk.begin(auth, ssid, pass);
  ArduinoOTA.setHostname("jxl_vito");
  ArduinoOTA.begin();
  setSyncInterval(10 * 60); // RTC Sync interval in seconds (10 minutes)
  delay(1000);  
//
  TERM.println("");
  TERM.println(clockDisplay());
  TERM.println("Ready - TEST OTA");
  TERM.print("IP address: ");
  TERM.println(WiFi.localIP());
  TERM.println("ça marche !");
  TERM.flush();

//vito 
  // setup VitoWifi
  VitoWifi.addDatapoint("roomtemp",    "temp", 0x3306, TEMPS).setCallback(handleRoomTemp);
  VitoWifi.addDatapoint("roomtempred", "temp", 0x3307, TEMPS).setCallback(handleRoomTempRed);
  VitoWifi.setup(&Serial);

   timer.setInterval( 10000L, jxl_clock);
   timer.setInterval( 10000L, vito);
}

// the loop function 
  void loop() {
  Blynk.run();
  timer.run();
  VitoWifi.loop();
  ArduinoOTA.handle();
   }

void vito(){
    VitoWifi.readAll();
    TERM.flush();
    TERM.printf("flag_temp : %d \n", flag_temp);
    if (flag_temp==1) {
// update values 
      VitoWifi.writeDatapoint("roomtemp"   , VTemp);    
      VitoWifi.writeDatapoint("roomtempred", VTempRed);    
    }
  TERM.flush();
}

void jxl_clock(){
  TERM.println("");
  TERM.println(clockDisplay());
  TERM.flush();
}

String clockDisplay()
// Digital clock display of the time
{
  String currentTime = "T: "+String(hour()) + ":" + minute() + ":" + second()+"  "+ String(day()) + " " + month() + " " + year();
  return currentTime;
}
JacquesBB commented 6 years ago

I finally found the way to properly quote the code in Markdown. ```C++
\ ```

It will make my posts more easy to read

bertmelis commented 6 years ago

Thanks for the code. Will look into this (and learn something about Blynk). I might only have some time this evening.

Does is to nothing at all of does it gives som weird errors?

JacquesBB commented 6 years ago

I have nothing in the terminal

I put a write in the loop to verify that I actually call the function. I get the message "passage in the loop" and nothing else. The values are not changed. I have previously verified with the Windows GUI interface that I can change these values with my optolink.

void vito(){
    VitoWifi.readAll();
    TERM.flush();
    TERM.printf("flag_temp : %d \n", flag_temp);
    if (flag_temp==1) {
// update values 
      TERM.printf("passage in the loop %d \n", flag_temp);
      VitoWifi.writeDatapoint("roomtemp"   , VTemp);    
      VitoWifi.writeDatapoint("roomtempred", VTempRed);    
    }
  TERM.flush();
}
JacquesBB commented 6 years ago

Hi Bert, Just an update.

I added the reading of two parameters : Burner Starts and Burner Status. Burner Starts ( 4 bytes)
Burner Status ( 1 bytes)

Contrarily to a previous message, they both work. I had a mistake. With TEMPS for 1 byte, it works.

void handleBurnerstarts(const char* name, const char* group, uint32_t BurnerStarts) {
  // do something with roomtemp
  TERM.printf("Burner Starts : %d\n", BurnerStarts);
  Blynk.virtualWrite(V10, BurnerStarts);
}

void handleBurnerstatus(const char* name, const char* group, uint8_t BurnerStatus) {
  // do something with roomtemp
  TERM.printf("Burner Status : %d\n", BurnerStatus);
  Blynk.virtualWrite(V11, BurnerStatus);
}

void setup() {
.......
//burner
  VitoWifi.addDatapoint("burnerstarts","burner", 0x088A, COUNT).setCallback(handleBurnerstarts);
  VitoWifi.addDatapoint("burnerstatus","burner", 0x551E, TEMPS).setCallback(handleBurnerstatus);
}
bertmelis commented 6 years ago

I'm trying to build a Blynk example myself but I can't get it to compile. Using VSCode and platformio on Win10. Blynktimer is not recognised...

#include <Arduino.h>
#include <VitoWifi.h>
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

const char ssid[] = "xxxx";
const char pass[] = "xxxx";
const char auth[] = "xxxx";
VitoWifi_setProtocol(P300);
bool updateItems = false;

BlynkTimer timer;
WidgetTerminal terminal(V1);

void update() {
  updateItems = true;
}

void globalCallbackHandler(const char* name, const char* group, const char* value) {
  terminal.printf("Received: %s - %s: %s\n", group, name, value);
}

void sendOutsidetemp(const char* name, const char* group, float value) {
  Blynk.virtualWrite(V0, value);
}

void setup() {
  // VitoWifi setup
  VitoWifi.setLogger(&terminal);
  VitoWifi.enableLogger();
  VitoWifi.addDatapoint("outsidetemp", "boiler", 0x5525, TEMP).setCallback(sendOutsidetemp);
  VitoWifi.setGlobalCallback(globalCallbackHandler);
  VitoWifi.setup(&Serial);

  Blynk.begin(auth, ssid, pass);
  timer.setInterval(30000L, update);
}

void loop() {
  VitoWifi.loop();
  Blynk.run();
  timer.run();
  if (updateItems) {
    updateItems = false;
    VitoWifi.readAll();
  }
}

It's for my heating so it may need some modifications. Made a dashboard with a terminal (V1) and a value display (V0)

JacquesBB commented 6 years ago

Maybe You should try with the arduino IDE. The libraries install very easily with the library manager, including the blynk library.

JacquesBB commented 6 years ago

Otherwise, you are not strictly obliged to use the timer library, but I experienced that the less you put in the main loop, the better it is for blynk.

You could try with the update call in the main loop

  if (millis() - lastMillis > 30 * 1000UL) {  // read all values every 30 seconds
    lastMillis = millis();
    update();
  }
bertmelis commented 6 years ago

It works now. So if you want to write to your heating you'll have to do something like this:

BLYNK_WRITE(V2) {
  int pinValue = param.asInt();
  VitoWifi.writeDatapoint("roomtemp", pinValue);
}

As I understand Blynk, this function will be called every time the widget connected to V2 has changed. The received value is then written to the VitoWifi.

bertmelis commented 6 years ago

FYI: The Arduino-loop in this firmware: https://github.com/bertmelis/esp-boiler takes a little less then 100µs on a Wemos D1 mini @ 80MHz. When people talk about long or blocking operations they usually talk about 10-20ms on these devices. Thats 200 times longer!

JacquesBB commented 6 years ago

As I understand Blynk, this function will be called every time the widget connected to V2 has changed. The received value is then written to the VitoWifi.

Yes, this is the way to communicate from the app on the phone to the hardware.

I am a little bit confused about the types. In my case, am I wrong to assume that the TEMPS type is uint8_t as it is coded on 1 byte ?

I have

uint8_t VTemp, VTempRed;
// in order to have global values 

// then 
BLYNK_WRITE(V3) //temp change
{
  VTemp = (uint8_t)param.asInt();
}

// and in the main loop, called by the timer
void vito(){
    VitoWifi.readAll();
      VitoWifi.writeDatapoint("roomtemp"   , VTemp);    
    }
}

But it still does not work, although I verify that VTemp is well transmitted ( I print it).

JacquesBB commented 6 years ago

I made an even simpler case

  void loop() {
  Blynk.run();
  timer.run();
  VitoWifi.loop();
  ArduinoOTA.handle();
   }

void vito(){
    VitoWifi.readAll();
    VitoWifi.writeDatapoint("roomtemp" , 14);    
}

But it still does not work. It reads everything, but It does not change the roomtemp value.

bertmelis commented 6 years ago

This should work. Keep in mind that VitoWifi is sending debug info at a quite fast rate and this may be too fast for over-the-internet transmission. So maybe comment our the setLogger and enableLogger lines

/* 
  V0: step V (set room temp)
  V1: labeled value (outside temp)
  V2: labeled value (set room temp)
  V3: terminal
*/

#include <Arduino.h>
#include <VitoWifi.h>  // https://github.com/bertmelis/VitoWifi
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

const char ssid[] = "xxxx";
const char pass[] = "xxxx";
const char auth[] = "xxxx";
VitoWifi_setProtocol(KW);
bool updateItems = false;

BlynkTimer timer;
WidgetTerminal terminal(V3);

// function to run every time BlynkTimer timer fires: just set a flag and handle in main loop
void update() {
  updateItems = true;
}

// fallback when no handler has been found, just print received data to Blynk terminal
void globalCallbackHandler(const char* name, const char* group, const char* value) {
  terminal.printf("Received: %s - %s: %s\n", group, name, value);
}

// send receive outside temperature to virtual pin V1
void sendOutsidetemp(const char* name, const char* group, float value) {
  Blynk.virtualWrite(V1, value);
}

// send receive room temperature (soll) to virtual pin V2
void sendRoomtempSet(const char* name, const char* group, byte value) {
  Blynk.virtualWrite(V2, value);
}

// callback for Blynk when widget Step V (on V0) has been changed
// write the receive value to VitoWifi and read back
BLYNK_WRITE(V0) {
  int pinValue = param.asInt();
  terminal.printf("Blynk update: V0 = %d\n", pinValue);
  VitoWifi.writeDatapoint("roomtempset", pinValue);
  VitoWifi.readDatapoint("roomtempset");
}

void setup() {
  // VitoWifi setup
  VitoWifi.setLogger(&terminal);  // might be too verbose/fast for Blynk to handle
  VitoWifi.enableLogger();  // might be too verbose/fast for Blynk to handle
  VitoWifi.addDatapoint("outsidetemp", "boiler", 0x5525, TEMP).setCallback(sendOutsidetemp);
  VitoWifi.addDatapoint("roomtempset", "heating", 0x2306, TEMPS).setWriteable().setCallback(sendRoomtempSet);
  VitoWifi.setGlobalCallback(globalCallbackHandler);
  VitoWifi.setup(&Serial);

  // Blynk setup
  Blynk.begin(auth, ssid, pass);
  timer.setInterval(30000L, update);
}

void loop() {
  VitoWifi.loop();
  Blynk.run();
  timer.run();

  // clear flag and read all datapoints
  if (updateItems) {
    updateItems = false;
    terminal.println("Updating items!");
    VitoWifi.readAll();
  }
}
JacquesBB commented 6 years ago

Thanks a lot for your effort, Bert !

The above changes you propose is to add the "setWriteable()" option in

VitoWifi.addDatapoint("roomtempset", "heating", 0x2306, TEMPS).setWriteable().setCallback(sendRoomtemp);

I will certainly try, but it will not be before next Friday evening, as I am out of my house for the week. I can program my ESP8266 OTA, but only on the same network.
Meanwhile, I will be able to improve my knowledge of your library.

It certainly is very powerful, and programmed efficiently, but to grasp all the possibilities is not that obvious. Especially at the beginning, as I found only yesterday late evening your reference file

https://github.com/bertmelis/VitoWifi/blob/TEMP_S/REFERENCE.md

It already helps, but what is needed, is a text providing the general structure of your library.

By the way, I hope you are enjoying interfacing with Blynk. It should be my small contribution to this project. When my app will be complete, or nearly, I will post it.

Best Wishes Jacques

bertmelis commented 6 years ago

Documentation is the Achilles' heel of many projects... Mine is not different :smile:

Please post your working example. You can even make a pull request to add it to the examples. I'll hear when you need more info.

JacquesBB commented 6 years ago

In all these codes, I dont see where one can set up the Heating time program.

Is it possible to have access to this programming as well ?

bertmelis commented 6 years ago

That's not implemented (yet). I don't use every possibility myself and add feature as they are asked or contributed.

Programming time functions behave like all the rest. It's only a datapoint type that has to be added. Only thing is that the dataponit structure has to be improved to keep up maintainability. When the list of implemented types grows, it is harder to keep an overview. I don't know yet how this has to be done.

JacquesBB commented 6 years ago

Right now, it is not a big issue for me. I will be perfectly happy if I can properly set the temperatures with the app, as I can then schedule anything I want through this app. My question was mostly by simple curiosity, as it was not clear for me.

JacquesBB commented 6 years ago

I see in your doc (https://github.com/bertmelis/VitoWifi/blob/TEMP_S/REFERENCE.md) that there is the option to say

  VitoWifi.addDatapoint("roomtempset", "heating", 0x2306, TEMPS, true).setCallback(sendRoomtempSet);

instead of

  VitoWifi.addDatapoint("roomtempset", "heating", 0x2306, TEMPS).setWriteable().setCallback(sendRoomtempSet);

Are the two options valid ? equivalent ?

bertmelis commented 6 years ago

the two options are equal by function. The first one is quicker, it avoids the extra function call.

JacquesBB commented 6 years ago

Hi Bert, I just arrived in my house. Immediately dowloaded my new code, and ...

IT WORKS !!!

Thanks a lot for the help. I will certainly come back to you, as I will now polish my code and interface, and may wish to have at the same time the linux command line interface and the Blynk visual interface. But this will be another story. I am already very happy with the present results.

bertmelis commented 6 years ago

Glad to hear! Thank you for testing!

I'm looking forward to see your developments.

bertmelis commented 6 years ago

Closing: "it works". Included the example in the code.