Skip to content

Commit

Permalink
Added ZCD pulse analysis
Browse files Browse the repository at this point in the history
(cherry picked from commit eb42a05a5bea25dd041b103ef0ff72093b844b07)
  • Loading branch information
mathieucarbou committed Aug 1, 2024
1 parent b517bf8 commit 5da028a
Show file tree
Hide file tree
Showing 20 changed files with 141 additions and 77 deletions.
5 changes: 5 additions & 0 deletions include/YaSolR.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <MycilaMQTT.h>
#include <MycilaNTP.h>
#include <MycilaPID.h>
#include <MycilaPulseAnalyzer.h>
#include <MycilaPZEM004Tv3.h>
#include <MycilaRelay.h>
#include <MycilaRouter.h>
Expand Down Expand Up @@ -70,6 +71,7 @@ extern Mycila::JSY jsy;
extern Mycila::Logger logger;
extern Mycila::MQTT mqtt;
extern Mycila::PID pidController;
extern Mycila::PulseAnalyzer pulseAnalyzer;
extern Mycila::PZEM pzemO1;
extern Mycila::PZEM pzemO2;
extern Mycila::Relay bypassRelayO1;
Expand Down Expand Up @@ -106,13 +108,16 @@ extern Mycila::Task mqttPublishConfigTask;
extern Mycila::TaskManager pioTaskManager;
extern Mycila::Task calibrationTask;
extern Mycila::Task carouselTask;
extern Mycila::Task dimmer1Task;
extern Mycila::Task dimmer2Task;
extern Mycila::Task displayTask;
extern Mycila::Task ds18Task;
extern Mycila::Task lightsTask;
extern Mycila::Task pzemO1PairingTask;
extern Mycila::Task pzemO2PairingTask;
extern Mycila::Task relayTask;
extern Mycila::Task routerTask;
extern Mycila::Task zcdTask;

extern Mycila::TaskManager jsyTaskManager;
extern Mycila::Task jsyTask;
Expand Down
7 changes: 4 additions & 3 deletions include/YaSolRWebsite.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
#include <map>

#ifdef APP_MODEL_OSS
#define LINE_CHART BAR_CHART
#define AREA_CHART BAR_CHART
#define ENERGY_CARD GENERIC_CARD
#define LINE_CHART BAR_CHART
#define AREA_CHART BAR_CHART
#define ENERGY_CARD GENERIC_CARD
#endif

