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

[P076] Added the ability to read reactive power, apparent power and energy #5169

Open
wants to merge 17 commits into
base: mega
Choose a base branch
from
Open
Changes from 15 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
214 changes: 171 additions & 43 deletions src/_P076_HLW8012.ino
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
//

/** Changelog:
* 2024-11-24 dsiggi: - Added the ability to read reactive power, apparent power and energy
* - Added ability to reset the energy counter with console command hlwresetenergy
* 2024-12-01 dsiggi: - The old measurement values (voltage, current and power factor) are now again the default to support old setups.
* - Removed spaces from value names
* - Uncrustify
* 2023-01-03 tonhuisman: Uncrustify source, apply some code improvements
* Older changelog not registered.
*/
Expand All @@ -31,10 +36,6 @@ HLW8012 *Plugin_076_hlw = nullptr;
# define PLUGIN_ID_076 76
# define PLUGIN_076_DEBUG false // activate extra log info in the debug
# define PLUGIN_NAME_076 "Energy (AC) - HLW8012/BL0937"
# define PLUGIN_VALUENAME1_076 "Voltage"
# define PLUGIN_VALUENAME2_076 "Current"
# define PLUGIN_VALUENAME3_076 "Power"
# define PLUGIN_VALUENAME4_076 "PowerFactor"

# define HLW_DELAYREADING 500

Expand All @@ -44,13 +45,28 @@ HLW8012 *Plugin_076_hlw = nullptr;
# define HLW_VOLTAGE_RESISTOR_DOWN (1000) // Real 1.009k
// -----------------------------------------------------------------------------------------------
int StoredTaskIndex = -1;
uint8_t p076_read_stage{};
unsigned long p076_timer{};

float p076_hcurrent{};
float p076_hvoltage{};
float p076_hpower{};
float p076_hpowfact{};
uint8_t p076_read_stage {};
unsigned long p076_timer {};

float p076_hcurrent {};
float p076_hvoltage {};
float p076_hpowerActive {};
float p076_hpowerReactive {};
float p076_hpowerApparent {};
float p076_hpowfact {};
float p076_henergy {};

# define P076_NR_OUTPUT_VALUES 4
# define P076_NR_OUTPUT_OPTIONS 7
# define P076_QUERY1_CONFIG_POS 0
# define P076_QUERY1 PCONFIG(0)
# define P076_QUERY2 PCONFIG(1)
# define P076_QUERY3 PCONFIG(2)
# define P076_QUERY4 PCONFIG(3)
# define P076_QUERY1_DFLT 0 // Voltage (V)
# define P076_QUERY2_DFLT 1 // Current (A)
# define P076_QUERY3_DFLT 2 // Active Power (W)
# define P076_QUERY4_DFLT 5 // Power Factor (cosphi)

# define P076_Custom 0

Expand All @@ -68,6 +84,9 @@ float p076_hpowfact{};
# define P076_Gosund 9
# define P076_Shelly_PLUG_S 10

// Forward declaration helper function
const __FlashStringHelper* p076_getQueryString(uint8_t query);

# if ESP_IDF_VERSION_MAJOR >= 5

// FIXME TD-er: Must check if older (and ESP8266) envs need IRAM_ATTR in the function declaration.
Expand Down Expand Up @@ -119,6 +138,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
Device[deviceCount].TimerOption = true;
Device[deviceCount].PluginStats = true;
Device[deviceCount].setPin1Direction(gpio_direction::gpio_output);
Device[deviceCount].OutputDataType = Output_Data_type_t::Simple;
break;
}

Expand All @@ -128,14 +148,25 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
}

case PLUGIN_GET_DEVICEVALUENAMES: {
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0],
PSTR(PLUGIN_VALUENAME1_076));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1],
PSTR(PLUGIN_VALUENAME2_076));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2],
PSTR(PLUGIN_VALUENAME3_076));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3],
PSTR(PLUGIN_VALUENAME4_076));
for (uint8_t i = 0; i < VARS_PER_TASK; ++i) {
if (i < P076_NR_OUTPUT_VALUES) {
uint8_t choice = PCONFIG(i + P076_QUERY1_CONFIG_POS);
ExtraTaskSettings.setTaskDeviceValueName(i, p076_getQueryString(choice));
} else {
ExtraTaskSettings.clearTaskDeviceValueName(i);
}
}
break;
}

