Struggling with a CT and ESP32-Wroom

Hi all, I am trying to detect when an appliance is on/off by using a 30A/1V CT Clamp (it has a built in 82 Ohm burden resistor). I created a voltage divider circuit:

When nothing is connected I get a raw value of about 1.5V, which is expected (due to voltage divider and shifting the sine wave up above 0V). But when I connect anything, it just maxes out at 3.3V, as if whatever it is has full on 30A going through it (but in actuality its 0A).

Here is my code:

sensor:
  - platform: adc
    pin: GPIO33
    name: "CT Clamp Raw ADC Value"
    update_interval: 1s
    accuracy_decimals: 4
    filters:
      - multiply: 3.3 # Scale raw ADC values to voltage (assuming 3.3V ADC reference)

Is something wrong with my circuit? Why wont it read properly? I have 2 of these idential CTs and they behave the same.

When you say nothing, do you mean the CT is not connected?

When you say “I connect anything”, do you mean you attach the CT but do not have it around any wire?

Do you have a picture of your setup?

Note that your YAML won’t do what you think, since the ADC is not likely to take a reading at the peak of the cycle.

Have you measured the output of the CT to make sure it is what you think it is?

For the Easy Button, unless you REALLY want the challenge.

This also gives you the actual power and voltage for more data.

Hey, thanks for the quick reply. Yes when I say ‘nothing’ I mean nothing is connected to the jack/wires that supposed to go to the CT. And when I say connect anything I do mean attach CT without it wrapping around any wires.

Here is the setup, sorry its ugly, just trying to get it to work before making it nice and putting into an enclosure.

One thing I am trying to understand is this. From electricity perspective. It exits 3v3 pin on ESP32. It then gets divided into ~1.65v by the circuit. It then goes through the negative (sleeve) lead of the CT, through CT into the positive lead and into GPIO33 ? Am I understanding this right?

I know that the role of the capacitor is to stabilize voltage, keep things from fluctuating. But how is it doing that? It is connected to ground, so doesnt it constantly suck up the ‘juice’ from the circuit?

I dont need it to be precise, just basically need to know when the device turns on (device pulls anywhere between 1 and 10 Amps). And I already have everything needed for this, so if I can, Id like to figure this out.

To answer your other question. I tried using mV setting on my multimeter, but it doesnt pick up anything from the CT. I think it doesnt sample enough?. I am a dummy, I had it in DC mode :), in AC mode it does pick up ~0.200V when I put ~4.5Amps through the CT.

I tried another ESP32-Wroom I had around, same result. I then tried an ESP8266 D1 mini, and that one worked like a charm… the problem is that I want to do washer/dryer on 1 ESP… and D1 mini has only a single A0 analog input…

So I think the issue is that ESP32-Wroom has absolutely unusable ADC pins. Bummer… I have some Arduino NANOs, I wonder if I can use one of those for the ADC part then use ESP32 to host it for home assistant…

Like already mentioned, you can’t read ct sensor just randomly with adc.
The reading could be peak, zero, or anything between.
Is there a reason not to use this component built for that purpose?

Ive tried using ct_clamp, but had similar result…

Since my last update Ive been messing with just going the Arduino route to prove that ESP32-Wroom’s ADC pins are just bad… but now I dont know what to think… it seems to be working through Arduino IDE in C.

No load, then putting on ~5 Amps, reading isnt precise but thats because I have no calibration done to it. But at least it senses that there is a load… with yaml ESPhome route it wouldnt sense it at all…

So it seems ESP32-Wroom is perfectly ok for this purpose? I am at a loss why it wont work through ESPhome.

I am completely new to ESPhome, so hope my next statement doesnt offend people. But is there any chance that ct_clamp and ADC libraries (I dont know if esphome uses libraries during compile but it seems to?) for this specific chip ESP32-Wroom are buggy? D1 Mini seems to be #1 choice for ESPhome, any chance ESP32-Wroom’s implementation needs work to fix this ADC stuff?

Or am I just using it incorrectly? It tried the most simple code to get it to work (what I originally posted). D1 Mini and Arduino IDE seem to point to my circuit being correct… so I just need to figure out the software bit?

#define SENSOR_PIN 34 
#define ADC_MAX 4095       // 12-bit ADC resolution for ESP32
#define VREF 3.3     
#define SENSOR_RATIO 30.0  // CT 30A/1V
#define BIAS_VOLTAGE 1.54  

const int numSamples = 500; 

void setup() {
    Serial.begin(115200);
    delay(1000);
}

