Cascading diversion with espeasy, wemosR3 and emontx shield

since I upgraded my GTI there no need for the extra functions of my other GTI limiter/diversion hardware
so trimmed it down and made it work based on only a emontx shield , espeasy firmware and wemos R3 uno… My diversion is based on PWM and logic bubble, it is quick and fairly accurate within +/- 10 watts on a 1000 watt diversion element. the smaller the element the more accurate it gets. hence the cascading SSR… as you only have a 100 and 120 possible steps with triac… to get higher accuracy you use multiple cascading diversion at lower wattage. example if I used 2 500 watt diversion then I have +/- 5 watt accuracy compared to the +/- 10 watt accuracy of single 1000 watt diversion. the firmware is set up as 3 cascading and one static. the static is just for grandfathering it into other locations . but you can edit the sketch and have it cascade over all 4 SSRs.

it has wifi connecting transmitting data via Espeasy software and home automation.

since i am using LCD I disabled CT4 port - the and I use cat5 connection between the UNO and SSR connection block
here a picture of it I just have to find a suitable box for it…

firmware:

#define FILTERSETTLETIME 5000 //  Time (ms) to allow the filters to settle before sending data


const int CT1 = 1;                                                      //  divert sensor - Set to 0 to disable 
const int CT2 = 1;                                                      // Inverter sensor - Set to 0 to disable 
const int CT3 = 1;                                                      //grid sensor 
const int CT4 = 0;                                                      // windgen  sensor - Set to 0 to disable  disable if using divert display

float grid = 0;                                                          //grid   usage
float stepa = 0;   // 
float stepb = 1;
float stepc = 1;
float prestep =1;
float step1 = 0;   // 
float step2 = 1;
float step3 = 1;
float prestep1 =1;
float curinvt = 1; //percentage of power usage comparison over or below grid usage 
float curelem =1;
float kw = 0;
int curgrid = 0;                                                      // current  PMW step
int curgrid2 = 0;                                                     //current triac step
float invert =100;
float wind = 100;
float diverter =100;
float element = 5000; //wattage of  element  use in case  no inverter reading or grid tie outside  inverter reading - -

int pulse = 5;       // pin for static pulse  disable if you cascade on 4 ssr
int pulse1 = 9;
int pulse2 = 6;
int pulse3 = 10;
//int pulse4 = 5; enable pulse 4 if you wish 4 cascading ssr

int invstatus = 3;  // pin for led display  showing overproduction 
float per = 0;

int stat ;
int stepbu;
int ios = 3;  /// Number of SSR to control in cascading  mode

float stepa4 = 0;   // 
float stepb4 = 1;
float stepc4 = 1;
float prestep4 =0; //1
int stepbu4;
int stat4 ;
float curelem4 =1;
int curgrid4 = 0;
int ssr=0; // 0= zerocrossing 1 = phase angle 
int sV;
int full;
int type = 0; // 0= casdading -  1 = equal for diverting 
int DIVERT = 0;
String value;

#include "EmonLib.h"
EnergyMonitor ct1,ct2,ct3, ct4;   // Create  instances for each CT channel

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3f,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display



typedef struct { int power1, power2, power3, power4, Vrms;} PayloadTX;      // create structure - a neat way of packaging data for RF comms
PayloadTX emontx;                                                       

const int LEDpin = 9;                                                   // On-board emonTx LED 

boolean settled = false;

