the78mole / esphome_components

ESPhome Components from the little digger
Other
24 stars 11 forks source link

Process a single bit of a telegram for a sensor. #9

Closed jensgraef closed 1 year ago

jensgraef commented 1 year ago

Be able to process a single bit of a byte of a specific telegram and use that for a sensor -> read "Betriebswerte" of HK1, WW and so on.

See https://github.com/the78mole/esphome_components/issues/4#issuecomment-1336187716 for the original suggestion.

Bascht74 commented 1 year ago

@jensgraef We need that e.g. for this:

{ LPWW , true, false, SensorType::INT, 0, "Ladepumpe", "", NULL }, // ["aus", "Ladepumpe", "Warmwasserpumpe", "beide"]

There we need to create two binary sensors out of it:

BIN 00 DEC 0 = Beide aus
BIN 01 DEC 1 = Ladepumpe an
BIN 10 DEC 2 = Zirkulationspumpe an
DIN 11 DEC 3 = Ladepumpe und Zirkulationspumpe an

actually this telegram is mapped to a binary sensor so only the first bit is processed (Ladepumpe an/aus).

Do you see any way to do that with the existing code?

This is the water heater of the Buderus: image

As you can see there must be something wrong with it because it is losing temperature quiet fast... 15°C in 2,6 hours !?!? That is why I want so see the circulation that is behind the other "bit" --> Zirkulationspumpe :-) ...

jensgraef commented 1 year ago

With the current code you could map it to a numeric sensor and do the remaining work in home assistant, e.g. with an automation that toggles two switches depending on the value of the number.

I've got a rough idea how to do it in the km217 module, but it's not yet full thought true:

  1. create a new sensor type BitArray
  2. create a new class for handling those bit arrays. This class should carry a list map of bit indices to binary sensors
  3. extend the python part to allow telling the module "this is a binary sensor, and it should watch bit x of this parameter
  4. extend the main class to add such a binary sensor to the class defined in 2

I wont have much time until next week, so it might take some time to build this.

the78mole commented 1 year ago

@jensgraef you name it, it is really crap to implement inside an ESPhome component.

Bascht74 commented 1 year ago

Yes, this is how modbus controller has done it.

