Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#170 Pulse meter #310

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,34 @@ Depending on your HP model, SG3 might be configurable in "ECO mode", "Normal mod

Note: Smart Grid needs to be switched ON in the heatpump configuration menu, otherwise SG1 and SG2 contacts are not evaluated.

## Step 5 (optional) - Pulse Meter feature
_Needs step 4 - Smart grid features_
ESPaltherma can communicate how much energy the HP should consume via a pulse meter. For this, uncomment and confugre `PIN_PULSE`, `PULSES_PER_kWh` and `PULSE_DURATION_MS` in `src/setup.h`. Send energy amount in Watt to MQTT channel `espaltherma/pulse/set`. Current Watt setting is available in `espaltherma/pulse/state`.

### Hardware
Similar to the SG1 and SG2 connections, an additional relay is needed for the pulse meter. As relays have a rather limited cycle count and switching is slow, it is strongly recommended to use alternatives like optocouplers. The relay/optocoupler needs to be connected to the S4S contacts of your heat pump and needs a 200 Ohm resistor on the positive side of the connection.

For the optocoupler I used the [LTV 815](https://cdn-reichelt.de/documents/datenblatt/A200/LTV815_LTV825_LTV845_LIT.pdf). Be aware of the supported Voltage. On my heat pump I measured between 12 and 20 Volt on all connections (SG1, SG2 and Smart Grid). Here is the resulting PCB, note that also the SG1, SG2 contacts as well as the serial connection is implemented. _Important: The 200 Ohm resistor on the Smart Grid (S4S) connection is missing_

<img src="doc/images/PCB_bottom.jpg" width="250" style="display: inline"/> <img src="doc/images/PCB_top.jpg" width="200" /> <img src="doc/images/PCB_wiring.jpg" width="200" />

### Heat Pump Settings
[9.A.2] Installer settings > Energy Metering > Electricity meter 2: Set to "1000/kWh for PV-Panel". Keep this in sync with the `PULSES_PER_kWh` setting in `src/setup.h`.

Possibly following settings are also needed
[9.8.4] Installer settings > Benefit kWh power supply > Benefit kWh power supply: Set to "Smartgrid"

### Usage
Set the Smart Grid value to "2 - Recommended ON" and set a set the _excess power_ in Watt in the MQTT channel `espaltherma/pulse/set`.

Be aware, that this is not a direct command for the heat pump. It was build to be an information of a smart meter, thus the heat pumpt decides wheather or not to turn on. Could very well be, that the heat pump needs to see a certain level of power over a duration of time.
Also when the heat pump starts, the reported "excess power" needs to decrease. Unless you know what you do, it would be wise to report the exported power as seen by your smart meter!

Note the limits to the reporded power. Limits are depnendend on these settings:
* `PULSES_PER_kWh` (default is 1000)
* `PULSE_DURATION_MS` (default is 50)
For the above defaults, the minimum reported power is 60 W and the maximum is 18 kWh. If you need to represent more power, try reducing `PULSE_DURATION_MS` to at most 10. If this is not sufficient, adapt `PULSES_PER_kWh` but keep it in sync with the heat pump setting.

# Troubleshooting

## Specific issues with M5
Expand Down
Binary file added doc/images/PCB_bottom.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/PCB_top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/images/PCB_wiring.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions include/mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ void reconnectMqtt()
#ifndef PIN_SG1
// Publish empty retained message so discovered entities are removed from HA
client.publish("homeassistant/select/espAltherma/sg/config", "", true);
#endif
#ifdef PIN_PULSE
// Pulse Meter
client.publish("homeassistant/number/espAltherma/pulse/config", "{\"availability\":[{\"topic\":\"espaltherma/LWT\",\"payload_available\":\"Online\",\"payload_not_available\":\"Offline\"}],\"availability_mode\":\"all\",\"unique_id\":\"espaltherma_grid\",\"device\":{\"identifiers\":[\"ESPAltherma\"],\"manufacturer\":\"ESPAltherma\",\"model\":\"M5StickC PLUS ESP32-PICO\",\"name\":\"ESPAltherma\"},\"icon\":\"mdi:meter-electric\",\"name\":\"EspAltherma Power Limitation\",\"min\":0,\"max\":90000,\"mode\":\"box\",\"unit_of_measurement\":\"W\",\"command_topic\":\"espaltherma/pulse/set\",\"state_topic\":\"espaltherma/pulse/state\"}", true);
client.subscribe("espaltherma/pulse/set");
client.publish("espaltherma/pulse/state", "0");
#endif
#ifndef PIN_PULSE
// Publish empty retained message so discovered entities are removed from HA
client.publish("homeassistant/select/espAltherma/pulse/config", "", true);
#endif
}
else
Expand Down Expand Up @@ -184,6 +194,98 @@ void callbackSg(byte *payload, unsigned int length)
}
#endif

