From 1d269c165496e5974440dd9574341e00e0298296 Mon Sep 17 00:00:00 2001 From: Thales Maia Date: Sat, 8 Nov 2025 09:33:38 -0300 Subject: [PATCH 1/3] Triac plugin - Detect zero cross and trigger a triac. --- src/_P184_Triac.ino | 380 +++++++++++++++++++++++ src/src/CustomBuild/define_plugin_sets.h | 1 + 2 files changed, 381 insertions(+) create mode 100644 src/_P184_Triac.ino diff --git a/src/_P184_Triac.ino b/src/_P184_Triac.ino new file mode 100644 index 0000000000..a138c1f397 --- /dev/null +++ b/src/_P184_Triac.ino @@ -0,0 +1,380 @@ +// This plugin detect zerocross and trigger a triac. Used for power electronics. +// Comon uses are AC light dimming or AC Fan speed control. + +#include "_Plugin_Helper.h" + +#ifdef USES_P184 +#define PLUGIN_184 +#define PLUGIN_ID_184 184 // plugin id +#define PLUGIN_NAME_184 "Triac" // "Plugin Name" is what will be dislpayed in the selection list +#define PLUGIN_VALUENAME1_184 "Trigger" // variable output of the plugin. The label is in quotation marks +#define PLUGIN_VALUENAME2_184 "Power" // multiple outputs are supported +// #define PLUGIN_184_DEBUG false // set to true for extra log info in the debug +#define P184_OUTPUT_TYPE_INDEX 2 + + +#define P184_60HZ_HALF_WAVE_TIME_US_ONE_PERCENT ((uint32_t)83) // 1/60/2*1% +#define P184_50HZ_HALF_WAVE_TIME_US_ONE_PERCENT ((uint32_t)100) // 1/50/2*1% + +#define P184_TRIGGER_CONFIG() PCONFIG(0) +#define P184_TRIGGER_EDGE_CONFIG() PCONFIG(1) +#define P184_DEAD_ZONE_CONFIG() PCONFIG(2) +#define P184_FREQ_CONFIG() PCONFIG(3) + +#define P184_ZERO_CROSS_PIN() PIN(0) +#define P184_TRIGGER_PIN() PIN(1) + +// int8_t P184_TRIGGER_PIN(); +// uint8_t p184_trigger; + +struct P184_data_struct { + int8_t zero_crossing_pin = -1; + int8_t trigger_pin = -1; + uint8_t trigger_value = 0; + uint8_t power_value = 0; + uint8_t dead_zone = 0; + uint32_t freq_timing_val = P184_60HZ_HALF_WAVE_TIME_US_ONE_PERCENT; + uint64_t time_us = 0; + hw_timer_t *p184_timer = NULL; + // hw_timer_t *debounce = NULL; +}; + +// Lookup table to map Power % (index) to Trigger % (value) +// Generated from the formula: power_ratio = 1 - (t/pi) + sin(2t)/(2pi) +// This avoids floating point math in real-time and allows setting power directly. +const uint8_t power_to_trigger_lut[101] PROGMEM = { + // Power % (index) -> Trigger % (value) + 100, 87, 82, 78, 75, 73, 71, 69, 67, 66, 64, 63, 61, 60, 59, 58, 57, 56, 55, 54, + 53, 52, 51, 50, 49, 48, 48, 47, 46, 45, 44, 44, 43, 42, 42, 41, 40, 40, 39, 38, + 38, 37, 36, 36, 35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 26, 26, 25, + 24, 24, 23, 22, 22, 21, 20, 20, 19, 18, 18, 17, 16, 16, 15, 14, 13, 13, 12, 11, + 11, 10, 9, 8, 8, 7, 6, 5, 5, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, + 0 // Power 100% -> Trigger 0% +}; + +P184_data_struct P184_data; + +void IRAM_ATTR P184_zero_crossing() { + // This is only necessary for debounce + // detachInterrupt(P184_data.zero_crossing_pin); + if (P184_data.trigger_pin != -1 && P184_data.p184_timer != NULL) { + if (P184_data.trigger_value == 0) { + digitalWrite(P184_data.trigger_pin, HIGH); + } else { + digitalWrite(P184_data.trigger_pin, LOW); + timerRestart(P184_data.p184_timer); + } + } +} + +void IRAM_ATTR P184_timer_handler() { + if (P184_data.trigger_pin != -1) { + if (P184_data.trigger_value != 100) { + digitalWrite(P184_data.trigger_pin, HIGH); + } + } +} + +// A plugin has to implement the following function +boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) +{ + // function: reason the plugin was called + // event: ??add description here?? + // string: ??add description here?? + + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + // This case defines the device characteristics, edit appropriately + // Attention: dev Values set to 0 or false should be removed to save a few bytes (unneeded assignments) + + auto& dev = Device[++deviceCount]; + dev.Number = PLUGIN_ID_184; // Plugin ID number. (PLUGIN_ID_184) + // dev.Type = DEVICE_TYPE_DUMMY; // How the device is connected. e.g. DEVICE_TYPE_SINGLE => connected through 1 datapin + dev.VType = Sensor_VType::SENSOR_TYPE_DIMMER; // Type of value the plugin will return. e.g. SENSOR_TYPE_STRING + // dev.Ports = 0; // Port to use when device has multiple I/O pins (N.B. not used much) + dev.ValueCount = 2; // The number of output values of a plugin. The value should match the number of keys PLUGIN_VALUENAME1_184 + dev.OutputDataType = Output_Data_type_t::Simple; // Subset of selectable output data types (Default = no selection) + dev.PullUpOption = false; // Allow to set internal pull-up resistors. + dev.InverseLogicOption = false; // Allow to invert the boolean state (e.g. a switch) + dev.FormulaOption = false; // Allow to enter a formula to convert values during read. (not possible with Custom enabled) + dev.Custom = false; + dev.SendDataOption = true; // Allow to send data to a controller. + dev.GlobalSyncOption = true; // No longer used. Was used for ESPeasy values sync between nodes + dev.TimerOption = true; // Allow to set the "Interval" timer for the plugin. + dev.TimerOptional = false; // When taskdevice timer is not set and not optional, use default "Interval" delay (Settings.Delay) + dev.DecimalsOnly = true; // Allow to set the number of decimals (otherwise treated a 0 decimals) + dev.CustomVTypeVar = false; // Enable to allow the user to configure the Sensor_VType per Value that's available for the plugin + break; + } + + case PLUGIN_GET_DEVICENAME: + { + // return the device name + string = F(PLUGIN_NAME_184); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_184)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_184)); + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire + // string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + // This is only called when dev.OutputDataType is not Output_Data_type_t::Default + // The position in the config parameters used in this example is PCONFIG(P184_OUTPUT_TYPE_INDEX) + // Must match the one used in case PLUGIN_GET_DEVICEVTYPE (best to use a define for it) + // see P026_Sysinfo.ino for more examples. + event->Par1 = 2; //getValueCountFromSensorType(static_cast(PCONFIG(P184_OUTPUT_TYPE_INDEX))); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + // This is only called when dev.OutputDataType is not Output_Data_type_t::Default + // The position in the config parameters used in this example is PCONFIG(P184_OUTPUT_TYPE_INDEX) + // Must match the one used in case PLUGIN_GET_DEVICEVALUECOUNT (best to use a define for it) + // IDX is used here to mark the PCONFIG position used to store the Device VType. + // see _P026_Sysinfo.ino for more examples. + // event->idx = P184_OUTPUT_TYPE_INDEX; + event->sensorType = Sensor_VType::SENSOR_TYPE_DIMMER; //static_cast(PCONFIG(event->idx)); + success = true; + break; + } + + case PLUGIN_SET_DEFAULTS: + { + // Set a default config here, which will be called when a plugin is assigned to a task. + P184_TRIGGER_CONFIG() = 0; + P184_DEAD_ZONE_CONFIG() = 0; + P184_ZERO_CROSS_PIN() = -1; + P184_TRIGGER_PIN() = -1; + PCONFIG(P184_OUTPUT_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_DIMMER); + success = true; + break; + } + + + case PLUGIN_WEBFORM_LOAD: + { + addRowLabel(F("Trigger Pin")); + addPinSelect(PinSelectPurpose::Generic_output, "trigger_pin", P184_TRIGGER_PIN()); + + addRowLabel(F("Zero Cross Pin")); + addPinSelect(PinSelectPurpose::Generic_input, "zero_crossing_pin", P184_ZERO_CROSS_PIN()); + { + const __FlashStringHelper* optionsEdge[] = { F("RISING"), F("FALLING")}; + const int optionsValsEdge[] = { RISING, FALLING }; + constexpr int optionsCountEdge = NR_ELEMENTS(optionsValsEdge); + const FormSelectorOptions trigEdgeSelector(optionsCountEdge, optionsEdge, optionsValsEdge); + trigEdgeSelector.addFormSelector(F("Interrupt mode"), F("trigger_edge"), P184_TRIGGER_EDGE_CONFIG()); + } + + { + const __FlashStringHelper* optionsFreq[] = { F("60Hz"), F("50Hz")}; + const int optionsValsFreq[] = { P184_60HZ_HALF_WAVE_TIME_US_ONE_PERCENT, P184_50HZ_HALF_WAVE_TIME_US_ONE_PERCENT }; + constexpr int optionsCountFreq = NR_ELEMENTS(optionsValsFreq); + const FormSelectorOptions freqSelector(optionsCountFreq, optionsFreq, optionsValsFreq); + freqSelector.addFormSelector(F("Grid frequency"), F("freq"), P184_FREQ_CONFIG()); + } + + + addFormNumericBox(F("Trigger"), F("trigger"), P184_TRIGGER_CONFIG(), 0, 100); + addUnit(F("%")); + + addFormNumericBox(F("Trigger Deadzone"), F("trigger_deadzone"), P184_DEAD_ZONE_CONFIG(), 0, 20); + addUnit(F("%")); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + + if (P184_data.zero_crossing_pin != -1) + { + detachInterrupt(P184_data.zero_crossing_pin); + } + + P184_TRIGGER_CONFIG() = getFormItemInt(F("trigger")); + P184_DEAD_ZONE_CONFIG() = getFormItemInt(F("trigger_deadzone")); + P184_ZERO_CROSS_PIN() = getFormItemInt(F("zero_crossing_pin")); + P184_TRIGGER_PIN() = getFormItemInt(F("trigger_pin")); + P184_TRIGGER_EDGE_CONFIG() = getFormItemInt(F("trigger_edge")); + P184_FREQ_CONFIG() = getFormItemInt(F("freq")); + + P184_data.trigger_value = P184_TRIGGER_CONFIG(); + // Recalculate power value based on the new trigger value from the form + for (int i = 0; i <= 100; ++i) { + if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { + P184_data.power_value = i; + break; // Found the highest power for this trigger level or lower + } + } + // after the form has been saved successfuly, set success and break + success = true; + break; + } + case PLUGIN_INIT: + { + // this case defines code to be executed when the plugin is initialised + P184_data.trigger_pin = P184_TRIGGER_PIN(); + P184_data.trigger_value = P184_TRIGGER_CONFIG(); + P184_data.dead_zone = P184_DEAD_ZONE_CONFIG(); + P184_data.freq_timing_val = P184_FREQ_CONFIG(); + P184_data.zero_crossing_pin = P184_ZERO_CROSS_PIN(); + + P184_data.trigger_value = P184_TRIGGER_CONFIG(); + // Calculate initial power value based on the loaded trigger value + for (int i = 0; i <= 100; ++i) { + if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { + P184_data.power_value = i; + break; // Found the highest power for this trigger level or lower + } + } + + + if (P184_data.zero_crossing_pin != -1) + { + pinMode(P184_data.zero_crossing_pin, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(P184_data.zero_crossing_pin), P184_zero_crossing, P184_TRIGGER_EDGE_CONFIG()); + } + if (P184_data.trigger_pin != -1) + { + pinMode(P184_data.trigger_pin, OUTPUT); + digitalWrite(P184_data.trigger_pin, LOW); + P184_data.p184_timer = timerBegin(1000000); + P184_data.time_us = uint64_t(P184_data.trigger_value) * P184_data.freq_timing_val; + timerAttachInterrupt(P184_data.p184_timer, &P184_timer_handler); + timerAlarm(P184_data.p184_timer, P184_data.time_us, true, 0); + } + + success = true; + break; + } + + case PLUGIN_READ: + { + // code to be executed to read data + UserVar.setFloat(event->TaskIndex, 0, P184_data.trigger_value); + UserVar.setFloat(event->TaskIndex, 1, P184_data.power_value); + + success = true; + break; + } + + case PLUGIN_WRITE: + { + + // parse string to extract the command + String tmpString = parseString(string, 1); // already converted to lowercase + + if (equals(tmpString, F("triac"))) { + String subcmd = parseString(string, 2); + String valueStr = parseString(string, 3); + long value = valueStr.toInt(); + + if (value >= 0 && value <= 100) { + if (equals(subcmd, F("power"))) { + // User wants to set POWER: triac,power, + P184_data.power_value = value; + // Find the corresponding trigger value from LUT + uint8_t new_trigger = pgm_read_byte(&power_to_trigger_lut[P184_data.power_value]); + P184_data.trigger_value = new_trigger; + success = true; + } else if (equals(subcmd, F("trigger"))) { + // User wants to set TRIGGER directly: triac,trigger, + uint8_t new_trigger = value; + + // Apply dead zone logic + if (new_trigger > (100 - P184_data.dead_zone)) { + new_trigger = 100; + } else if (new_trigger < P184_data.dead_zone) { + new_trigger = 0; + } + P184_data.trigger_value = new_trigger; + + // Let's find the closest power value for the new trigger. + // This is a slow lookup, but only happens on command. + // The LUT maps power (index) to trigger (value). We need to find the index (power) + // for a given trigger value. + for (int i = 0; i <= 100; ++i) { + // Find the first power level (i) where the corresponding trigger + // is less than or equal to the one we just set. + if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { + P184_data.power_value = i; + break; // Found the highest power for this trigger level or lower + } + } + success = true; + } + + if (success) { + P184_TRIGGER_CONFIG() = P184_data.trigger_value; // Save state + P184_data.time_us = uint64_t(P184_data.trigger_value) * P184_data.freq_timing_val; + timerAlarm(P184_data.p184_timer, P184_data.time_us, true, 0); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat(F("P184 CMD : Trigger %d%% . Power %d%%"), P184_data.trigger_value, P184_data.power_value); + addLogMove(LOG_LEVEL_INFO, log); + } + } + } + } + break; + } + + case PLUGIN_EXIT: + { + // perform cleanup tasks here. For example, free memory, shut down/clear a display + if (P184_data.zero_crossing_pin != -1) + { + detachInterrupt(digitalPinToInterrupt(P184_data.zero_crossing_pin)); + } + if (P184_data.trigger_pin != -1) + { + digitalWrite(P184_data.trigger_pin, LOW); + if (P184_data.p184_timer != NULL) { + timerEnd(P184_data.p184_timer); + P184_data.p184_timer = NULL; + } + } + success = true; + break; + } + + // case PLUGIN_ONCE_A_SECOND: + // { + // // code to be executed once a second. Tasks which do not require fast response can be added here + + // success = true; + // } + + // case PLUGIN_TEN_PER_SECOND: + // { + // // code to be executed 10 times per second. Tasks which require fast response can be added here + // // be careful on what is added here. Heavy processing will result in slowing the module down! + + // success = true; + // } + } // switch + return success; +} // function + + +#endif \ No newline at end of file diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 64eb91524e..4b0abf78bc 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1495,6 +1495,7 @@ To create/register a plugin, you have to : #define USES_C013 // ESPEasy P2P network #endif +#define USES_P184 #ifdef NOTIFIER_SET_STABLE #define USES_N001 // Email From 0af3bac956fc4454bbac2381f7cec6b985c7ec49 Mon Sep 17 00:00:00 2001 From: Thales Maia Date: Tue, 11 Nov 2025 19:44:08 -0300 Subject: [PATCH 2/3] Name change Remove global struct Remove comments and unnecessary code Add fixed lut size REG write for fast pin write Make interrupt static --- src/_P184_Triac.ino | 260 +++++++++-------------- src/src/CustomBuild/define_plugin_sets.h | 13 +- 2 files changed, 105 insertions(+), 168 deletions(-) diff --git a/src/_P184_Triac.ino b/src/_P184_Triac.ino index a138c1f397..7e769dbe4c 100644 --- a/src/_P184_Triac.ino +++ b/src/_P184_Triac.ino @@ -6,10 +6,9 @@ #ifdef USES_P184 #define PLUGIN_184 #define PLUGIN_ID_184 184 // plugin id -#define PLUGIN_NAME_184 "Triac" // "Plugin Name" is what will be dislpayed in the selection list +#define PLUGIN_NAME_184 "Output - Triac" // "Plugin Name" is what will be dislpayed in the selection list #define PLUGIN_VALUENAME1_184 "Trigger" // variable output of the plugin. The label is in quotation marks #define PLUGIN_VALUENAME2_184 "Power" // multiple outputs are supported -// #define PLUGIN_184_DEBUG false // set to true for extra log info in the debug #define P184_OUTPUT_TYPE_INDEX 2 @@ -24,19 +23,32 @@ #define P184_ZERO_CROSS_PIN() PIN(0) #define P184_TRIGGER_PIN() PIN(1) -// int8_t P184_TRIGGER_PIN(); -// uint8_t p184_trigger; - -struct P184_data_struct { - int8_t zero_crossing_pin = -1; - int8_t trigger_pin = -1; +struct P184_data_struct : public PluginTaskData_base { + gpio_num_t zero_crossing_pin = GPIO_NUM_NC; + gpio_num_t trigger_pin = GPIO_NUM_NC; uint8_t trigger_value = 0; uint8_t power_value = 0; uint8_t dead_zone = 0; uint32_t freq_timing_val = P184_60HZ_HALF_WAVE_TIME_US_ONE_PERCENT; - uint64_t time_us = 0; - hw_timer_t *p184_timer = NULL; - // hw_timer_t *debounce = NULL; + hw_timer_t *timer = NULL; + + // Funções de interrupção como membros estáticos da struct + static void IRAM_ATTR zero_crossing_handler(void *arg) { + P184_data_struct* p184_data = static_cast(arg); + if (p184_data->trigger_value == 0) { + REG_WRITE(GPIO_OUT_W1TS_REG, (1 << p184_data->trigger_pin)); // Fast gpio_set_level(HIGH) + } else { + REG_WRITE(GPIO_OUT_W1TC_REG, (1 << p184_data->trigger_pin)); // Fast gpio_set_level(LOW) + timerRestart(p184_data->timer); + } + } + + static void IRAM_ATTR timer_handler(void *arg) { + P184_data_struct* p184_data = static_cast(arg); + if (p184_data->trigger_value != 100) { + REG_WRITE(GPIO_OUT_W1TS_REG, (1 << p184_data->trigger_pin)); // Fast gpio_set_level(HIGH) + } + } }; // Lookup table to map Power % (index) to Trigger % (value) @@ -51,36 +63,11 @@ const uint8_t power_to_trigger_lut[101] PROGMEM = { 11, 10, 9, 8, 8, 7, 6, 5, 5, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0 // Power 100% -> Trigger 0% }; - -P184_data_struct P184_data; - -void IRAM_ATTR P184_zero_crossing() { - // This is only necessary for debounce - // detachInterrupt(P184_data.zero_crossing_pin); - if (P184_data.trigger_pin != -1 && P184_data.p184_timer != NULL) { - if (P184_data.trigger_value == 0) { - digitalWrite(P184_data.trigger_pin, HIGH); - } else { - digitalWrite(P184_data.trigger_pin, LOW); - timerRestart(P184_data.p184_timer); - } - } -} - -void IRAM_ATTR P184_timer_handler() { - if (P184_data.trigger_pin != -1) { - if (P184_data.trigger_value != 100) { - digitalWrite(P184_data.trigger_pin, HIGH); - } - } -} +constexpr uint8_t power_to_trigger_lut_size = NR_ELEMENTS(power_to_trigger_lut); // A plugin has to implement the following function boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) { - // function: reason the plugin was called - // event: ??add description here?? - // string: ??add description here?? boolean success = false; @@ -93,27 +80,18 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) auto& dev = Device[++deviceCount]; dev.Number = PLUGIN_ID_184; // Plugin ID number. (PLUGIN_ID_184) - // dev.Type = DEVICE_TYPE_DUMMY; // How the device is connected. e.g. DEVICE_TYPE_SINGLE => connected through 1 datapin dev.VType = Sensor_VType::SENSOR_TYPE_DIMMER; // Type of value the plugin will return. e.g. SENSOR_TYPE_STRING - // dev.Ports = 0; // Port to use when device has multiple I/O pins (N.B. not used much) dev.ValueCount = 2; // The number of output values of a plugin. The value should match the number of keys PLUGIN_VALUENAME1_184 dev.OutputDataType = Output_Data_type_t::Simple; // Subset of selectable output data types (Default = no selection) - dev.PullUpOption = false; // Allow to set internal pull-up resistors. - dev.InverseLogicOption = false; // Allow to invert the boolean state (e.g. a switch) - dev.FormulaOption = false; // Allow to enter a formula to convert values during read. (not possible with Custom enabled) - dev.Custom = false; dev.SendDataOption = true; // Allow to send data to a controller. dev.GlobalSyncOption = true; // No longer used. Was used for ESPeasy values sync between nodes dev.TimerOption = true; // Allow to set the "Interval" timer for the plugin. - dev.TimerOptional = false; // When taskdevice timer is not set and not optional, use default "Interval" delay (Settings.Delay) dev.DecimalsOnly = true; // Allow to set the number of decimals (otherwise treated a 0 decimals) - dev.CustomVTypeVar = false; // Enable to allow the user to configure the Sensor_VType per Value that's available for the plugin break; } case PLUGIN_GET_DEVICENAME: { - // return the device name string = F(PLUGIN_NAME_184); break; } @@ -125,45 +103,13 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - // Called to show non default pin assignment or addresses like for plugins using serial or 1-Wire - // string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - // This is only called when dev.OutputDataType is not Output_Data_type_t::Default - // The position in the config parameters used in this example is PCONFIG(P184_OUTPUT_TYPE_INDEX) - // Must match the one used in case PLUGIN_GET_DEVICEVTYPE (best to use a define for it) - // see P026_Sysinfo.ino for more examples. - event->Par1 = 2; //getValueCountFromSensorType(static_cast(PCONFIG(P184_OUTPUT_TYPE_INDEX))); - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - // This is only called when dev.OutputDataType is not Output_Data_type_t::Default - // The position in the config parameters used in this example is PCONFIG(P184_OUTPUT_TYPE_INDEX) - // Must match the one used in case PLUGIN_GET_DEVICEVALUECOUNT (best to use a define for it) - // IDX is used here to mark the PCONFIG position used to store the Device VType. - // see _P026_Sysinfo.ino for more examples. - // event->idx = P184_OUTPUT_TYPE_INDEX; - event->sensorType = Sensor_VType::SENSOR_TYPE_DIMMER; //static_cast(PCONFIG(event->idx)); - success = true; - break; - } - case PLUGIN_SET_DEFAULTS: { // Set a default config here, which will be called when a plugin is assigned to a task. P184_TRIGGER_CONFIG() = 0; - P184_DEAD_ZONE_CONFIG() = 0; - P184_ZERO_CROSS_PIN() = -1; - P184_TRIGGER_PIN() = -1; + P184_DEAD_ZONE_CONFIG() = 5; + P184_ZERO_CROSS_PIN() = GPIO_NUM_NC; + P184_TRIGGER_PIN() = GPIO_NUM_NC; PCONFIG(P184_OUTPUT_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_DIMMER); success = true; break; @@ -206,12 +152,6 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - - if (P184_data.zero_crossing_pin != -1) - { - detachInterrupt(P184_data.zero_crossing_pin); - } - P184_TRIGGER_CONFIG() = getFormItemInt(F("trigger")); P184_DEAD_ZONE_CONFIG() = getFormItemInt(F("trigger_deadzone")); P184_ZERO_CROSS_PIN() = getFormItemInt(F("zero_crossing_pin")); @@ -219,50 +159,53 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) P184_TRIGGER_EDGE_CONFIG() = getFormItemInt(F("trigger_edge")); P184_FREQ_CONFIG() = getFormItemInt(F("freq")); - P184_data.trigger_value = P184_TRIGGER_CONFIG(); - // Recalculate power value based on the new trigger value from the form - for (int i = 0; i <= 100; ++i) { - if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { - P184_data.power_value = i; - break; // Found the highest power for this trigger level or lower - } - } - // after the form has been saved successfuly, set success and break success = true; break; } case PLUGIN_INIT: { + P184_data_struct* p184_data = (P184_data_struct*)getPluginTaskData(event->TaskIndex); + if (p184_data == nullptr) { + // Aloca a memória se ainda não existir + p184_data = new P184_data_struct; + if (p184_data == nullptr) { + // Falha na alocação de memória + return false; + } + initPluginTaskData(event->TaskIndex, p184_data); + } // this case defines code to be executed when the plugin is initialised - P184_data.trigger_pin = P184_TRIGGER_PIN(); - P184_data.trigger_value = P184_TRIGGER_CONFIG(); - P184_data.dead_zone = P184_DEAD_ZONE_CONFIG(); - P184_data.freq_timing_val = P184_FREQ_CONFIG(); - P184_data.zero_crossing_pin = P184_ZERO_CROSS_PIN(); + p184_data->trigger_pin = (gpio_num_t)P184_TRIGGER_PIN(); + p184_data->trigger_value = P184_TRIGGER_CONFIG(); + p184_data->dead_zone = P184_DEAD_ZONE_CONFIG(); + p184_data->freq_timing_val = P184_FREQ_CONFIG(); + p184_data->zero_crossing_pin = (gpio_num_t)P184_ZERO_CROSS_PIN(); - P184_data.trigger_value = P184_TRIGGER_CONFIG(); // Calculate initial power value based on the loaded trigger value - for (int i = 0; i <= 100; ++i) { - if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { - P184_data.power_value = i; + for (int i = 0; i <= power_to_trigger_lut_size; ++i) { + if (pgm_read_byte(&power_to_trigger_lut[i]) <= p184_data->trigger_value) { + p184_data->power_value = i; break; // Found the highest power for this trigger level or lower } } - - if (P184_data.zero_crossing_pin != -1) - { - pinMode(P184_data.zero_crossing_pin, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(P184_data.zero_crossing_pin), P184_zero_crossing, P184_TRIGGER_EDGE_CONFIG()); - } - if (P184_data.trigger_pin != -1) - { - pinMode(P184_data.trigger_pin, OUTPUT); - digitalWrite(P184_data.trigger_pin, LOW); - P184_data.p184_timer = timerBegin(1000000); - P184_data.time_us = uint64_t(P184_data.trigger_value) * P184_data.freq_timing_val; - timerAttachInterrupt(P184_data.p184_timer, &P184_timer_handler); - timerAlarm(P184_data.p184_timer, P184_data.time_us, true, 0); + if (p184_data->zero_crossing_pin == GPIO_NUM_NC || p184_data->trigger_pin == GPIO_NUM_NC) { + detachInterrupt(digitalPinToInterrupt(p184_data->zero_crossing_pin)); + gpio_set_level(p184_data->trigger_pin, LOW); + p184_data->timer = NULL; + return false; + } + else { + pinMode(p184_data->zero_crossing_pin, INPUT_PULLUP); + pinMode(p184_data->trigger_pin, OUTPUT); + gpio_set_level(p184_data->trigger_pin, LOW); // REG_WRITE(GPIO_OUT_W1TC_REG, (1 << p184_data->trigger_pin)); // Fast gpio_set_level(LOW) + attachInterruptArg(digitalPinToInterrupt(p184_data->zero_crossing_pin), &P184_data_struct::zero_crossing_handler, p184_data, P184_TRIGGER_EDGE_CONFIG()); + + + p184_data->timer = timerBegin(1000000); // 1MHz - timer can be set to microseconds + uint32_t time_us = ( (p184_data->trigger_value > p184_data->dead_zone ? p184_data->trigger_value : p184_data->dead_zone) ) * p184_data->freq_timing_val; + timerAttachInterruptArg(p184_data->timer, &P184_data_struct::timer_handler, p184_data); + timerAlarm(p184_data->timer, (uint64_t)time_us, true, 0); } success = true; @@ -271,9 +214,11 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { + P184_data_struct* p184_data = (P184_data_struct*)getPluginTaskData(event->TaskIndex); + if (p184_data == nullptr) return false; // code to be executed to read data - UserVar.setFloat(event->TaskIndex, 0, P184_data.trigger_value); - UserVar.setFloat(event->TaskIndex, 1, P184_data.power_value); + UserVar.setFloat(event->TaskIndex, 0, p184_data->trigger_value); + UserVar.setFloat(event->TaskIndex, 1, p184_data->power_value); success = true; break; @@ -281,57 +226,50 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WRITE: { - + P184_data_struct* p184_data = (P184_data_struct*)getPluginTaskData(event->TaskIndex); + if (p184_data == nullptr) return false; // parse string to extract the command String tmpString = parseString(string, 1); // already converted to lowercase if (equals(tmpString, F("triac"))) { String subcmd = parseString(string, 2); String valueStr = parseString(string, 3); - long value = valueStr.toInt(); + long value = event->Par2; if (value >= 0 && value <= 100) { if (equals(subcmd, F("power"))) { // User wants to set POWER: triac,power, - P184_data.power_value = value; + p184_data->power_value = value; // Find the corresponding trigger value from LUT - uint8_t new_trigger = pgm_read_byte(&power_to_trigger_lut[P184_data.power_value]); - P184_data.trigger_value = new_trigger; + uint8_t new_trigger = pgm_read_byte(&power_to_trigger_lut[p184_data->power_value]); + p184_data->trigger_value = new_trigger; success = true; } else if (equals(subcmd, F("trigger"))) { // User wants to set TRIGGER directly: triac,trigger, uint8_t new_trigger = value; - - // Apply dead zone logic - if (new_trigger > (100 - P184_data.dead_zone)) { - new_trigger = 100; - } else if (new_trigger < P184_data.dead_zone) { - new_trigger = 0; - } - P184_data.trigger_value = new_trigger; + p184_data->trigger_value = new_trigger; // Let's find the closest power value for the new trigger. // This is a slow lookup, but only happens on command. // The LUT maps power (index) to trigger (value). We need to find the index (power) // for a given trigger value. - for (int i = 0; i <= 100; ++i) { + for (int i = 0; i <= power_to_trigger_lut_size; ++i) { // Find the first power level (i) where the corresponding trigger // is less than or equal to the one we just set. - if (pgm_read_byte(&power_to_trigger_lut[i]) <= P184_data.trigger_value) { - P184_data.power_value = i; + if (pgm_read_byte(&power_to_trigger_lut[i]) <= p184_data->trigger_value) { + p184_data->power_value = i; break; // Found the highest power for this trigger level or lower } } success = true; } - if (success) { - P184_TRIGGER_CONFIG() = P184_data.trigger_value; // Save state - P184_data.time_us = uint64_t(P184_data.trigger_value) * P184_data.freq_timing_val; - timerAlarm(P184_data.p184_timer, P184_data.time_us, true, 0); + if (success && p184_data->timer != NULL) { + P184_TRIGGER_CONFIG() = p184_data->trigger_value; // Save state + uint32_t time_us = ( (p184_data->trigger_value > p184_data->dead_zone ? p184_data->trigger_value : p184_data->dead_zone) ) * p184_data->freq_timing_val; + timerAlarm(p184_data->timer, (uint64_t)time_us, true, 0); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat(F("P184 CMD : Trigger %d%% . Power %d%%"), P184_data.trigger_value, P184_data.power_value); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("P184 CMD : Trigger %d%% . Power %d%%"), p184_data->trigger_value, p184_data->power_value)); } } } @@ -341,37 +279,29 @@ boolean Plugin_184(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_EXIT: { + P184_data_struct* p184_data = (P184_data_struct*)getPluginTaskData(event->TaskIndex); + if (p184_data == nullptr) return true; // Nothing to do // perform cleanup tasks here. For example, free memory, shut down/clear a display - if (P184_data.zero_crossing_pin != -1) + if (p184_data->zero_crossing_pin != GPIO_NUM_NC) { - detachInterrupt(digitalPinToInterrupt(P184_data.zero_crossing_pin)); + detachInterrupt(digitalPinToInterrupt(p184_data->zero_crossing_pin)); + p184_data->zero_crossing_pin = GPIO_NUM_NC; } - if (P184_data.trigger_pin != -1) + if (p184_data->trigger_pin != GPIO_NUM_NC) { - digitalWrite(P184_data.trigger_pin, LOW); - if (P184_data.p184_timer != NULL) { - timerEnd(P184_data.p184_timer); - P184_data.p184_timer = NULL; + gpio_set_level(p184_data->trigger_pin, LOW); + if (p184_data->timer != NULL) { + timerEnd(p184_data->timer); + p184_data->timer = NULL; } + p184_data->trigger_pin = GPIO_NUM_NC; } + + clearPluginTaskData(event->TaskIndex); + success = true; break; } - - // case PLUGIN_ONCE_A_SECOND: - // { - // // code to be executed once a second. Tasks which do not require fast response can be added here - - // success = true; - // } - - // case PLUGIN_TEN_PER_SECOND: - // { - // // code to be executed 10 times per second. Tasks which require fast response can be added here - // // be careful on what is added here. Heavy processing will result in slowing the module down! - - // success = true; - // } } // switch return success; } // function diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 4b0abf78bc..a25249c9ac 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1495,8 +1495,6 @@ To create/register a plugin, you have to : #define USES_C013 // ESPEasy P2P network #endif -#define USES_P184 - #ifdef NOTIFIER_SET_STABLE #define USES_N001 // Email #define USES_N002 // Buzzer @@ -1735,6 +1733,9 @@ To create/register a plugin, you have to : #if !defined(USES_P178) && defined(ESP32) #define USES_P178 // Extra IO - LU9685 Servo controller #endif + #ifndef USES_P184 + #define USES_P184 // Output - Triac + #endif #endif // ifdef PLUGIN_SET_COLLECTION_G // Collection of all energy related plugins. @@ -1807,7 +1808,9 @@ To create/register a plugin, you have to : #if !defined(USES_P180) && defined(ESP32) #define USES_P180 // Generic - I2C Generic #endif - + #ifndef USES_P184 + #define USES_P184 // Output - Triac + #endif #endif // ifdef PLUGIN_ENERGY_COLLECTION // Collection of display plugins, set A (non-AdaGFX_Helper). @@ -2733,6 +2736,10 @@ To create/register a plugin, you have to : #define USES_P180 // Generic - I2C Generic #endif + #ifndef USES_P184 + #define USES_P184 // Output - Triac + #endif + // Controllers #ifndef USES_C015 #define USES_C015 // Blynk From 4382a66c00bcc5bd129a2ef91d849b184251fa24 Mon Sep 17 00:00:00 2001 From: Thales Maia Date: Tue, 16 Dec 2025 17:38:53 -0300 Subject: [PATCH 3/3] Dont remember --- platformio_esp32_envs.ini | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index e645770968..bc6c939fbb 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -311,7 +311,11 @@ extends = esp32_IRExt_LittleFS_ETH board = esp32_4M build_flags = ${esp32_IRExt_LittleFS_ETH.build_flags} - +[env:normal_ESP32_IRExt_4M316k_LittleFS_ETH_P184] +extends = env:normal_ESP32_IRExt_4M316k_LittleFS_ETH +board = esp32_4M +build_flags = ${env:normal_ESP32_IRExt_4M316k_LittleFS_ETH.build_flags} + -DUSES_P184 ; ESP32 MAX builds 16M flash ------------------------------