Maybe this could help (I don't understand it completely but it seems to follow a lot of @jensgraef points... )

https://esphome.io/components/modbus_controller.html --> Section bitmask in the documentation

Code: https://github.com/esphome/esphome/blob/dev/esphome/components/modbus_controller/modbus_controller.h --> template N mask_and_shift_by_rightbit(N data, uint32_t mask) plus: https://github.com/esphome/esphome/tree/dev/esphome/components/modbus_controller/binary_sensor

Maybe we could do the same with the address and byte that has so be read?

So maybe something like this could be very flexibly, because everybody can "select" anything via his yaml code without changing the source / adding something to the source:

binary_sensor:
- platform: km271_wifi
  id: some_sensor_id
  name: "Zirkulationspumpe"
  address: 0x8429
  byte: 1
  bitmask: 0x2 #bit 1
- platform: km271_wifi
  id: some_sensor_id
  name: "Ladepumpe"
  address: 0x8429
  byte: 1 # first byte after the address
  bitmask: 0x1 #bit 0
- platform: km271_wifi
  id: some_sensor_id
  name: "Absenkung_Solar"
  address: 0x8429
  byte: 1 # first byte after the address
  bitmask: 0x2 #bit 2

same "style" for sensors:

- platform: km271_wifi
  id: raumsolltemperatur_hk1
  name: "Raumsolltemperatur HK1"
  address: 0x8004
  byte: 1 # first byte after the address
  unit_of_measurement: "°c"
  device_class: "temperature"
  state_class: "measurement"
  value_type: U_INT_DIV2
  accuracy_decimals: 1

This way we could even read the config variables to sensors:

- platform: km271_wifi
  id: tagtemperatur_hk1
  name: "Tagtemperatur_HK1"
  address: 0x0000
  byte: 3 # third byte after the address
  unit_of_measurement: "°c"
  device_class: "temperature"
  state_class: "measurement"
  value_type: U_INT # unsigned integer
  accuracy_decimals: 1

It would be more generic and will need longer yaml code but you would be very flexible with the yaml without changing anything to the c++ code...

select entites could go from:

select:
  - platform: template
    name: "Warmwasser Betriebsart"
    id: warmwasser_betriebsart
    entity_category: config
    optimistic: true
    options:
      - Dauerhaft aus (0)
      - Dauerhaft ein (1)
      - Automatik (2)
    initial_option: Automatik (2)
    set_action: 
      - lambda:
          auto index = id(warmwasser_betriebsart).index_of(x);
          if (index.has_value()) {
            uint8_t command[] = {0x0C, 0x0E, (uint8_t)index.value(), 0x65, 0x65, 0x65, 0x65, 0x65};
            budoil->writer.enqueueTelegram(command, 8);
          }

to:

select:
  - platform: km271_wifi
    name: "Warmwasser Betriebsart"
    id: warmwasser_betriebsart
    entity_category: config
    address: 0x0C0E
    byte: 1 # first byte after the address
    optionsmap:
      "Dauerhaft aus (0)": 0
      "Dauerhaft ein (1)": 1
      "Automatik (2)": 2
      "Three": 3

as to: https://esphome.io/components/select/modbus_controller.html

number would be like sensor and switch like binary sensor

jensgraef commented 1 year ago

25 adds the foundation for single bit sensors and adds the circulation pump / loading pump sensors.

@Bascht74 If you like you can check out the branch support-single-bit-sensors and check if it works as expected.

If someone wants to create a pull request for splitting all the other sensors (e.g. boiler error), feel free to do so.

qschneider commented 1 year ago

I extended km271.h and km271_params.h with more single-bit entities, if its ok i move on with binaray_sensor.py and buderus-km271.yaml

qschneider commented 1 year ago

km271.h#

#pragma once

#include <string>
#include <vector>
#include <chrono>
#include <iostream>
#include <sys/time.h>
#include <ctime>
#include "3964r.h"
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "km271_params.h"

#define GENERATE_SENSOR_SETTER(key, parameterId, sensorTypeParam) void set_##key##_sensor(esphome::sensor::Sensor *sensor) \
  { set_sensor(parameterId, sensorTypeParam, sensor); }
#define GENERATE_BINARY_SENSOR_SETTER(key, parameterId, sensorTypeParam) void set_##key##_binary_sensor(esphome::binary_sensor::BinarySensor *sensor) \
  { set_binary_sensor(parameterId, sensorTypeParam, sensor); }
#define GENERATE_SWITCH_SETTER(key, parameterId, sensorTypeParam) void set_##key##_switch(BuderusParamSwitch *switch_) \
  { set_switch(parameterId, sensorTypeParam, switch_); }
#define GENERATE_NUMBER_SETTER(key, parameterId, sensorTypeParam) void set_##key##_number(BuderusParamNumber *number) \
  { set_number(parameterId, sensorTypeParam, number); }

namespace esphome {
namespace KM271 {

class KM271Component : public Component, public uart::UARTDevice {
public:
  KM271Component();

  void loop() override;
  void dump_config() override;

  // Betriebswerte 1 HK1
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_switch_off_optimization, BW1HK1, 0);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_switch_on_optimization, BW1HK1, 1);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_automatic, BW1HK1, 2);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_ww_priority_processing, BW1HK1, 3);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_screed_drying, BW1HK1, 4);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_holiday, BW1HK1, 5);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_antifreeze, BW1HK1, 6);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_manually, BW1HK1, 7);
  // Betriebswerte 2 HK1
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_summer, BW2HK1, 0);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_day, BW2HK1, 1);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_no_comm_with_rc, BW2HK1, 2);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_rc_faulty, BW2HK1, 3);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_flow_sensor_error, BW2HK1, 4);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_max_flow, BW2HK1, 5);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_external_fault_input, BW2HK1, 6);
  // GENERATE_BINARY_SENSOR_SETTER(heating_circuit_1_party_pause, BW2HK1, 7);

  GENERATE_SENSOR_SETTER(heating_circuit_1_flow_target_temperature, VSTHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_flow_temperature, VITHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_room_target_temperature, RSTHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_room_temperature, RITHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_pump_power, PLHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_mixer_position, MSHK1, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_curve_p10, KLHK1_P10, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_curve_0, KLHK1_P00, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_1_curve_n10, KLHK1_N10, 0);

  // Betriebswerte 1 HK2
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_switch_off_optimization, BW1HK2, 0);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_switch_on_optimization, BW1HK2, 1);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_automatic, BW1HK2, 2);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_ww_priority_processing, BW1HK2, 3);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_screed_drying, BW1HK2, 4);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_holiday, BW1HK2, 5);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_antifreeze, BW1HK2, 6);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_manually, BW1HK2, 7);
  // Betriebswerte 2 HK2
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_summer, BW2HK2, 0);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_day, BW2HK2, 1);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_no_comm_with_rc, BW2HK2, 2);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_rc_faulty, BW2HK2, 3);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_flow_sensor_error, BW2HK2, 4);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_max_flow, BW2HK2, 5);
  GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_external_fault_input, BW2HK2, 6);
  //GENERATE_BINARY_SENSOR_SETTER(heating_circuit_2_party_pause, BW2HK2, 7);

  GENERATE_SENSOR_SETTER(heating_circuit_2_flow_target_temperature, VSTHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_flow_temperature, VITHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_room_target_temperature, RSTHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_room_temperature, RITHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_pump_power, PLHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_mixer_position, MSHK2, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_curve_p10, KLHK2_P10, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_curve_0, KLHK2_P00, 0);
  GENERATE_SENSOR_SETTER(heating_circuit_2_curve_n10, KLHK2_N10, 0);

  // Betriebswerte 1 WW
  GENERATE_BINARY_SENSOR_SETTER(ww_automatic, BW1WW, 0);
  GENERATE_BINARY_SENSOR_SETTER(ww_disinfection, BW1WW, 1);
  GENERATE_BINARY_SENSOR_SETTER(ww_reload, BW1WW, 2);
  GENERATE_BINARY_SENSOR_SETTER(ww_holiday, BW1WW, 3);
  GENERATE_BINARY_SENSOR_SETTER(ww_error_disinfection, BW1WW, 4);
  GENERATE_BINARY_SENSOR_SETTER(ww_error_sensor, BW1WW, 5);
  GENERATE_BINARY_SENSOR_SETTER(ww_error_stays_cold, BW1WW, 6);
  GENERATE_BINARY_SENSOR_SETTER(ww_error_anode, BW1WW, 7);
  // Betriebswerte 2 WW
  GENERATE_BINARY_SENSOR_SETTER(ww_loading, BW2WW, 0);
  GENERATE_BINARY_SENSOR_SETTER(ww_manually, BW2WW, 1);
  GENERATE_BINARY_SENSOR_SETTER(ww_reloading, BW2WW, 2);
  GENERATE_BINARY_SENSOR_SETTER(ww_switch_off_optimization, BW2WW, 3);
  GENERATE_BINARY_SENSOR_SETTER(ww_switch_on_optimization, BW2WW, 4);
  GENERATE_BINARY_SENSOR_SETTER(ww_day_mode, BW2WW, 5);
  GENERATE_BINARY_SENSOR_SETTER(ww_post_processing, BW2WW, 6);
  GENERATE_BINARY_SENSOR_SETTER(ww_priority_processing, BW2WW, 7);

  GENERATE_SENSOR_SETTER(ww_target_temperature, WWST, 0);
  GENERATE_SENSOR_SETTER(ww_temperature, WWIT, 0);

  GENERATE_SENSOR_SETTER(boiler_target_temperature, KVST, 0);
  GENERATE_SENSOR_SETTER(boiler_temperature, KVIT, 0);
  GENERATE_SENSOR_SETTER(boiler_turn_on_temperature, BET, 0);
  GENERATE_SENSOR_SETTER(boiler_turn_off_temperature, BAT, 0);
  GENERATE_SENSOR_SETTER(exhaust_gas_temperature, ABTMP, 0);

  GENERATE_SENSOR_SETTER(outdoor_temperature, AT, 0);
  GENERATE_SENSOR_SETTER(attenuated_outdoor_temperature, ATD, 0);
  // Pumpenansteuerung
  GENERATE_BINARY_SENSOR_SETTER(load_pump_running, LPWW, 0);
  GENERATE_BINARY_SENSOR_SETTER(circulation_pump_running, LPWW, 1);
  GENERATE_BINARY_SENSOR_SETTER(solar_pump_lowering, LPWW, 2);
  // Kesselfehler
  GENERATE_BINARY_SENSOR_SETTER(error_burner_malfunction, KFEHL, 0);
  GENERATE_BINARY_SENSOR_SETTER(error_boiler_sensor, KFEHL, 1);
  GENERATE_BINARY_SENSOR_SETTER(error_additional_sensor, KFEHL, 2);
  GENERATE_BINARY_SENSOR_SETTER(error_boiler_stays_cold, KFEHL, 3);
  GENERATE_BINARY_SENSOR_SETTER(error_exhaust_gas_sensor, KFEHL, 4);
  GENERATE_BINARY_SENSOR_SETTER(error_exhaust_gas_over_limit, KFEHL, 5);
  GENERATE_BINARY_SENSOR_SETTER(error_safety_chain_released, KFEHL, 6);
  GENERATE_BINARY_SENSOR_SETTER(error_external_disturbance, KFEHL, 7);
  // Kesselbetrieb
  GENERATE_BINARY_SENSOR_SETTER(boiler_emission_test, KBETR, 0);
  GENERATE_BINARY_SENSOR_SETTER(boiler_1st_stage_operation, KBETR, 1);
  GENERATE_BINARY_SENSOR_SETTER(boiler_protection, KBETR, 2);
  GENERATE_BINARY_SENSOR_SETTER(boiler_under_operation, KBETR, 3);
  GENERATE_BINARY_SENSOR_SETTER(boiler_performance_free, KBETR, 4);
  GENERATE_BINARY_SENSOR_SETTER(boiler_performance_high, KBETR, 5);
  GENERATE_BINARY_SENSOR_SETTER(boiler_2st_stage_operation, KBETR, 6);

  GENERATE_BINARY_SENSOR_SETTER(boiler_actuation, BANST, 0);

  GENERATE_SWITCH_SETTER(ww_heating_auto_off, CFG_WW_Aufbereitung, 0);
  GENERATE_NUMBER_SETTER(ww_temperature, CFG_WW_Temperatur, 3);

  void setup();
  float get_setup_priority() const override;
  void update();
  void on_shutdown();
  Writer3964R writer;