#ifdef PIN_PULSE
// time between pulses (excl. PULSE_DURATION_MS)
volatile double ms_until_pulse = 0;

// hardware timer pointer
hw_timer_t * timerPulseStart = NULL;
hw_timer_t * timerPulseEnd = NULL;

// hardware timer callback for when the pulse should start
void IRAM_ATTR onPulseStartTimer()
{
#ifdef PULSE_LED_BUILTIN
digitalWrite(LED_BUILTIN, HIGH);
#endif
digitalWrite(PIN_PULSE, HIGH);

timerWrite(timerPulseEnd, 0);
timerAlarmWrite(timerPulseEnd, PULSE_DURATION_MS * 1000, false);
timerAlarmEnable(timerPulseEnd);

}

// hardware timer callback when the pulse duration is over
void IRAM_ATTR onPulseEndTimer()
{
#ifdef PULSE_LED_BUILTIN
digitalWrite(LED_BUILTIN, LOW);
#endif
digitalWrite(PIN_PULSE, LOW);

timerWrite(timerPulseStart, 0);
timerAlarmWrite(timerPulseStart, ms_until_pulse * 1000, false);
timerAlarmEnable(timerPulseStart);

}

void setupPulseTimer()
{
Serial.println("Setting up pulse timer");
// Initilise the timer.
// Parameter 1 is the timer we want to use. Valid: 0, 1, 2, 3 (total 4 timers)
// Parameter 2 is the prescaler. The ESP32 default clock is at 80MhZ. The value "80" will
// divide the clock by 80, giving us 1,000,000 ticks per second.
// Parameter 3 is true means this counter will count up, instead of down (false).
timerPulseStart = timerBegin(0, 80, true);
timerPulseEnd = timerBegin(1, 80, true);

// Attach the timer to the interrupt service routine named "onTimer".
// The 3rd parameter is set to "true" to indicate that we want to use the "edge" type (instead of "flat").
timerAttachInterrupt(timerPulseStart, &onPulseStartTimer, true);
timerAttachInterrupt(timerPulseEnd, &onPulseEndTimer, true);

// one tick is 1 micro second -> multiply msec by 1000
timerAlarmWrite(timerPulseStart, ms_until_pulse * 1000, false);
timerAlarmEnable(timerPulseStart);
}