case PLUGIN_SET_DEFAULTS: {
dsiggi marked this conversation as resolved.
Show resolved Hide resolved
// Load some defaults
P076_QUERY1 = P076_QUERY1_DFLT;
P076_QUERY2 = P076_QUERY2_DFLT;
P076_QUERY3 = P076_QUERY3_DFLT;
P076_QUERY4 = P076_QUERY4_DFLT;

success = true;
break;
}

Expand All @@ -146,6 +177,24 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
break;
}

case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: {
// To select the data in the 4 fields.
const __FlashStringHelper *options[P076_NR_OUTPUT_OPTIONS];

for (uint8_t i = 0; i < P076_NR_OUTPUT_OPTIONS; ++i) {
options[i] = p076_getQueryString(i);
}

for (uint8_t i = 0; i < P076_NR_OUTPUT_VALUES; ++i) {
const uint8_t pconfigIndex = i + P076_QUERY1_CONFIG_POS;
sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P076_NR_OUTPUT_OPTIONS, options);
}

success = true;

break;
}

case PLUGIN_WEBFORM_LOAD: {
uint8_t devicePinSettings = PCONFIG(7);

Expand Down Expand Up @@ -287,7 +336,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
hlwMultipliers[2] = getFormItemFloat(F("powmult"));

if ((hlwMultipliers[0] > 1.0) && (hlwMultipliers[1] > 1.0) && (hlwMultipliers[2] > 1.0)) {
SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast<const uint8_t *>(&hlwMultipliers),
SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast < const uint8_t * > (&hlwMultipliers),
dsiggi marked this conversation as resolved.
Show resolved Hide resolved
sizeof(hlwMultipliers));
# if PLUGIN_076_DEBUG
addLog(LOG_LEVEL_INFO, F("P076: Saved Calibration from Config Page"));
Expand All @@ -305,10 +354,17 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
}

# if PLUGIN_076_DEBUG
addLogMove(LOG_LEVEL_INFO, strformat(F("P076: PIN Settings curr_read: %d cf_edge: %d cf1_edge: %d")
addLogMove(LOG_LEVEL_INFO, strformat(F("P076: PIN Settings curr_read: %d cf_edge: %d cf1_edge: %d"),
PCONFIG(4), PCONFIG(5), PCONFIG(6)));
# endif // if PLUGIN_076_DEBUG

// Save output selector parameters.
for (uint8_t i = 0; i < P076_NR_OUTPUT_VALUES; ++i) {
const uint8_t pconfigIndex = i + P076_QUERY1_CONFIG_POS;
const uint8_t choice = PCONFIG(pconfigIndex);
sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p076_getQueryString(choice));
}

success = true;
break;
}
Expand Down Expand Up @@ -339,9 +395,12 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
case 3: // Read voltage + active power + power factor