namespace YaSolR {
Expand Down Expand Up @@ -69,6 +69,7 @@ namespace YaSolR {

Statistic _udpMessageRateBuffer = Statistic(&dashboard, YASOLR_LBL_157);

Statistic _zcdPulseLength = Statistic(&dashboard, YASOLR_LBL_187);
Statistic _zcdPulsePeriod = Statistic(&dashboard, YASOLR_LBL_185);

Statistic _time = Statistic(&dashboard, YASOLR_LBL_034);
Expand Down
4 changes: 2 additions & 2 deletions include/i18n/en.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@
#define YASOLR_LBL_182 "Output 2 Temperature MQTT Topic"
#define YASOLR_LBL_183 "Output 1 Dimmer Min/Max Remapping"
#define YASOLR_LBL_184 "Output 2 Dimmer Min/Max Remapping"
#define YASOLR_LBL_185 "Zero-Cross Pulse Period"
#define YASOLR_LBL_185 "ZCD: Pulse Period"
#define YASOLR_LBL_186 "Resistance Value Detection"
#define YASOLR_LBL_187
#define YASOLR_LBL_187 "ZCD: Pulse Length"
#define YASOLR_LBL_188
#define YASOLR_LBL_189
#define YASOLR_LBL_190
Expand Down
4 changes: 2 additions & 2 deletions include/i18n/fr.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@
#define YASOLR_LBL_182 "Topic MQTT pour la température sortie 2"
#define YASOLR_LBL_183 "Redéfinition Min/Max Variateur Sortie 1"
#define YASOLR_LBL_184 "Redéfinition Min/Max Variateur Sortie 2"
#define YASOLR_LBL_185 "Période des Zero-Cross"
#define YASOLR_LBL_185 "ZCD: Période des pulses"
#define YASOLR_LBL_186 "Détection valeur résistances"
#define YASOLR_LBL_187
#define YASOLR_LBL_187 "ZCD: Longueur des pulses"
#define YASOLR_LBL_188
#define YASOLR_LBL_189
#define YASOLR_LBL_190
Expand Down
6 changes: 5 additions & 1 deletion lib/DimmableLight/src/thyristor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,12 @@ void Thyristor::begin() {

void Thyristor::end() {
detachInterrupt(digitalPinToInterrupt(syncPin));
queue.reset();
#ifdef NETWORK_FREQ_RUNTIME
setFrequency(0);
#endif
#ifdef MONITOR_FREQUENCY
queue.reset();
#endif
}

float Thyristor::getFrequency() {
Expand Down
2 changes: 1 addition & 1 deletion lib/DimmableLight/src/thyristor.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
#endif

// If enabled, you can monitor the actual frequency of the electrical network.
#define MONITOR_FREQUENCY
// #define MONITOR_FREQUENCY

#define FILTER_INT_PERIOD

Expand Down
16 changes: 10 additions & 6 deletions lib/MycilaDimmer/MycilaDimmer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ extern Mycila::Logger logger;

static const uint16_t TABLE_PHASE_DELAY[TABLE_PHASE_LEN] PROGMEM{0xefea, 0xdfd4, 0xd735, 0xd10d, 0xcc12, 0xc7cc, 0xc403, 0xc094, 0xbd6a, 0xba78, 0xb7b2, 0xb512, 0xb291, 0xb02b, 0xaddc, 0xaba2, 0xa97a, 0xa762, 0xa557, 0xa35a, 0xa167, 0x9f7f, 0x9da0, 0x9bc9, 0x99fa, 0x9831, 0x966e, 0x94b1, 0x92f9, 0x9145, 0x8f95, 0x8de8, 0x8c3e, 0x8a97, 0x88f2, 0x8750, 0x85ae, 0x840e, 0x826e, 0x80cf, 0x7f31, 0x7d92, 0x7bf2, 0x7a52, 0x78b0, 0x770e, 0x7569, 0x73c2, 0x7218, 0x706b, 0x6ebb, 0x6d07, 0x6b4f, 0x6992, 0x67cf, 0x6606, 0x6437, 0x6260, 0x6081, 0x5e99, 0x5ca6, 0x5aa9, 0x589e, 0x5686, 0x545e, 0x5224, 0x4fd5, 0x4d6f, 0x4aee, 0x484e, 0x4588, 0x4296, 0x3f6c, 0x3bfd, 0x3834, 0x33ee, 0x2ef3, 0x28cb, 0x202c, 0x1016};

void Mycila::Dimmer::begin(const int8_t pin) {
void Mycila::Dimmer::begin(const int8_t pin, const uint8_t nominalFrequency) {
if (_dimmer)
return;

if (!nominalFrequency) {
LOGE(TAG, "Disable Dimmer on pin %" PRId8 ": Invalid nominal frequency: %d", pin, nominalFrequency);
return;
}

if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) {
_pin = (gpio_num_t)pin;
} else {
Expand All @@ -52,6 +57,7 @@ void Mycila::Dimmer::begin(const int8_t pin) {

LOGI(TAG, "Enable Dimmer on pin %" PRId8, _pin);

_semiPeriod = 1000000 / 2 / nominalFrequency;
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW);

Expand All @@ -75,14 +81,13 @@ void Mycila::Dimmer::setDutyCycle(float newDutyCycle) {
if (!_dimmer)
return;

const uint16_t semiPeriod = _zcd->getNominalSemiPeriod();
if (semiPeriod == 0)
if (_semiPeriod == 0)
return;

// ensure newDuty is within bounds
_dutyCycle = constrain(newDutyCycle, 0, _dutyCycleLimit);
if (_dutyCycle == 0) {
_dimmer->setDelay(semiPeriod);
_dimmer->setDelay(_semiPeriod);

} else if (_dutyCycle >= 1) {
_dimmer->setDelay(0);
Expand All @@ -99,8 +104,7 @@ void Mycila::Dimmer::setDutyCycle(float newDutyCycle) {
const uint32_t a = TABLE_PHASE_DELAY[index];
const uint32_t b = TABLE_PHASE_DELAY[index + 1];
const uint32_t delay = a - (((a - b) * (slot & 0xffff)) >> 16);
const uint32_t period = semiPeriod;
_dimmer->setDelay((delay * period) >> 16);
_dimmer->setDelay((delay * _semiPeriod) >> 16);
}
}

Expand Down
14 changes: 6 additions & 8 deletions lib/MycilaDimmer/MycilaDimmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/
#pragma once

#include <MycilaZCD.h>
#include <WString.h>
#include <esp32-hal-gpio.h>
#include <thyristor.h>
Expand All @@ -18,10 +17,9 @@
namespace Mycila {
class Dimmer {
public:
explicit Dimmer(ZCD& zcd) : _zcd(&zcd) {}
~Dimmer() { end(); }

void begin(const int8_t pin);
void begin(const int8_t pin, const uint8_t nominalFrequency);
void end();

gpio_num_t getPin() const { return _pin; }
Expand All @@ -30,15 +28,16 @@ namespace Mycila {
#ifdef MYCILA_JSON_SUPPORT
void toJson(const JsonObject& root) const {
const float angle = getPhaseAngle();
root["enabled"] = _dimmer != nullptr;
root["state"] = isOn() ? "on" : "off";
root["angle_d"] = angle * RAD_TO_DEG;
root["angle"] = angle;
root["delay"] = getFiringDelay();
root["duty_cycle"] = _dutyCycle;
root["duty_cycle_limit"] = _dutyCycleLimit;
root["duty_cycle_min"] = _dutyCycleMin;
root["duty_cycle_max"] = _dutyCycleMax;
root["enabled"] = _dimmer != nullptr;
root["state"] = isOn() ? "on" : "off";
root["semi_period"] = _semiPeriod;
}
#endif

Expand Down Expand Up @@ -78,14 +77,13 @@ namespace Mycila {
// At 100% power, the phase angle is equal to 0
float getPhaseAngle() const {
// angle_rad = PI * delay_us / period_us
uint16_t semiPeriod = _zcd->getNominalSemiPeriod();
return semiPeriod == 0 ? PI : PI * getFiringDelay() / semiPeriod;
return _semiPeriod == 0 ? PI : PI * getFiringDelay() / _semiPeriod;
}

private:
ZCD* _zcd;
gpio_num_t _pin = GPIO_NUM_NC;
Thyristor* _dimmer = nullptr;
uint16_t _semiPeriod = 0;
float _dutyCycleMin = 0;
float _dutyCycleMax = 1;
float _dutyCycleLimit = 1;
Expand Down
15 changes: 8 additions & 7 deletions lib/MycilaDimmer/MycilaZCD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ extern Mycila::Logger logger;

// Mycila::ZCD

void Mycila::ZCD::begin(const int8_t pin, const uint8_t frequency) {
void Mycila::ZCD::begin(const int8_t pin, const uint8_t nominalFrequency) {
if (_enabled)
return;

if (!nominalFrequency) {
LOGE(TAG, "Disable Dimmer on pin %" PRId8 ": Invalid nominal frequency: %d", pin, nominalFrequency);
return;
}

if (GPIO_IS_VALID_GPIO(pin)) {
_pin = (gpio_num_t)pin;
} else {
Expand All @@ -43,11 +48,11 @@ void Mycila::ZCD::begin(const int8_t pin, const uint8_t frequency) {
return;
}

LOGI(TAG, "Enable Zero-Cross Detection on pin %" PRId8 " with frequency %" PRIu8 " Hz", _pin, frequency);
LOGI(TAG, "Enable Zero-Cross Detection on pin %" PRId8 " with frequency %" PRIu8 " Hz", _pin, nominalFrequency);

// https://github.com/fabianoriccardi/dimmable-light/wiki/Notes-about-specific-architectures#interrupt-issue
Thyristor::semiPeriodShrinkMargin = 400;
Thyristor::setFrequency(frequency);
Thyristor::setFrequency(nominalFrequency);
Thyristor::setSyncPin(_pin);
Thyristor::setSyncDir(RISING);
Thyristor::begin();
Expand All @@ -59,11 +64,7 @@ void Mycila::ZCD::end() {
if (_enabled) {
LOGI(TAG, "Disable Zero-Cross Detection on pin %" PRId8, _pin);
_enabled = false;
Thyristor::setFrequency(0);
Thyristor::end();
_pin = GPIO_NUM_NC;
}
}

uint16_t Mycila::ZCD::getNominalSemiPeriod() const { return Thyristor::getNominalSemiPeriod(); }
uint32_t Mycila::ZCD::getPulsePeriod() const { return _enabled ? Thyristor::getPulsePeriod() : 0; }
10 changes: 0 additions & 10 deletions lib/MycilaDimmer/MycilaZCD.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,9 @@ namespace Mycila {
gpio_num_t getPin() const { return _pin; }
bool isEnabled() const { return _enabled; }

// in microseconds
// 50Hz => 10000
// 60Hz => 8333
uint16_t getNominalSemiPeriod() const;

// Zero-Cross pulse information
uint32_t getPulsePeriod() const;

#ifdef MYCILA_JSON_SUPPORT
void toJson(const JsonObject& root) const {
root["enabled"] = isEnabled();
root["nominal_semiperiod_us"] = getNominalSemiPeriod();
root["pulse_period_us"] = getPulsePeriod();
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ lib_deps =
mathieucarbou/MycilaLogger @ 3.1.2
mathieucarbou/MycilaMQTT @ 4.1.1
mathieucarbou/MycilaNTP @ 4.0.0
mathieucarbou/MycilaPulseAnalyzer @ 1.0.0
mathieucarbou/MycilaPulseAnalyzer @ 1.0.1
mathieucarbou/MycilaPZEM004Tv3 @ 4.0.6
mathieucarbou/MycilaRelay @ 4.0.1
mathieucarbou/MycilaSystem @ 2.0.6
Expand Down
13 changes: 7 additions & 6 deletions src/Website.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,8 @@ void YaSolR::WebsiteClass::updateCards() {
_relay1SwitchCount.set(String(relay1.getSwitchCount()).c_str());
_relay2SwitchCount.set(String(relay2.getSwitchCount()).c_str());
_udpMessageRateBuffer.set((String(udpMessageRateBuffer.rate()) + " msg/s").c_str());
_zcdPulsePeriod.set((String(zcd.getPulsePeriod()) + " us").c_str());
_zcdPulsePeriod.set((String(pulseAnalyzer.getPeriod()) + " us").c_str());
_zcdPulseLength.set((String(pulseAnalyzer.getLength()) + " us").c_str());
_time.set(Mycila::Time::getLocalStr().c_str());
_uptime.set(Mycila::Time::toDHHMMSS(Mycila::System.getUptime()).c_str());
#ifdef APP_MODEL_TRIAL
Expand Down Expand Up @@ -821,14 +822,14 @@ void YaSolR::WebsiteClass::updateCards() {
// Hardware (status)
_status(_jsy, KEY_ENABLE_JSY, jsy.isEnabled(), jsy.isConnected(), YASOLR_LBL_110);
_status(_mqtt, KEY_ENABLE_MQTT, mqtt.isEnabled(), mqtt.isConnected(), mqtt.getLastError() ? mqtt.getLastError() : YASOLR_LBL_113);
_status(_output1Dimmer, KEY_ENABLE_OUTPUT1_DIMMER, dimmerO1.isEnabled(), zcd.getNominalSemiPeriod() > 0, YASOLR_LBL_179);
_status(_output1Dimmer, KEY_ENABLE_OUTPUT1_DIMMER, dimmerO1.isEnabled(), zcd.isEnabled(), YASOLR_LBL_179);
_status(_output1DS18, KEY_ENABLE_OUTPUT1_DS18, ds18O1.isEnabled(), ds18O1.getLastTime() > 0, YASOLR_LBL_114);
_status(_output1PZEM, KEY_ENABLE_OUTPUT1_PZEM, pzemO1.isEnabled(), pzemO1.isConnected() && pzemO1.readAddress() == YASOLR_PZEM_ADDRESS_OUTPUT1, pzemO1.isConnected() ? YASOLR_LBL_110 : YASOLR_LBL_180);
_status(_output2Dimmer, KEY_ENABLE_OUTPUT2_DIMMER, dimmerO2.isEnabled(), zcd.getNominalSemiPeriod() > 0, YASOLR_LBL_179);
_status(_output1PZEM, KEY_ENABLE_OUTPUT1_PZEM, pzemO1.isEnabled(), pzemO1.isConnected() && pzemO1.readAddress() == YASOLR_PZEM_ADDRESS_OUTPUT1, pzemO1.isConnected() ? YASOLR_LBL_180 : YASOLR_LBL_110);
_status(_output2Dimmer, KEY_ENABLE_OUTPUT2_DIMMER, dimmerO2.isEnabled(), zcd.isEnabled(), YASOLR_LBL_179);
_status(_output2DS18, KEY_ENABLE_OUTPUT2_DS18, ds18O2.isEnabled(), ds18O2.getLastTime() > 0, YASOLR_LBL_114);
_status(_output2PZEM, KEY_ENABLE_OUTPUT2_PZEM, pzemO2.isEnabled(), pzemO2.isConnected() && pzemO2.readAddress() == YASOLR_PZEM_ADDRESS_OUTPUT2, pzemO2.isConnected() ? YASOLR_LBL_110 : YASOLR_LBL_180);
_status(_output2PZEM, KEY_ENABLE_OUTPUT2_PZEM, pzemO2.isEnabled(), pzemO2.isConnected() && pzemO2.readAddress() == YASOLR_PZEM_ADDRESS_OUTPUT2, pzemO2.isConnected() ? YASOLR_LBL_180 : YASOLR_LBL_110);
_status(_routerDS18, KEY_ENABLE_DS18_SYSTEM, ds18Sys.isEnabled(), ds18Sys.getLastTime() > 0, YASOLR_LBL_114);
_status(_zcd, KEY_ENABLE_ZCD, zcd.isEnabled(), zcd.getPulsePeriod() > 0, YASOLR_LBL_110);
_status(_zcd, KEY_ENABLE_ZCD, true, zcd.isEnabled(), YASOLR_LBL_110);

// Hardware (config)
_output1PZEMSync.update(!pzemO1PairingTask.isPaused());
Expand Down
24 changes: 6 additions & 18 deletions src/init/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Mycila::Task initConfigTask("Init Config", [](void* params) {
relayTask.setInterval(7 * Mycila::TaskDuration::SECONDS);
routerTask.setEnabledWhen([]() { return !router.isCalibrationRunning(); });
routerTask.setInterval(500 * Mycila::TaskDuration::MILLISECONDS);
zcdTask.setInterval(1500 * Mycila::TaskDuration::MILLISECONDS);

// mqttTaskManager
mqttPublishConfigTask.setEnabledWhen([]() { return mqtt.isConnected(); });
Expand Down Expand Up @@ -124,24 +125,6 @@ Mycila::Task initConfigTask("Init Config", [](void* params) {
if (config.getBool(KEY_ENABLE_OUTPUT2_DS18))
ds18O2.begin(config.get(KEY_PIN_OUTPUT2_DS18).toInt());

// Electricity: ZCD
if (config.getBool(KEY_ENABLE_ZCD))
zcd.begin(config.get(KEY_PIN_ZCD).toInt(), config.get(KEY_GRID_FREQUENCY).toInt() == 60 ? 60 : 50);

// Electricity: Dimmer 1
dimmerO1.setDutyCycleMin(config.get(KEY_OUTPUT1_DIMMER_MIN).toFloat() / 100);
dimmerO1.setDutyCycleMax(config.get(KEY_OUTPUT1_DIMMER_MAX).toFloat() / 100);
dimmerO1.setDutyCycleLimit(config.get(KEY_OUTPUT1_DIMMER_LIMIT).toFloat() / 100);
if (config.getBool(KEY_ENABLE_OUTPUT1_DIMMER))
dimmerO1.begin(config.get(KEY_PIN_OUTPUT1_DIMMER).toInt());

// Electricity: Dimmer 2
dimmerO2.setDutyCycleMin(config.get(KEY_OUTPUT2_DIMMER_MIN).toFloat() / 100);
dimmerO2.setDutyCycleMax(config.get(KEY_OUTPUT2_DIMMER_MAX).toFloat() / 100);
dimmerO2.setDutyCycleLimit(config.get(KEY_OUTPUT2_DIMMER_LIMIT).toFloat() / 100);
if (config.getBool(KEY_ENABLE_OUTPUT2_DIMMER))
dimmerO2.begin(config.get(KEY_PIN_OUTPUT2_DIMMER).toInt());

// Electricity: Relays
if (config.getBool(KEY_ENABLE_OUTPUT1_RELAY))
bypassRelayO1.begin(config.get(KEY_PIN_OUTPUT1_RELAY).toInt(), config.get(KEY_OUTPUT1_RELAY_TYPE) == YASOLR_RELAY_TYPE_NC ? Mycila::RelayType::NC : Mycila::RelayType::NO);
Expand Down Expand Up @@ -209,4 +192,9 @@ Mycila::Task initConfigTask("Init Config", [](void* params) {
ESPConnect.setAutoRestart(true);
ESPConnect.setBlocking(false);
ESPConnect.begin(webServer, Mycila::AppInfo.defaultHostname, Mycila::AppInfo.defaultSSID, config.get(KEY_ADMIN_PASSWORD), {config.get(KEY_WIFI_SSID), config.get(KEY_WIFI_PASSWORD), config.getBool(KEY_ENABLE_AP_MODE)});

// ZCD + Dimmers
zcdTask.forceRun();
dimmer1Task.forceRun();
dimmer2Task.forceRun();
});
3 changes: 3 additions & 0 deletions src/init/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ Mycila::Task initCoreTask("Init Core", [](void* params) {
// pioTaskManager
calibrationTask.setManager(pioTaskManager);
carouselTask.setManager(pioTaskManager);
dimmer1Task.setManager(pioTaskManager);
dimmer2Task.setManager(pioTaskManager);
displayTask.setManager(pioTaskManager);
ds18Task.setManager(pioTaskManager);
lightsTask.setManager(pioTaskManager);
pzemO1PairingTask.setManager(pioTaskManager);
pzemO2PairingTask.setManager(pioTaskManager);
relayTask.setManager(pioTaskManager);
routerTask.setManager(pioTaskManager);
zcdTask.setManager(pioTaskManager);

// jsyTaskManager
jsyTask.setManager(jsyTaskManager);
Expand Down
2 changes: 2 additions & 0 deletions src/init/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Mycila::Task initLoggingTask("Init Logging", [](void* params) {

// Log execution time for some "ONCE" tasks
ds18Task.setCallback(debug ? LOG_EXEC_TIME : nullptr);
dimmer1Task.setCallback(debug ? LOG_EXEC_TIME : nullptr);
dimmer2Task.setCallback(debug ? LOG_EXEC_TIME : nullptr);
haDiscoveryTask.setCallback(debug ? LOG_EXEC_TIME : nullptr);
mqttConfigTask.setCallback(debug ? LOG_EXEC_TIME : nullptr);
mqttPublishConfigTask.setCallback(debug ? LOG_EXEC_TIME : nullptr);
Expand Down
Loading

0 comments on commit 5da028a

Please sign in to comment.