// Pulse Meter callback
void callbackPulse(byte *payload, unsigned int length)
{
payload[length] = '\0';
String ss((char*)payload);
long target_watt = ss.toInt();

// also converts from kWh to Wh
float WH_PER_PULSE = (1.0 / PULSES_PER_kWh) * 1000;

ms_until_pulse = ((3600.0 / target_watt) * WH_PER_PULSE * 1000) - PULSE_DURATION_MS;
if ((ms_until_pulse + PULSE_DURATION_MS) > 60 * 1000) {
// cap the maximum pulse length to 1 minute
// a change of the pulse is only applied, after the current pulse is finished. Thus if the pulse rate is very low,
// it will take a long time to adjust the rate
ms_until_pulse = 60 * 1000;
target_watt = (long) WH_PER_PULSE * 60;
Serial.printf("Capping pulse to %d Watt to ensure pulse rate is <= 60 sec\n", target_watt);
}
if (ms_until_pulse < 100) {
// ensure a 100 ms gap between two pulses
// taken from https://www.manualslib.de/manual/480757/Daikin-Brp069A61.html?page=10#manual
ms_until_pulse = 100;
long original_target = target_watt;
target_watt = (long) ((1000 * WH_PER_PULSE * 3600) / (ms_until_pulse + PULSE_DURATION_MS));
Serial.printf("WARNING pulse frequency to high, capping at %d Watt! Target is %d Watt. Decrease PULSE_DURATION or PULSES_PER_kWh\n", target_watt, original_target);
}
if (timerPulseStart == NULL) {
setupPulseTimer();
}
client.publish("espaltherma/pulse/state", String(target_watt).c_str());
Serial.printf("Set pulse meter to target %d Watt\n", target_watt);
}
#endif

void callback(char *topic, byte *payload, unsigned int length)
{
Serial.printf("Message arrived [%s] : %s\n", topic, payload);
Expand All @@ -197,6 +299,12 @@ void callback(char *topic, byte *payload, unsigned int length)
{
callbackSg(payload, length);
}
#endif
#ifdef PIN_PULSE
else if (strcmp(topic, "espaltherma/pulse/set") == 0)
{
callbackPulse(payload, length);
}
#endif
else
{
Expand Down
9 changes: 8 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ void extraLoop()
{
client.loop();
ArduinoOTA.handle();
// give interrupts time
vTaskDelay(0);
while (busy)
{ //Stop processing during OTA
ArduinoOTA.handle();
Expand Down Expand Up @@ -207,7 +209,12 @@ void setup()
digitalWrite(PIN_SG2, SG_RELAY_INACTIVE_STATE);
pinMode(PIN_SG1, OUTPUT);
pinMode(PIN_SG2, OUTPUT);

#endif
#ifdef PIN_PULSE
pinMode(PIN_PULSE, OUTPUT);
#ifdef PULSE_LED_BUILTIN
pinMode(LED_BUILTIN, OUTPUT);
#endif
#endif
#ifdef ARDUINO_M5Stick_C_Plus
gpio_pulldown_dis(GPIO_NUM_25);
Expand Down
11 changes: 9 additions & 2 deletions src/setup.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@

//Smart grid control - Optional:
//Uncomment and set to enable SG mqtt functions
//#define PIN_SG1 32// Pin connected to dry contact SG 1 relay (normally open)
//#define PIN_SG2 33// Pin connected to dry contact SG 2 relay (normally open)
// #define PIN_SG1 32// Pin connected to dry contact SG 1 relay (normally open)
// #define PIN_SG2 33// Pin connected to dry contact SG 2 relay (normally open)
// Define if your SG relay board is Low or High triggered (signal pins)
// Only uncomment one of them
#define SG_RELAY_HIGH_TRIGGER
//#define SG_RELAY_LOW_TRIGGER

//Pulse Meter control - Optional:
//Uncomment and set to enable Pulse Meter mqtt functions
// #define PIN_PULSE 25// Pin connected to pulse meter relay
// #define PULSES_PER_kWh 1000 // match setting on HP (TODO hint for setting path)
// #define PULSE_DURATION_MS 50 // Duration of the pulse, decrease on very high energy settings (TODO give example)
// #define PULSE_LED_BUILTIN 1 // also pulse the build in LED

// DO NOT CHANGE: Defines the SG active/inactive relay states, according to the definition of the trigger status
#if defined(SG_RELAY_LOW_TRIGGER)
#define SG_RELAY_ACTIVE_STATE LOW
Expand Down