void setup() 
{
   lcd.init();                      // initialize the lcd 
  lcd.init();
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(3,0);
  lcd.print("GTI Limiter");
  lcd.setCursor(2,1);
  lcd.print("Stephen krywenko!");
      
  Serial.begin(115200);
  analogWrite(pulse1, 0 );
  analogWrite(pulse2, 0 );
  analogWrite(pulse3, 0 );
  //analogWrite(pulse4, 0 );  //Enable  if you wish to cascade on  4 ssr /disable  pulse other below




  if (CT1) ct1.current(1, 60.600);                                     // Setup emonTX CT channel (ADC input, calibration)
  if (CT2) ct2.current(2, 60.606);                                     // Calibration factor = CT ratio / burden resistance
  if (CT3) ct3.current(3, 60.606);                                     // emonTx Shield Calibration factor = (100A / 0.05A) / 33 Ohms
  if (CT4) ct1.current(1, 60.600); 
  
  if (CT1) ct1.voltage(0, 136.54, 1.7);                                // ct.voltageTX(ADC input, calibration, phase_shift) - make sure to select correct calibration for AC-AC adapter  http://openenergymonitor.org/emon/modules/emontx/firmware/calibration. Default set for Ideal Power adapter                                         
  if (CT2) ct2.voltage(0, 136.54, 1.7);                                // 268.97 for the UK adapter, 260 for the Euro and 130 for the US.
  if (CT3) ct3.voltage(0, 136.54, 1.7);
  if (CT4) ct1.voltage(0, 136.54, 1.7);
  
                                                                                     
}