protected:
  const t_Buderus_R2017_ParamDesc *findParameterForNewSensor(Buderus_R2017_ParameterId parameterId, uint16_t sensorTypeParam, bool writableRequired);

  void set_sensor(Buderus_R2017_ParameterId parameterId, uint16_t sensorTypeParam, esphome::sensor::Sensor *sensor);
  void set_binary_sensor(Buderus_R2017_ParameterId parameterId, uint16_t sensorTypeParam, esphome::binary_sensor::BinarySensor *sensor);
  void set_switch(Buderus_R2017_ParameterId parameterId, uint16_t sensorTypeParam, BuderusParamSwitch *switch_);
  void set_number(Buderus_R2017_ParameterId parameterId, uint16_t sensorTypeParam, BuderusParamNumber *number);

  void process_incoming_byte(uint8_t c);
  void parse_buderus(uint8_t * buf, size_t len);

  // Helper function (for better readability of code)
  void send_ACK_DLE();
  void send_NAK();
  void writeRequestValues();
  size_t genDataString(char* outbuf, uint8_t* inbuf, size_t len);
  void print_hex_buffer(uint8_t* buf, size_t len);

  uint32_t last_received_byte_time;
  Parser3964R parser;
  /** used to call the loop function of the sensors every x calls to the loop function of the component */
  uint8_t sensorLoopCounter;
};

} // namespace KM271
} // namespace esphome
qschneider commented 1 year ago