if (timeOutReached(p076_timer)) {
p076_hvoltage = Plugin_076_hlw->getVoltage(valid);
p076_hpower = Plugin_076_hlw->getActivePower(valid);
p076_hpowfact = static_cast<int>(100 * Plugin_076_hlw->getPowerFactor(valid));
p076_hvoltage = Plugin_076_hlw->getVoltage(valid);
p076_hpowerActive = Plugin_076_hlw->getActivePower(valid);
p076_hpowerReactive = Plugin_076_hlw->getReactivePower(valid);
p076_hpowerApparent = Plugin_076_hlw->getApparentPower(valid);
p076_hpowfact = static_cast < int > (100 * Plugin_076_hlw->getPowerFactor(valid));
p076_henergy = Plugin_076_hlw->getEnergy();
++p076_read_stage;

// Measurement is done, schedule a new PLUGIN_READ call
Expand All @@ -363,41 +422,71 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
// Force a measurement start.
// ++p076_read_stage;
// } else if (p076_read_stage > 3) {
bool valid = false;
p076_hpower = Plugin_076_hlw->getActivePower(valid);
bool valid = false;
float HLW[7];
p076_hpowerActive = Plugin_076_hlw->getActivePower(valid);

if (valid) {
HLW[2] = p076_hpowerActive;
success = true;
}

p076_hvoltage = Plugin_076_hlw->getVoltage(valid);

if (valid) {
HLW[0] = p076_hvoltage;
success = true;
}

p076_hcurrent = Plugin_076_hlw->getCurrent(valid);

if (valid) {
HLW[1] = p076_hcurrent;
success = true;
}
p076_hpowfact = static_cast<int>(100 * Plugin_076_hlw->getPowerFactor(valid));

p076_hpowfact = static_cast < int > (100 * Plugin_076_hlw->getPowerFactor(valid));

if (valid) {
HLW[5] = p076_hpowfact;
success = true;
}

UserVar.setFloat(event->TaskIndex, 0, p076_hvoltage);
UserVar.setFloat(event->TaskIndex, 1, p076_hcurrent);
UserVar.setFloat(event->TaskIndex, 2, p076_hpower);
UserVar.setFloat(event->TaskIndex, 3, p076_hpowfact);
p076_hpowerReactive = Plugin_076_hlw->getReactivePower(valid);

if (valid) {
HLW[3] = p076_hpowerReactive;
success = true;
dsiggi marked this conversation as resolved.
Show resolved Hide resolved
}

p076_hpowerApparent = Plugin_076_hlw->getApparentPower(valid);

if (valid) {
HLW[4] = p076_hpowerApparent;
success = true;
dsiggi marked this conversation as resolved.
Show resolved Hide resolved
}

p076_henergy = Plugin_076_hlw->getEnergy();
HLW[6] = p076_henergy;

UserVar.setFloat(event->TaskIndex, 0, HLW[P076_QUERY1]);
UserVar.setFloat(event->TaskIndex, 1, HLW[P076_QUERY2]);
UserVar.setFloat(event->TaskIndex, 2, HLW[P076_QUERY3]);
UserVar.setFloat(event->TaskIndex, 3, HLW[P076_QUERY4]);

// Measurement is complete.
p076_read_stage = 0;

# if PLUGIN_076_DEBUG
addLogMove(LOG_LEVEL_INFO,
strformat(F("P076: Read values - V=%.2f - A=%.2f - W=%.2f - Pf%%=%.2f"),
p076_hvoltage, p076_hcurrent, p076_hpower, p076_hpowfact));
strformat(F("P076: Read values - V=%.2f - A=%.2f - W=%.2f - VAR=%.2f - VA=%.2f - Pf%%=%.2f - Ws=%.2f"),
p076_hvoltage,
p076_hcurrent,
p076_hpowerActive,
p076_hpowerReactive,
p076_hpowerApparent,
p076_hpowfact,
p076_henergy));
# endif // if PLUGIN_076_DEBUG

// Plugin_076_hlw->toggleMode();
Expand All @@ -420,7 +509,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
const uint8_t SEL_PIN = CONFIG_PIN1;

if (validGpio(CF_PIN) && validGpio(CF1_PIN) && validGpio(SEL_PIN)) {
Plugin_076_hlw = new (std::nothrow) HLW8012;
Plugin_076_hlw = new(std::nothrow) HLW8012;

if (Plugin_076_hlw) {
const uint8_t currentRead = PCONFIG(4);
Expand Down Expand Up @@ -467,6 +556,14 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)

// Need a few seconds to read the first sample, so trigger a new read a few seconds after init.
Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 5000);

// Set default querys if not set
if ((P076_QUERY1 == 0) && (P076_QUERY2 == 0) && (P076_QUERY3 == 0) && (P076_QUERY4 == 0)) {
P076_QUERY1 = P076_QUERY1_DFLT;
P076_QUERY2 = P076_QUERY2_DFLT;
P076_QUERY3 = P076_QUERY3_DFLT;
P076_QUERY4 = P076_QUERY4_DFLT;
}
success = true;
}
}
Expand Down Expand Up @@ -522,12 +619,26 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
}
success = true;
}