void loop() 
{ 
  if (CT1) {

    ct1.calcVI(20,2000);                                                  // Calculate all. No.of crossings, time-out 
    emontx.power1 = ct1.realPower;
    diverter =  emontx.power1;
    Serial.print("TaskValueSet,1,3,"); Serial.println(emontx.power1);
                                      
  }
  
  emontx.Vrms = ct1.Vrms*100;                                            // AC Mains rms voltage 
  
  if (CT2) {
   
    ct2.calcVI(20,2000);                                                  // Calculate all. No.of crossings, time-out 
    emontx.power2 = ct2.realPower;
    invert = emontx.power2;
    Serial.print("TaskValueSet,1,2,"); Serial.println(emontx.power2);  

  } 

  if (CT3) {
    ct3.calcVI(20,2000);                                                  // Calculate all. No.of crossings, time-out 
    emontx.power3 = ct3.realPower;
    grid = emontx.power3; 
    Serial.print("TaskValueSet,1,1,"); Serial.println(emontx.power3);

  } 
  
   if (CT4) {
     ct1.calcVI(20,2000);                                                  // Calculate all. No.of crossings, time-out 
    emontx.power1 = ct1.realPower;
    wind = emontx.power1; 
    Serial.print("TaskValueSet,1,3,"); Serial.println(emontx.power1);


  }
 
   Serial.print("TaskValueSet,1,4,"); Serial.println(ct1.Vrms);
   
 //########################
 
  if (invert <0){       // for capture ac adapter errors is it display constant zero on inverter display -- ct or ac adapter need to be reversed
    invert = 0;
  }
  if (wind <0){       // for capture ac adapter errors is it display constant zero on inverter display -- ct or ac adapter need to be reversed
    wind = 0;
  }
  //Serial.print(" "); Serial.print(ct1.Vrms);


if (grid != 0 ) {  
if (invert >=0) {

  step1 = ( grid / invert);

  prestep1 = (step2);

step2 = (prestep1 + step1);
  if (step2 > 1) {
    step2 =1;
  }
  if (step2 < 0) {
    step2 = 0;
  }
  curinvt = (0  + step2);
  
  curgrid2 = ( 254 * curinvt  );
  curgrid2 =(254-curgrid2);  //inverts the value of curgrid if need be 


  
}
}

if (CT2){
if (grid !=0) {
 
  
  //curgrid = 0;
  stepc = (grid / element); 

  prestep = (stepb);

stepb = (prestep + stepc);
  if (stepb > 0) {
    stepb =0;
  }
  if (stepb < (0-ios)) {
    stepb = (0-ios);
  }
  curelem = (0  + stepb);
stepbu=curelem;
curelem = (curelem - stepbu);
  curgrid = ( 254 * curelem  );
  curgrid =(0-curgrid);  //inverts the value of curgrid if need be 

}

if (grid !=0) {
 
  
  //curgrid = 0;
  stepc4 = (grid / element); 

  prestep4 = (stepb4);

stepb4 = (prestep4 + stepc4);
  if (stepb4 > 1) {
    stepb4 =1;
  }
  if (stepb4 < 0) {
    stepb4 = 0;
  }
  curelem4 = (0  + stepb4);


  curgrid4 = ( 255 * curelem4  );
  curgrid4 =(255-curgrid4);  //inverts the value of curgrid if need be 

}

}

int statc ;
int ivar;
int statb ;

stat = (0-stepbu);
if (curgrid==256){curgrid=0;}

//#############################//

analogWrite(pulse,curgrid4); // single pulse signal for SSR off arduino board // disable if you want 4 cascading ssr

//############################//


 
if (stat > (ios-1)) {stat=(ios-1);curgrid=255;full=1;}
if (stat ==0) {ivar = 1;}
else  {ivar = 0;}
  if ( type == 0){
   // Serial.println(" solar Diversion - Cascading");
if (stat != statb) {
  statc=(stat+1);
  statb=stat;
  for(int i=ivar;i < stat; i++){
   
     ////////pwmController.setChannelPWM(i, 255 << 4);



if ( i == 0){
  analogWrite(pulse1, 255 );
}
if ( i == 1){
  analogWrite(pulse2, 255 );
}
if ( i == 2){
  analogWrite(pulse3, 255 );
}
/*if ( i == 3){                   //enable for 4th ssr
  analogWrite(pulse4, 255 );
}*/
  }
   
  for(int i=statc;i <ios; i++){
    
   ///////////  pwmController.setChannelPWM(i, 0 << 4);
   



if ( i == 0){
  analogWrite(pulse1, 0 );
}
if ( i == 1){
  analogWrite(pulse2, 0 );
}
if ( i == 2){
  analogWrite(pulse3, 0 );
}
 /*if ( i == 3){                  //enable for 4th ssr
  analogWrite(pulse4, 0 );
}*/
     }

     }
      if (ssr==1){
      boolean st=false;                                  //an indicator to exit the while loop

  unsigned long start = millis();    //millis()-start makes sure it doesnt get stuck in the loop if there is an error.

  while(st==false)                                   //the while loop...
  {
    sV = analogRead(0);                    //using the voltage waveform
    if ((sV < (1024*0.55)) && (sV > (1024*0.45))) st=true;  //check its within range
    if ((millis()-start)>2000) st = true;
  }
  sV=map(curgrid,0,255,10000,0);  //delay before pulse  
     delayMicroseconds(sV); 
     
    }
     ////////////pwmController.setChannelPWM(stat, curgrid << 4);


     DIVERT = curgrid;


if ( stat == 0){
  analogWrite(pulse1, DIVERT );
  analogWrite(pulse2, 0 );
  analogWrite(pulse3, 0 );
  //analogWrite(pulse4, 0 );                  //enable for 4th ssr
}
if ( stat == 1){
  analogWrite(pulse2, DIVERT );
  analogWrite(pulse3, 0 );
 // analogWrite(pulse4, 0 );                  //enable for 4th ssr
}
if ( stat == 2){
  analogWrite(pulse3, DIVERT );
 // analogWrite(pulse4, 0 );                  //enable for 4th ssr
}
if ( stat == 3){
  //analogWrite(pulse4, DIVERT );                  //enable for 4th ssr
}

     
  }

    
  if (type == 1){
   // Serial.println(" solar Diversion -  In Unison");
    for(int i=0;i < ios; i++){
    ///////////// pwmController.setChannelPWM(i, curgrid << 4);

 DIVERT = curgrid ;

if ( i == 0){
  analogWrite(pulse1, DIVERT );
}
if ( i == 1){
  analogWrite(pulse2, DIVERT );
}
if ( i == 2){
  analogWrite(pulse3, DIVERT );
}
/*if ( i == 3){
  analogWrite(pulse4, DIVERT );                  //enable for 4th ssr
}*/
   
  } 
  }


analogWrite(invstatus, curgrid4);  // led display  showing overproduction 


kw =  (grid / 1000) ;
per = ( curgrid / 254);
per = (1 - per);
//per = ( 100 * per);
    lcd.backlight();
    lcd.clear();      
    lcd.setCursor(0,0);
    lcd.print("KWATTS ");
    lcd.print(kw);
    lcd.setCursor(0,1);
    lcd.print("Volts  ");
    lcd.print(ct1.Vrms);
    lcd.setCursor(0,2);
    lcd.print("GTI    ");
    lcd.print(invert);
    if (CT1){
    lcd.setCursor(0,3);  
    lcd.print("Divert ");   //displays current step of triac and ad5206 chip 
    //lcd.print ( "-");
    lcd.print (diverter);
    }
    if (CT4){
      lcd.setCursor(0,3);
      lcd.print("Wind   ");   // displays  wind inverter output 
      lcd.print (wind);
    }


  // because millis() returns to zero after 50 days ! 
  if (!settled && millis() > FILTERSETTLETIME) settled = true;

  if (settled)                                                            
  { 
        
 



                                                     

                                                     
  }

}