void loop() {
    float sumSquares = 0; 
    for (int i = 0; i < numSamples; i++) {
        int adc_value = analogRead(SENSOR_PIN);  // Read ADC value
        float voltage = (adc_value * VREF) / ADC_MAX;  
        float acVoltage = voltage - BIAS_VOLTAGE;  // Remove DC bias

        sumSquares += acVoltage * acVoltage; 
        delayMicroseconds(200);  // Short delay to sample AC wave properly
    }

    float meanSquare = sumSquares / numSamples;  // Mean of squared values
    float rmsVoltage = sqrt(meanSquare);  // RMS voltage

    float currentRMS = rmsVoltage * SENSOR_RATIO;  // Convert to RMS current

    // Output readings
    Serial.print("RMS Voltage: ");
    Serial.print(rmsVoltage, 3);
    Serial.print("V | RMS Current: ");
    Serial.print(currentRMS, 3);
    Serial.println("A");

    delay(1000); 
}

Being new to ESPhome, Id appreciate it if someone could give me some code to try and get it to work? But I have tried the following (from this project) with no luck:

sensor:
  - platform: ct_clamp
    sensor: adc_sensor
    name: "Dryer Current"
    unit_of_measurement: "A"
    icon: "mdi:lightning-bolt-circle"
    id: dryer_current
    update_interval: 20s
    filters:
      - calibrate_linear:
          - 0.0 -> 0.0
          - 0.058 -> 4.20
    on_value:
      then:
        - sensor.template.publish:
            id: dryer_code
            # codes: 0 Idle, 1 Door Open, 2 Drying
            state: !lambda |-
              if (x > 5) return 2; 
              return 0;

  - platform: template
    id: dryer_code
    internal: true
    on_value:
      then:
        - text_sensor.template.publish:
            id: dryer
            state: !lambda |-
              if (x == 2) return "Drying";
              return "Idle";

  - platform: adc
    pin: GPIO34
    id: adc_sensor

text_sensor:
  - platform: template
    name: "Dryer"
    id: dryer
    icon: "mdi:tumble-dryer"

Perhaps its something as silly as the header (top portion) of the yaml? Maybe im pulling in wrong “libraries” for lack of a better term? This is what I have at the top… is it correct for ESP32-Wroom?

esphome:
  name: esp32_wroom_device
  friendly_name: ESP32 WROOM Device
  platformio_options:
    board_build.flash_mode: dio 

esp32:
  board: esp32dev 
  framework:
    type: esp-idf 

and this is the board I have in case it makes a difference (I have 2 of them and they behave the same):

Keep it simple:

sensor:
  - platform: ct_clamp
    sensor: adc_sensor
    name: "Dryer Current"
    unit_of_measurement: "A"
    icon: "mdi:lightning-bolt-circle"
    id: dryer_current
    update_interval: 20s

  - platform: adc
    pin: GPIO34
    id: adc_sensor
    attenuation: auto

Post what you get with that at 0A and 5A.
Be aware that update interval is 20s…

1 Like

With that code it seems to be responding, but how do I then make it output correct values?

If you post what I asked (in text, not images), I will try to match it.

Sure, I thought formatting from an image may be easier to read.

Here is the log:

Time	level	Tag	Message
13:21:28	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.015A after 346 different samples (1730 SPS)
13:21:28	[D]	[sensor:093]	
'Dryer Current': Sending state 0.01548 A with 2 decimals of accuracy
13:21:40	[D]	[sensor:093]	
'adc_sensor': Sending state 0.13884 V with 2 decimals of accuracy
13:21:48	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.015A after 345 different samples (1725 SPS)
13:21:48	[D]	[sensor:093]	
'Dryer Current': Sending state 0.01529 A with 2 decimals of accuracy
13:21:54	[I]	[safe_mode:041]	
Boot seems successful; resetting boot loop counter
13:21:54	[D]	[esp32.preferences:114]	
Saving 1 preferences to flash...
13:21:54	[D]	[esp32.preferences:142]	
Saving 1 preferences to flash: 0 cached, 1 written, 0 failed
13:22:08	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.015A after 319 different samples (1595 SPS)
13:22:08	[D]	[sensor:093]	
'Dryer Current': Sending state 0.01547 A with 2 decimals of accuracy
13:22:28	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.014A after 344 different samples (1720 SPS)
13:22:28	[D]	[sensor:093]	
'Dryer Current': Sending state 0.01441 A with 2 decimals of accuracy

INSERTED CT CLAMP IN BUT 0A

13:22:39	[D]	[sensor:093]	
'adc_sensor': Sending state 1.70988 V with 2 decimals of accuracy
13:22:48	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.005A after 406 different samples (2030 SPS)
13:22:48	[D]	[sensor:093]	
'Dryer Current': Sending state 0.00499 A with 2 decimals of accuracy
13:23:08	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.005A after 405 different samples (2025 SPS)
13:23:08	[D]	[sensor:093]	
'Dryer Current': Sending state 0.00462 A with 2 decimals of accuracy
13:23:28	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.006A after 404 different samples (2020 SPS)
13:23:28	[D]	[sensor:093]	
'Dryer Current': Sending state 0.00551 A with 2 decimals of accuracy