if (equals(command, F("hlwresetenergy"))) {
Plugin076_ResetEnergy();
success = true;
}
}
break;
}
return success;
}

void Plugin076_ResetEnergy() {
if (Plugin_076_hlw) {
Plugin_076_hlw->resetEnergy();
# if PLUGIN_076_DEBUG
addLog(LOG_LEVEL_INFO, F("P076: Reset Energy counter to zero"));
# endif // if PLUGIN_076_DEBUG
}
}

void Plugin076_ResetMultipliers() {
if (Plugin_076_hlw) {
Plugin_076_hlw->resetMultipliers();
Expand All @@ -542,19 +653,19 @@ void Plugin076_SaveMultipliers() {
if (StoredTaskIndex < 0) {
return; // Not yet initialized.
}
ESPEASY_RULES_FLOAT_TYPE hlwMultipliers[3]{};
ESPEASY_RULES_FLOAT_TYPE hlwMultipliers[3] {};

if (Plugin076_ReadMultipliers(hlwMultipliers[0], hlwMultipliers[1], hlwMultipliers[2])) {
# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast<const uint8_t *>(&hlwMultipliers),
SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast < const uint8_t * > (&hlwMultipliers),
sizeof(hlwMultipliers));
# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
double hlwMultipliers_d[3]{};
double hlwMultipliers_d[3] {};
hlwMultipliers_d[0] = hlwMultipliers[0];
hlwMultipliers_d[1] = hlwMultipliers[1];
hlwMultipliers_d[2] = hlwMultipliers[2];

SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast<const uint8_t *>(&hlwMultipliers_d),
SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast < const uint8_t * > (&hlwMultipliers_d),
sizeof(hlwMultipliers_d));
# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
}
Expand Down Expand Up @@ -585,7 +696,7 @@ bool Plugin076_LoadMultipliers(taskIndex_t TaskIndex,
}
double hlwMultipliers[3];

LoadCustomTaskSettings(TaskIndex, reinterpret_cast<uint8_t *>(&hlwMultipliers),
LoadCustomTaskSettings(TaskIndex, reinterpret_cast < uint8_t * > (&hlwMultipliers),
sizeof(hlwMultipliers));

if (hlwMultipliers[0] > 1.0) {
Expand Down Expand Up @@ -615,10 +726,13 @@ void Plugin076_Reset(taskIndex_t TaskIndex) {
p076_read_stage = 0;
p076_timer = 0;

p076_hcurrent = 0.0f;
p076_hvoltage = 0.0f;
p076_hpower = 0.0f;
p076_hpowfact = 0.0f;
p076_hcurrent = 0.0f;
p076_hvoltage = 0.0f;
p076_hpowerActive = 0.0f;
p076_hpowerReactive = 0.0f;
p076_hpowerApparent = 0.0f;
p076_hpowfact = 0.0f;
p076_henergy = 0.0f;
}

// When using interrupts we have to call the library entry point
Expand All @@ -635,4 +749,18 @@ void IRAM_ATTR p076_hlw8012_cf_interrupt() {
}
}

const __FlashStringHelper* p076_getQueryString(uint8_t query) {
switch (query)
{
case 0: return F("Voltage_V");
case 1: return F("Current_A");
case 2: return F("Active_Power_W");
case 3: return F("Reactive_Power_VAR");
case 4: return F("Apparent_Power_VA");
case 5: return F("Power_Factor_cosphi");
case 6: return F("Energy_Ws");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See what I did for the P082_GPS in Plugin_082_valuename
There I have the display string and a task value string.
The display string is shown in the combobox when selecting the output value.
The non-display string is the shorter taskvalue name.
So maybe that's also more useful here as you would otherwise get really long strings for [taskname#taskvaluename] which may be an issue when showing on some display as most line fields for display plugins have a limited string length.
Also it will probably mess up the layout on the "Devices" tab.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that wouldn't look good on the overview. I have adapted the strings based on the P082 plugin.

}
return F("");
}

#endif // USES_P076