how to flash it to wemos R3 just follow this basic howto it based on espeasy for easy sending of data to various CMS, databases and home automation programs

it has some remant code from the base emontx sketch it just needs a little more clean up.

okay good luck have fun

1 Like

hi there – Here a cleaned up version of the code, works well at least on 60hz power systems I made it a bit more tidier and easier to enable and disable functions . ie LCD, number SSRs, system type etc…
to make it a little more accurate I adjusted the the Freq of the pwm to 30 hz and and set the pulse to begin at zero-crossing.

All in all it working reasonably well, and the cascading function if functioning as expected- as it is diverting 3kw at times and maintaining an reasonably low variability …

Diverter_cascading.zip (4.0 KB)

I think I accidentally fat fingered and deleted this line in the adjustable setting

int LCD = 1; // 1 to enable 0 to disable

1 Like

here an adaption of my SSR code - though it does not use the cascading function. but it is wattage controller for a SSR. using off the self stuff. wemos R3 ( uno/wifi passes data to database) a keypad shield ( slightly modified - pulled the A0 pin and redirected it to pin A5) and emontx shield

it works reasonable well. it variation is +/- 5 % depending on the element size here it is controlling 4500 watt element ( it averages out over 10 seconds it is about 1% accuracy for heating )

DSC05305

up/down is SSR % control
left/right is wattage control ( if you leave it floating using wattage it is more accurate )
and select is Save ( save setting to eeprom and automatically reloads them when rebooted)

I built this for my water to water heat pump-- last fall one of my ground loop split and was leaking so I had to disable +30% of my field… ( I will retrench a new line this summer to compensate for the lost line and I probably put down a redundancy line beside it so if it splits again I just switch to the redundancy instead of digging up my yard again ) – but my ground temp was falling to very low temps so the heat pump efficiency was dropping … so this is design to help compensate as the COP was only about 1.5 … so the pump was laboring on and on… so this was to help me set the amount kw boost I needed ( which is currently set to about 2kw) later on I will add a thermo sensor and let it automatically adjust boost depending on the ground loop temp ie any thing above 7c (0%) and below 6C about 10% for each degree colder

uno sketch ( wifi esp 8266 is handle by espeasy)
keypad2.zip (3.2 KB)

Hi @stephen, interested in what you are doing here, but not really understanding what you are doing or how :slightly_smiling_face:.

GTI? To me that is a car… :rofl: :rofl:.

I know what PWM is, but from where to where.

I suspect there is a lot here, but could you start at the beginning please :grin:.

Hi borpin