km271_params.h

#pragma once

#include <string>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/number/number.h"
#include <unordered_map>

namespace esphome {
namespace KM271 {

enum Buderus_R2017_ParameterId {
  CFG_WW_Temperatur = 0x007e,
  CFG_WW_Aufbereitung = 0x0085,
  BW1HK1    = 0x8000, //: "Betriebswerte 1 HK1"
  BW2HK1    = 0x8001, //: "Betriebswerte 2 HK1"
  VSTHK1    = 0x8002, //: "Vorlaufsolltemperatur HK1"       (Grad)
  VITHK1    = 0x8003, //: "Vorlaufisttemperatur HK1"        (Grad)
  RSTHK1    = 0x8004, //: "Raumsolltemperatur HK1"          (Grad)
  RITHK1    = 0x8005, //: "Raumisttemperatur HK1"           (Grad)   
  EOZHK1    = 0x8006, //: "Einschaltoptimierungszeit HK1"
  AOZHK1    = 0x8007, //: "Ausschaltoptimierungszeit HK1"
  PLHK1     = 0x8008, //: "Pumpenleistung HK1"              (Prozent)
  MSHK1     = 0x8009, //: "Mischerstellung HK1"             (Prozent)
  NB01      = 0x800a, //: "nicht belegt"
  NB02      = 0x800b, //: "nicht belegt"
  KLHK1_P10 = 0x800c, //: "Heizkennlinie HK1 bei + 10 Grad" (Grad)
  KLHK1_P00 = 0x800d, //: "Heizkennlinie HK1 bei 0 Grad"    (Grad)
  KLHK1_N10 = 0x800e, //: "Heizkennlinie HK1 bei - 10 Grad" (Grad)
  NB03      = 0x800f, //: "nicht belegt"
  NB04      = 0x8010, //: "nicht belegt"
  NB05      = 0x8011, //: "nicht belegt"

  BW1HK2    = 0x8112, //: "Betriebswerte 1 HK2"
  BW2HK2    = 0x8113, //: "Betriebswerte 2 HK2"
  VSTHK2    = 0x8114, //: "Vorlaufsolltemperatur HK2"       (Grad)
  VITHK2    = 0x8115, //: "Vorlaufisttemperatur HK2"        (Grad)
  RSTHK2    = 0x8116, //: "Raumsolltemperatur HK2"          (Grad)
  RITHK2    = 0x8117, //: "Raumisttemperatur HK2"           (Grad)
  EOZHK2    = 0x8118, //: "Einschaltoptimierungszeit HK2"
  AOZHK2    = 0x8119, //: "Ausschaltoptimierungszeit HK2"
  PLHK2     = 0x811a, //: "Pumpenleistung HK2"              (Prozent)
  MSHK2     = 0x811b, //: "Mischerstellung HK2"             (Prozent)
  NB06      = 0x811c, //: "nicht belegt"
  NB07      = 0x811d, //: "nicht belegt"
  KLHK2_P10 = 0x811e, //: "Heizkennlinie HK2 bei + 10 Grad" (Grad)
  KLHK2_P00 = 0x811f, //: "Heizkennlinie HK2 bei 0 Grad"    (Grad)
  KLHK2_N10 = 0x8120, //: "Heizkennlinie HK2 bei - 10 Grad" (Grad)
  NB08      = 0x8121, //: "nicht belegt"
  NB09      = 0x8122, //: "nicht belegt"
  NB10      = 0x8123, //: "nicht belegt"

  BW1WW     = 0x8424, //: "Betriebswerte 1 WW"
  BW2WW     = 0x8425, //: "Betriebswerte 2 WW"
  WWST      = 0x8426, //: "Warmwassersolltemperatur"        (Grad)
  WWIT      = 0x8427, //: "Warmwasseristtemperatur",        (Grad)
  OZWW      = 0x8428, //: "Warmwasseroptimierungszeit"
  LPWW      = 0x8429, //: "Ladepumpe"                       ["aus", "Ladepumpe", "Warmwasserpumpe", "beide"]