turmed on ~5A (its a heat gun, it starts at ~5A by the end of the log its at ~3.8A)

13:23:39	[D]	[sensor:093]	
'adc_sensor': Sending state 1.55641 V with 2 decimals of accuracy
13:23:48	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.180A after 420 different samples (2100 SPS)
13:23:48	[D]	[sensor:093]	
'Dryer Current': Sending state 0.18002 A with 2 decimals of accuracy
13:24:08	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.149A after 420 different samples (2100 SPS)
13:24:08	[D]	[sensor:093]	
'Dryer Current': Sending state 0.14900 A with 2 decimals of accuracy
13:24:28	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.130A after 415 different samples (2075 SPS)
13:24:28	[D]	[sensor:093]	
'Dryer Current': Sending state 0.12992 A with 2 decimals of accuracy

One thing that is weird to me is why is it reporting “Raw AC value” in Amps? Shouldnt it be reading Voltage on the pin? The CT outputs from 0V at 0A to 1V at 30A. Would be much eaiser to work with voltage.

It outputs whatever unit you you give on your code.
unit_of_measurement: "A"
But it doesn’t affect the number anyhow.
So if you now add this to ct_clamp code, does it get closer?

filters:
  - calibrate_linear:
      - 0.015 -> 0.0
      - 0.18 -> 5.0

This is very interesting. I am using basically the same ESP to monitor a 5vdc rail with a voltage divider. I only see a tiny bit of a noise issue and have wondered if a cap would smooth it out. I have found that the hardest part of using the ADC is getting the multiplier correct for the use case. I did tailor my divider to max output of 2.75vdc when 5vdc was presented to the divider.

I am looking for a way to monitor current on the rail as well and will be following this one.

Your case is very much different since you measure pretty flat DC voltage. CT-sensor with burden outputs relatively low AC-voltage which has to be sampled differently because it’s mains frequency sine wave.

You can use hall effect sensor like ACS712…

Yes! I did some more calibrating and now the values are within 0.1 AMP of true values. Thank you!

One thing that seems to be off are values really near zero.

Here is the code:

sensor:
  - platform: ct_clamp
    sensor: adc_sensor
    name: "Dryer Current"
    unit_of_measurement: "A"
    icon: "mdi:lightning-bolt-circle"
    id: dryer_current
    update_interval: 20s
    filters:
      - calibrate_linear:
        - 0.004 -> 0.0
        - 0.006 -> 0.04
        - 0.015 -> 0.36
        - 0.018 -> 0.45
        - 0.025 -> 0.63
        - 0.137 -> 3.54
        - 0.139 -> 3.65
        - 0.171 -> 4.32
        - 0.207 -> 4.82

  - platform: adc
    pin: GPIO34
    id: adc_sensor
    unit_of_measurement: "V"
    attenuation: auto

Note how the 0.004 case is explicitly covered as 0. But it still outputs it as 0.1A:

00:29:52	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.004A after 450 different samples (2250 SPS)
00:29:52	[D]	[sensor:093]	
'Dryer Current': Sending state 0.09609 A with 2 decimals of accuracy

Also if I disconnect the CT (open circuit) it bounced around between 0.3A and 0.5A. But should really be 0. I am assuming this is noise within the ESP32 and/or circuit?

00:38:40	[D]	[ct_clamp:041]	
'Dryer Current' - Raw AC Value: 0.022A after 389 different samples (1945 SPS)
00:38:40	[D]	[sensor:093]	
'Dryer Current': Sending state 0.52317 A with 2 decimals of accuracy

You should use:
method: exact,
otherwise your datapoints make only one approximate line

    filters:
      - calibrate_linear:
          method: exact
          datapoints:
          
            - 0.004 -> 0.0
            - 0.006 -> 0.04
            - 0.015 -> 0.36
            - 0.018 -> 0.45
            - 0.025 -> 0.63
            - 0.137 -> 3.54
            - 0.139 -> 3.65
            - 0.171 -> 4.32
            - 0.207 -> 4.82
    

Will that work ok with values that are not mapped? Like does it extrapolate the line further? If I plug in 10A will it guess correctly? Or will it cap it at 4.82 (highest datapoint) ? And the inbetween values will work?

Yes, but I suggest to give datapoints for the whole range you need.

But don’t expect miracles from ESP32 ADC, it’s quite bad overall…