Glad you are interested :slight_smile:
it just similar to Mk2 PV Router but using different method. I use frequency as an easy way to determine TRIAC firing time.
my grid is 60 hz. and when using an arduino the lowest i can set it easily is 30 hz ( esp is 10 hz but when I set it at 12-15 hz I get better accuracy less of a discernible pulse ) and I get a full ~256 steps. I can run a simple induction/brush motor at ~10 rpm ( it will behave like a stepper motor).

I changed my GTI ( grid tie inverter ) to WVC and I can change their output via wireless connection. My original diverter used digital pot to reduce DC input to GTI on top of normal diversion.

basically it works like this:
1- calculates the the element size compare the number of available steps … Ie: 1000 watt element 255 steps = 4 watt per step
2- once the grid goes to negative. it looks at the number of watts of over production, divides by “4 watt” to get the appropriate PWM step. ie: 160 watt overproduction / 4watts = 40 —> sets the PWM 40 ----> fires the SSR using this PWM.
3- next cycle looks to see if grid is at zero - and whether it is over firing or under firing the SSR. then does a bubble search to see what the next appropriate step will be . ie at PWM of 40 = in real world 150 watts. the bubble search say it should be PWM of 43. but on the next cycle the SSR output is 163 watts. the bubble search then calculates it should be PWM of 42… and so on and so on :slight_smile: :slight_smile:
( since it using a bubble search you can use a wide variety of frequencies. the higher the frequency the less usable steps but the bubble search will hone in and figure out the the correct pulse with in 3 or 4 cycles ie: the default PWM freq only gives you about 150 usable steps where the SSR will fire )

Cascading:
the Cascading function allows you to fire multiple diversion sources. the smaller the element the more accurate the diversion. example at 60 hz there are 120 possible zero crossing. so a standard hotwater tank in N.A uses 4500watt
4500 / 120 = 38 watts
1000/120 = 8 watts
500/120 = 4 watts
so cascading allows me to get higher accuracy and larger loads- ( I have a version that use pca9685 16 channel PWM of cascading)
but it basically works like this when the first PWM reaches the 255 it begins to fires the next one, when it reaches the full it goes to the next until they are are on or all off

visually :

1 SSR ( PWM 255) →
2 -------------------------SSR ( PWM 255) →
3-----------------------------------------------------SSR (PWM 255) →
4------------------------------------------------------------------------------SSR(PWM 255) – > FULL

so say i use 1000 w elements with 4 cascading SSR 120 * 4 = 480 usable steps on total combine 4000 watt elements - but I have an accuracy of ~10 watts +/- 2%
so in my case I have 2 hot water tanks in my house 4 elements. they are primarily heated by solar or a heat pump ( the electric elements are not used only as redundancy backup). in each case the elements are 4500 watt at 240 volts I operate them at 120 volts so they are 1140 watt output.

Ok thanks. So the SSR can control the amount of power it passes in the PWM steps?

yes
but the control is abit different’
say as smooth 50% out put- 1 = 0n 0 = off at zero crossing
10101010101010101010101010101010
mine method would fire like this depending on frequency
50%
11001100110011001100110011001100 ( some times it will group as 000 and 111 ( the pulse) but mostly 00 11)
you can use different frequency to imitate the smoother output but there are some trade off you will have poorer control at lower or higher end ie the default arduino freq the pulsing of the triac starts around 80 steps (ie the motor will not prform as a stepper at lower outputs ) and at the upper end you loose about 20 steps

also some SSR are particular as they have higher voltage thresh hold even with in the same brand. example - an andrino pulse volt is 5 volts and esp is 3volt. and even with in the same lot of SSR all will work with uno but there will be a certain portion that will not work with esp. the led may light up and pulse but there not enough volts to trigger the triac to fire.

1 Like

just mentioning you do not have to use an SSR you can use what ever size TRIAC is suitable ie BTA41 but you will need to use MOC3043 to beable to work with all the PWM pulses frequencies

1 Like