  KVST      = 0x882a, //: "Kesselvorlaufsolltemperatur"     (Grad)
  KVIT      = 0x882b, //: "Kesselvorlaufisttemperatur"      (Grad)
  BET       = 0x882c, //: "Brennereinschalttemperatur"      (Grad)
  BAT       = 0x882d, //: "Brennerausschalttemperatur"      (Grad)
  KINT1     = 0x882e, //: "Kesselintegral 1"
  KINT2     = 0x882f, //: "Kesselintegral 2"
  KFEHL     = 0x8830, //: "Kesselfehler"
  KBETR     = 0x8831, //: "Kesselbetrieb"
  BANST     = 0x8832, //: "Brenneransteuerung"              ["aus", "an"]
  ABTMP     = 0x8833, //: "Abgastemperatur"                 (Grad)
  MODBSTELL = 0x8834, //: "modulare Brenner Stellwert"
  NB11      = 0x8835, //: "nicht belegt"
  BLZ1S2    = 0x8836, //: "Brennerlaufzeit 1 Stunden 2"
  BLZ1S1    = 0x8837, //: "Brennerlaufzeit 1 Stunden 1"
  BLZ1S0    = 0x8838, //: "Brennerlaufzeit 1 Stunden 0"
  BLZ2S2    = 0x8839, //: "Brennerlaufzeit 2 Stunden 2"
  BLZ2S1    = 0x883a, //: "Brennerlaufzeit 2 Stunden 1"
  BLZ2S0    = 0x883b, //: "Brennerlaufzeit 2 Stunden 0"

  AT        = 0x893c, //: "Aussentemperatur"                (Grad)
  ATD       = 0x893d, //: "gedaempfte Aussentemperatur"     (Grad)
  VVK       = 0x893e, //: "Versionsnummer VK"
  VNK       = 0x893f, //: "Versionsnummer NK"
  MODKENN   = 0x8940, //: "Modulkennung"
  NB12      = 0x8941, //: "nicht belegt"

  ALARM     = 0xaa42, //: "ERR_Alarmstatus"

  CFG1      = 0x0000, //: "Sommar ab, HK1 Nacht-/Tag-/Urlaubstemperatur, Betriebsart"
  CFG2      = 0x000E, //: "HK1 Max Temperatur, HK1 Auslegung"
  CFG3      = 0x0015, //: "HK1 Aufschalttemperatur, HK1 Aussenhalt_ab"
  CFG4      = 0x001c, //: "HK1 Absenkungsart, HK1 Heizsystem"
  CFG5      = 0x0031, //: "HK1 Temperatur Offset, HK1 Fernbedienung, Frost ab"
  CFG6      = 0x004d, //: "WW Vorgang"
  CFG7      = 0x0070, //: "Gebäudeart"
  CFG8      = 0x007e, //: "WW Temperatur"
  CFG9      = 0x0085, //: "WW Betriebsart, WW Aufbereitung, WW Zirkulation"
  CFG10     = 0x0093, //: "Sprache, Anzeige"
  CFG11     = 0x009a, //: "Brennerart, Max Kesseltemperatur"
  CFG12     = 0x00a1, //: "Pumplogik, Abgastemperaturschwelle"
  CFG13     = 0x00a8, //: "Brenner Min Modulation, Brenner Mod Laufzeit"
};

enum SensorType {
    NONE = 0,
    UNSIGNED_INT,
    SIGNED_INT,
    UNSIGNED_INT_DIVIDED_BY_2,
    STRING,
    BYTE_AT_OFFSET, // a single byte, with offset in bytes specified in sensor param
    BIT_AT_OFFSET, // a single bit, with offset in bits specified in sensor param
    TAG_NACHT_AUTO_SELECT, //   [ 0 => "Nacht", 1=> "Tag", 2=> "Automatik" ],
};

class Writer3964R;

class BuderusParamSwitch: public esphome::switch_::Switch {
public:
    BuderusParamSwitch();
    void setupWriting(Writer3964R * writer, Buderus_R2017_ParameterId parameterId, SensorType sensorType);
protected:
    void write_state(bool state) override;

private:
    Writer3964R * writer;
    Buderus_R2017_ParameterId parameterId;
    SensorType sensorType;
};

class BuderusParamNumber: public esphome::number::Number {
public:
    BuderusParamNumber();
    void setupWriting(Writer3964R * writer, Buderus_R2017_ParameterId parameterId, SensorType sensorType);
    void loop();

protected:
    void control(float value);

private:
    Writer3964R * writer;
    Buderus_R2017_ParameterId parameterId;
    SensorType sensorType;
    uint32_t lastWriteRequest;
    bool hasPendingWriteRequest;
    float pendingWriteValue;
};

struct t_Buderus_R2017_ParamDesc {
    Buderus_R2017_ParameterId parameterId;
    bool writable;
    SensorType sensorType;
    uint16_t sensorTypeParam;
    const char* desc;
    const char* unit;
};

typedef struct t_Buderus_R2017_ParamDesc t_Buderus_R2017_ParamDesc;

/** A BuderusValueHandler handles a value that we received from the heater. It passes the data to the configured switches, sensors and so on. */
class BuderusValueHandler {
    public:
        BuderusValueHandler(const t_Buderus_R2017_ParamDesc* paramDesc, esphome::sensor::Sensor * sensor);
        BuderusValueHandler(const t_Buderus_R2017_ParamDesc* paramDesc, esphome::binary_sensor::BinarySensor * sensor);
        BuderusValueHandler(const t_Buderus_R2017_ParamDesc* paramDesc, BuderusParamSwitch * paramSwitch);
        BuderusValueHandler(const t_Buderus_R2017_ParamDesc* paramDesc, BuderusParamNumber* paramNumber);