Well stuck at home due to the pandemic-- so i worked on my cascading diverter- figured out a way for detecting HZ and then letting the device configure itself automatically for what every the grid is ie 60 hz or 50hz… ( plus data logging the info )
the most I can configure it for is 3 cascading SSR - for some reason the forth is either on or off no PWM available on pin 11
and there is some drift that has to be compensated for detecting the HZ later on I guess the CPU can not keep up.

if someone wants to try against a 50hz system let me know if it works- that would be nice.
on my 60hz grid it works fine.
simply hook up an ssr to pin 9 and connect it to a basic induction motor or a hair dryer. it should slowly step the motor up. starting around 10rpm
all you should have to do is is adjust the frequency divider
FRAC = 1 either increase it ie FRAC = .5 - doubles your frequency or FRAC =2 1/2s it… to find the best frequency for a 50hz
then you might have to adjust DRIFT to get the hz to equal your grid… currently it set at low resolution PWM 256 but you can set to hi resolution PWM ( 65536 pwm ) but my math is not set up for that but is doable. just change 255 → 65535 and 254 → 65534 but pin 3 will have to still be 255 range ( just divide “DIVERT” by 256 will work easy enough)
diverter_HZ_detection_cascading.zip (4.9 KB)

here the pwm of 65536
diverter 65536.zip (4.9 KB)

here an example of the 16 bit diverter bubble searching the correct pulse as it basically steps up 1% at every reset of the PWM… it a pretty smooth even acceleration of the motor… even with the ping a a PWM fire of TRIAC

and the uno sketch for it
diverter_16bit_autohz.zip (5.1 KB)

I was thinking if I had 3 phase- it would be fairly easy to construct a load balance diverter. but you would have to use a mega as the base since it will have 11 16 bit pins (2,
3, 5, 6, 7, 8, 11, 12, 44, 45, and 46) and you could have 3 cascading SSR per phase

here a movie of my final device I switch it to 16 pwm as it allow me a bit of buffer in the steps for bubble searching… 255 should be good but i found that 255 can be a bit to small of a step 512 or 1024 work well. as the shifting of a few pwm steps this way or that way it can hone in quite well… where 255 one step this way or that way can be to much of a step in some cases … but I will give it try for a few weeks to see how it preforms

here a small movie of the “finished” device sorry it goes a bit blurry at the end as i demonstrate the loss of the AC adapter connection.

and here the info sent to the on board esp8266 with espeasy loaded , which in turn is sent to my database and home automation system

well here I guess my latest update to my energy diverter- it working well but I downgraded to 256 steps… as it seams fine enough.
I averaged out the sends to espeasy to to every 5th cycle to reduce the load on espeasy to data send to every about 1.5 seconds
cascading_energy_diverter_256a.zip (5.5 KB)

and here an image with in the espeasy firmware that shows the data coming in from the uno energy monitoring and diverting firmware…
for me and my 60hz grid it works fine and pretty accurate
grid- is the current grid usage:
GTI- is the Grid tie inverter:
Misc- is the 3 CT sensor which is the diverting output:
Casscading-is the current step it on
Static- if you are using a static pulse:
Hz is the current Hz of the grid :

and here it is cascaded into the second SSR as it is over 255

1 Like

in an effort to make it as compatible and universal here another update to the firmware. since I was able to get to 13-15hz pwm ( for 60hz grid) on Uno the diverter is working very well ( I could only do that with esp driven version of emontx shield before but was not very stable to do that and send data at the same time ). with an UNO as the diverter base and esp handling only sending data it works very stable… but I lost one cascade as i required 4 and I can only get 3 when using PWM.h so i added a 4 relay block to compensate…
SSR1—>0- 255 steps (1000 watt diversion element)
------------> SSR2 —> 256-510 steps ( 1000watt diversion element)
-------------------> as soon as SSR3 fires ( on the fifth cycle - relay 1 turns on ( 500 watt diversion)
-------------------> as soon as SSR3 fires again ( on the fifth cycle - relay 2 turns on ( 500 watt diversion )
-------------------> as soon as SSR3 fires again ( on the fifth cycle - relay 3 turns on ( 500 watt diversion )
-------------------> as soon as SSR3 fires again ( on the fifth cycle - relay 4 turns on ( 500 watt diversion )
-------------------> SSR3 → 511- 765 step ( 1000 watt diversion)
total possible diversion 5KW based on the above configuration
in reverse- once the diversion drops to the SSR1 on the 5th cycle it turns off relay 1 if not enough on the next 5th cycle it turns off relay 2 ( and so on)
if a very large load comes on and the SSR drop to zero the relays are all turned off at once ( this would be determined by the element size you listed how fast this would happen when you first uploaded the firmware )also as a safety feature if +/- AC detection is lost all SSRs and relays are disabled

here the daughter shield for the emontx shield it very simple–
energydivertershield

Autosensing-diverter-relay.zip (5.9 KB)

if using fortek SSR buy ones that are +double the AMP rating for long life… if making your own SSR use MOC3043 my preferred triac is bta40600 and then mount them to a old cpu cooler and if they die they quickly and easily replace
for the relay block use one that has it own power sources ( in my case I had a dead wemos D1 the 3volt died but the 5v worked fine and places a 4 block relay shield on it and solder a rj-45 to it GND to GND and the appropriate wire to which ever pin was required operate the relays -ie: 4,5,6 and 7 on the dead wemos D1)

okay good luck and have fun…

1 Like

Hi all, i’m new here.

I’m looking for a simple solution to control the power of a submersion heater based on the excessive power.

I currently have a ESP32 project which already containts a variable that holds the available power for the heater.

Also i have a Fortek ssr available (MOC3063) to play around with.
So the only thing i’m missing is the bursting/modulating part of the code to control the SSR.

Are there any simple arduino based examples availble which i can implement in my ESP32 project?
As the MOC3063 is a zero detection type, do i need to implement some kind of synchronisation for the “zero crossing” or is this not necessary?

Welcome, Ruben, to the OEM Forum.

Have you looked at ‘Learn’ here and “PV Diversion”? Learn→PV Diversion→Introduction

You’ll find quite a lot there, but unfortunately nothing that’s directly for the ESP32. But both detailed designs there use the MOC3063.

if you want to use my version of code that just uses PWM you can just use this bit of my code… all it is is a bubble search it gets an output from grid CT calculated the closest pwm, on the next loop looks at the grid CT if it not zero yet tries to calculate it again and setting the PWM. it generally finds in 3-4 cycles . i uses zero crossing with an arduino and I use a frequency similar to the grid . but it not necessary to find zero crossing if you do not want too. as the bubble search will keep searching until it finds something closest to zeroing it out no matter what

if (CT4){
if (grid !=0) {

  stepc = (grid / element); 
  prestep = (stepb);

stepb = (prestep + stepc);
  if (stepb > 1) {
    stepb =1;
  }
  if (stepb < 0) {
    stepb = 0;
  }
  curelem = (0  + stepb);


  curgrid = ( 254 * curelem  );
  curgrid =(254-curgrid);  //inverts the value of curgrid if need be 
 //   Serial.print(" curgrida ");
  //Serial.println(curgrid);
}
}

i use PWM.h and set the PWM to the frequency that works the best for me in my case the arduino has 256 steps your esp probably has 1024 you just have to adjust for that difference in pwm steps

the other ones watch grid and if it goes past zero after so many cycles it disables the ssr

grid is the current grid value
element is the size of the diversion
curgrid is the PWM pulse setting

not sure with the esp32 but I know with the esp the SSRs can be a bit of a hit and miss they say 3-24 volt compatibility for firing the SSR but some times the voltage output is not quite high enough to fire them consistently some times you have to try a couple SSR to find one that will play nice with esp 3 volt