        void parseAndTransmit(uint8_t *data, size_t len);
        void loop();

    public:
       const t_Buderus_R2017_ParamDesc * paramDesc;

    private:
        esphome::sensor::Sensor *sensor;
        esphome::binary_sensor::BinarySensor *binarySensor;
        BuderusParamSwitch *switch_;
        BuderusParamNumber *number;
};

typedef std::unordered_multimap<Buderus_R2017_ParameterId, BuderusValueHandler *, std::hash<int>> ValueHandlerMap;

static ValueHandlerMap valueHandlerMap;

static const t_Buderus_R2017_ParamDesc buderusParamDesc[] = {
    {CFG_WW_Temperatur, true, SensorType::BYTE_AT_OFFSET, 3, "CFG_WW_Temperatur", ""},
    {CFG_WW_Aufbereitung, true, SensorType::TAG_NACHT_AUTO_SELECT, 0, "CFG_WW_Aufbereitung", ""},

  // Betriebswerte 1 HK1
  //{BW1HK1, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 1 HK1", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 0, "HK1 Ausschaltoptimierung", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 1, "HK1 Einschaltoptimierung", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 2, "HK1 Automatik", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 3, "HK1 Warmwasservorrang", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 4, "HK1 Estrichtrocknung", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 5, "HK1 Ferien", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 6, "HK1 Frostschutz", ""},
  {BW1HK1, false, SensorType::BIT_AT_OFFSET, 7, "HK1 Manuell", ""},
  // Betriebswerte 2 HK1
  //{BW2HK1, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 2 HK1", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 0, "HK1 Sommer-Modus", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 1, "HK1 Tag-Modus", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 2, "HK1 Keine Kommunikation mit FB", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 3, "HK1 FB fehlerhaft", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 4, "HK1 Fehler Vorlauffuehler", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 5, "HK1 Maximaler Vorlauf", ""},
  {BW2HK1, false, SensorType::BIT_AT_OFFSET, 6, "HK1 Externer Stoereingang", ""},
  //{BW2HK1, false, SensorType::BIT_AT_OFFSET, 7, "HK1 Frei - Party/Pause", ""}, // unklar was hier gemeint ist

  {BW2HK1, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 2 HK1", ""},
  {VSTHK1, false, SensorType::UNSIGNED_INT, 0, "Vorlaufsolltemperatur HK1", "°C"},           // (Grad)
  {VITHK1, false, SensorType::UNSIGNED_INT, 0, "Vorlaufisttemperatur HK1", "°C"},            // (Grad)
  {RSTHK1, false, SensorType::UNSIGNED_INT_DIVIDED_BY_2, 0, "Raumsolltemperatur HK1", "°C"}, // (Grad)
  {RITHK1, false, SensorType::UNSIGNED_INT_DIVIDED_BY_2, 0, "Raumisttemperatur HK1", "°C"},  // (Grad)
  {EOZHK1, false, SensorType::UNSIGNED_INT, 0, "Einschaltoptimierungszeit HK1", ""},
  {AOZHK1, false, SensorType::UNSIGNED_INT, 0, "Ausschaltoptimierungszeit HK1", ""},
  {PLHK1, false, SensorType::UNSIGNED_INT, 0, "Pumpenleistung HK1", "%"}, // (Grad)
  {MSHK1, false, SensorType::SIGNED_INT, 0, "Mischerstellung HK1", "%"},  // (Grad)
  {NB01, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB02, false, SensorType::NONE, 0, "nicht belegt", ""},
  {KLHK1_P10, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK1 bei + 10 Grad", "°C"}, // (Grad)
  {KLHK1_P00, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK1 bei 0 Grad", "°C"},    // (Grad)
  {KLHK1_N10, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK1 bei - 10 Grad", "°C"}, // (Grad)
  {NB03, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB04, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB05, false, SensorType::NONE, 0, "nicht belegt", ""},

  // Betriebswerte 1 HK2
  //{BW1HK2, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 1 HK2", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 0, "HK2 Ausschaltoptimierung", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 1, "HK2 Einschaltoptimierung", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 2, "HK2 Automatik", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 3, "HK2 Warmwasservorrang", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 4, "HK2 Estrichtrocknung", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 5, "HK2 Ferien", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 6, "HK2 Frostschutz", ""},
  {BW1HK2, false, SensorType::BIT_AT_OFFSET, 7, "HK2 Manuell", ""},
  // Betriebswerte 2 HK2
  //{BW2HK2, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 2 HK2", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 0, "HK2 Sommer-Modus", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 1, "HK2 Tag-Modus", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 2, "HK2 Keine Kommunikation mit FB", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 3, "HK2 FB fehlerhaft", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 4, "HK2 Fehler Vorlauffuehler", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 5, "HK2 Maximaler Vorlauf", ""},
  {BW2HK2, false, SensorType::BIT_AT_OFFSET, 6, "HK2 Externer Stoereingang", ""},
  //{BW2HK2, false, SensorType::BIT_AT_OFFSET, 7, "HK2 Frei - Party/Pause", ""}, // unklar was hier gemeint ist

  {VSTHK2, false, SensorType::UNSIGNED_INT, 0, "Vorlaufsolltemperatur HK2", "°C"},           // (Grad)
  {VITHK2, false, SensorType::UNSIGNED_INT, 0, "Vorlaufisttemperatur HK2", "°C"},            // (Grad)
  {RSTHK2, false, SensorType::UNSIGNED_INT_DIVIDED_BY_2, 0, "Raumsolltemperatur HK2", "°C"}, // (Grad)
  {RITHK2, false, SensorType::UNSIGNED_INT_DIVIDED_BY_2, 0, "Raumisttemperatur HK2", "°C"},  // (Grad)
  {EOZHK2, false, SensorType::UNSIGNED_INT, 0, "Einschaltoptimierungszeit HK2", ""},
  {AOZHK2, false, SensorType::UNSIGNED_INT, 0, "Ausschaltoptimierungszeit HK2", ""},
  {PLHK2, false, SensorType::UNSIGNED_INT, 0, "Pumpenleistung HK2", "%"},
  {MSHK2, false, SensorType::SIGNED_INT, 0, "Mischerstellung HK2", "%"},
  {NB06, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB07, false, SensorType::NONE, 0, "nicht belegt", ""},
  {KLHK2_P10, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK2 bei + 10 Grad", "°C"}, // (Grad)
  {KLHK2_P00, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK2 bei 0 Grad", "°C"},    // (Grad)
  {KLHK2_N10, false, SensorType::UNSIGNED_INT, 0, "Heizkennlinie HK2 bei - 10 Grad", "°C"}, // (Grad)
  {NB08, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB09, false, SensorType::NONE, 0, "nicht belegt", ""},
  {NB10, false, SensorType::NONE, 0, "nicht belegt", ""},

  // Betriebswerte 1 WW
  // {BW1WW, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 1 WW", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 0, "WW Automatik", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 1, "WW Desinfektion", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 2, "WW Nachladung", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 3, "WW Ferien", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 4, "WW Fehler Desinfektion", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 5, "WW Fehler Fuehler", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 6, "WW Fehler WW bleibt kalt", ""},
  {BW1WW, false, SensorType::BIT_AT_OFFSET, 7, "WW Fehler Anode", ""},
  // Betriebswerte 2 WW
  // {BW2WW, false, SensorType::UNSIGNED_INT, 0, "Betriebswerte 2 WW", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 0, "WW Laden", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 1, "WW Manuell", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 2, "WW Nachladen", ""}, // ist 1 bei Lade-/Nachlade-Aktivität der Warmwasser - Ladepumpe, jedoch nicht über die komplette Zeit.
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 3, "WW Ausschaltoptimierung", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 4, "WW Einschaltoptimierung", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 5, "WW Tag Modus", ""},
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 6, "WW Nachbereitung", ""},    // wird gleichzeitig mit dem 6. Bit 1, aber mit zeitlicher Verzögerung 0. Möglicherweise das Zeitfenster, in dem bei Bedarf noch Warmwasserbereitung, auch über den Tag - Heiz - Modus hinaus, stattfindet.
  {BW2WW, false, SensorType::BIT_AT_OFFSET, 7, "WW Vorrangschaltung", ""}, // ist 1, solange Warmwasserbereitung mit Vorrang stattfindet.

  {WWST, false, SensorType::UNSIGNED_INT, 0, "WW Solltemperatur", "°C"}, // (Grad)
  {WWIT, false, SensorType::UNSIGNED_INT, 0, "WW Isttemperatur", "°C"},  // (Grad)
  {OZWW, false, SensorType::UNSIGNED_INT, 0, "WW Optimierungszeit", ""},
  // Pumpenansteuerung
  //{LPWW, false, SensorType::UNSIGNED_INT, 0, "Ladepumpe", ""}, // ["aus", "Ladepumpe", "Warmwasserpumpe", "beide"]
  {LPWW, false, SensorType::BIT_AT_OFFSET, 0, "Ladepumpe", ""},
  {LPWW, false, SensorType::BIT_AT_OFFSET, 1, "Zirkulationspumpe", ""},
  {LPWW, false, SensorType::BIT_AT_OFFSET, 2, "Solarpumpe Absenkung", ""},
  {KVST, false, SensorType::UNSIGNED_INT, 0, "Kesselvorlaufsolltemperatur", "°C"}, // (Grad)
  {KVIT, false, SensorType::UNSIGNED_INT, 0, "Kesselvorlaufisttemperatur", "°C"},  // (Grad)
  {BET, false, SensorType::UNSIGNED_INT, 0, "Brennereinschalttemperatur", "°C"},   // (Grad)
  {BAT, false, SensorType::UNSIGNED_INT, 0, "Brennerausschalttemperatur", "°C"},   // (Grad)
  {KINT1, false, SensorType::UNSIGNED_INT, 0, "Kesselintegral 1", ""},
  {KINT2, false, SensorType::UNSIGNED_INT, 0, "Kesselintegral 2", ""},
  // Kesselfehler
  //{KFEHL, false, SensorType::UNSIGNED_INT, 0, "Kesselfehler", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 0, "Fehler Brennerstoerung", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 1, "Fehler Kesselfuehler", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 2, "Fehler Zusatzfuehler", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 3, "Fehler Kessel bleibt kalt", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 4, "Fehler Abgasfuehler", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 5, "Fehler Abgas ueber Grenzwert", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 6, "Fehler Sicherungskette ausgeloest", ""},
  {KFEHL, false, SensorType::BIT_AT_OFFSET, 7, "Fehler Externe Stoerung", ""},
  // Kesselbetrieb
  //{KBETR, false, SensorType::UNSIGNED_INT, 0, "Kesselbetrieb", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 0, "Kessel Abgastest", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 1, "Kessel Betrieb 1.Stufe", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 2, "Kessel Schutz", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 3, "Kessel unter Betrieb", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 4, "Kessel Leistung frei", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 5, "Kessel Leistung hoch", ""},
  {KBETR, false, SensorType::BIT_AT_OFFSET, 6, "Kessel Betrieb 2.Stufe", ""},
  {BANST, false, SensorType::UNSIGNED_INT, 0, "Brenneransteuerung", ""}, // 0: Brenner aus, 1 : Brenner an in Stufe 1, 2 : Brenner an in Stufe 2(laut Buderus - ungeprüft)
  {ABTMP, false, SensorType::UNSIGNED_INT, 0, "Abgastemperatur", "°C"},  // (Grad)
  {MODBSTELL, false, SensorType::UNSIGNED_INT, 0, "modulare Brenner Stellwert", ""},
  {NB11, false, SensorType::NONE, 0, "nicht belegt", ""},
  {BLZ1S2, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 1 Stunden 2", "h"},
  {BLZ1S1, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 1 Stunden 1", "h"},
  {BLZ1S0, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 1 Stunden 0", "h"},
  {BLZ2S2, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 2 Stunden 2", "h"},
  {BLZ2S1, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 2 Stunden 1", "h"},
  {BLZ2S0, false, SensorType::UNSIGNED_INT, 0, "Brennerlaufzeit 2 Stunden 0", "h"},

  {AT, false, SensorType::SIGNED_INT, 0, "Aussentemperatur", "°C"},             // (Grad)
  {ATD, false, SensorType::SIGNED_INT, 0, "Gedaempfte Aussentemperatur", "°C"}, // (Grad)
  {VVK, false, SensorType::UNSIGNED_INT, 0, "Versionsnummer VK", ""},
  {VNK, false, SensorType::UNSIGNED_INT, 0, "Versionsnummer NK", ""},
  {MODKENN, false, SensorType::UNSIGNED_INT, 0, "Modulkennung", ""},
  {NB12, false, SensorType::NONE, 0, "nicht belegt", ""},
  // Alarmstatus
  //{ALARM, false, SensorType::UNSIGNED_INT, 0, "Alarmstatus", ""}
  {ALARM, false, SensorType::BIT_AT_OFFSET, 0, "Alarm Abgasfuehler", ""},
  //{ALARM, false, SensorType::BIT_AT_OFFSET, 1, "Alarm 02", ""},
  {ALARM, false, SensorType::BIT_AT_OFFSET, 2, "Alarm Kesselvorlauffuehler", ""},
  //{ALARM, false, SensorType::BIT_AT_OFFSET, 3, "Alarm 08", ""},
  {ALARM, false, SensorType::BIT_AT_OFFSET, 4, "Alarm Brenner", ""},
  //{ALARM, false, SensorType::BIT_AT_OFFSET, 5, "Alarm 20", ""},
  {ALARM, false, SensorType::BIT_AT_OFFSET, 6, "Alarm HK2-Vorlauffuehler", ""},
  //{ALARM, false, SensorType::BIT_AT_OFFSET, 7, "Alarm 80", ""},

};
}
}
qschneider commented 1 year ago

i also streamlined hot_water_XX and warm_water_XX to ww_XX because its more consistant and easier to read. This can also be done for heating_circuit_1 and heating_circuit_2 alias hc1 and hc2. If you rather have it like it was, feel free to change it back. If its ok, it also needs a change in the esphome yaml - so this would be a breaking change in configuration...

Bascht74 commented 1 year ago

Will you make a pull request? I was planning to do exactly the same on the next weekend! Glad to see it already done -:)

@jensgraef @the78mole What do you two think about the streamlining suggestions?

jensgraef commented 1 year ago

@Bascht74 I'm always a fan of making thinks more consistent and thus simpler. I'll create a pull request on behalf of @qschneider

jensgraef commented 1 year ago

But on the other hand - I'm not a fan of abbreviations - they tend to make things more complicated. So I'd like warm_water to stay warm_water and not become ww. But that's not a hill I'm prepared to die on :)

jensgraef commented 1 year ago

I've created a pull request based on the suggested changes: #30

jensgraef commented 1 year ago

@the78mole @qschneider @Bascht74: Do you think we can close this issue now?

qschneider commented 1 year ago

maybe we have to give the entities more "self speaking" names in the future, but for now i think it's fine.

Bascht74 commented 1 year ago

Yes! Great!