An ESP32 SCT-013 current sensor build is the cheapest non-invasive way to see how much power a circuit uses, because the clamp goes around a cable without cutting anything. In this tutorial you will build the bias circuit, compute RMS current with the ESP32 ADC, estimate power and kWh, and publish the results over MQTT to broker.connectmq.com for an energy dashboard.
Safety First
The SCT-013 itself is a split-core clamp that never touches live conductors, which is what makes this project approachable. The cabinet it goes into is a different matter. Opening a consumer unit or distribution board exposes lethal voltages. If you are not qualified, clamp only on a cable that is already accessible, such as a single appliance lead split into its conductors in a purpose-made adapter, or ask an electrician to install the clamp. Never open the clamp on an SCT-013-000 while current flows without a burden resistor fitted, because an unloaded current transformer can develop a dangerous voltage on its leads.
Choosing the Right SCT-013 Variant
| Model | Output | Burden needed | Range | Conversion |
|---|---|---|---|---|
| SCT-013-000 | Current, 50 mA at 100 A | Yes, external | Up to 100 A | Depends on your burden resistor |
| SCT-013-030 | Voltage, 1 V RMS at 30 A | No, built in | Up to 30 A | 30 A per volt |
| SCT-013-020 | Voltage, 1 V RMS at 20 A | No, built in | Up to 20 A | 20 A per volt |
| SCT-013-005 | Voltage, 1 V RMS at 5 A | No, built in | Up to 5 A | 5 A per volt |
For a first build, choose a voltage-output version sized to the circuit. Higher resolution at low currents comes from picking the smallest range that covers your maximum load: a 5 A clamp on a fridge circuit gives far better readings than a 100 A clamp.
If you use the SCT-013-000, add a burden resistor across its output. A 22 Ohm burden keeps the peak voltage under about 1.6 V at 100 A RMS, which fits the bias circuit below, and gives a conversion of 2000 / 22, roughly 90.9 A per volt.
The Bias Circuit
The clamp produces an AC signal that swings positive and negative around zero. The ESP32 ADC can only read 0 to roughly 3.1 V, so the signal must ride on a DC midpoint.
- Connect two equal resistors (10 kOhm each) in series between 3V3 and GND. Their junction sits at about 1.65 V.
- Connect a 10 uF electrolytic capacitor from the junction to GND to stabilise the midpoint.
- Connect one lead of the clamp (the 3.5 mm plug tip) to the junction.
- Connect the other lead (the sleeve) to the ADC input, GPIO 34.
With a 1 V RMS output, the signal peaks at about 1.41 V, so the ADC sees roughly 0.24 V to 3.06 V. That stays inside the range of the default attenuation. Use a pin on ADC1 (GPIO 32 to 39 on a classic ESP32); ADC2 pins cannot be read while Wi-Fi is active.
Measuring RMS Current
The code samples the ADC as fast as it can for 200 ms, which covers 10 full cycles at 50 Hz and 12 at 60 Hz. It computes the mean (the DC midpoint, measured rather than assumed) and the standard deviation around it, which is exactly the RMS value of the AC component. Multiplying by the clamp's conversion factor gives amps.
Measuring the midpoint on every pass removes errors from resistor tolerance and supply drift, and analogReadMilliVolts() applies the factory ADC calibration, which helps with the ESP32 ADC's known non-linearity.
The ESP32 SCT-013 Current Sensor Sketch
#include <WiFi.h>
#include <PubSubClient.h>
#include <Preferences.h>
#include <math.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/sct3m8w"; // use your own random id
const uint8_t CT_PIN = 34; // ADC1 pin
const float AMPS_PER_VOLT = 30.0f; // SCT-013-030; 90.9 for -000 with 22 Ohm burden
const float MAINS_VOLTAGE = 230.0f; // set 120.0 in North America
const float CALIBRATION = 1.00f; // adjust after comparing with a clamp meter
const float NOISE_FLOOR_A = 0.08f; // readings below this are reported as 0
const uint32_t WINDOW_MS = 200;
const uint32_t PUBLISH_MS = 10000;
const uint32_t SAVE_MS = 600000; // persist energy every 10 min
WiFiClient net;
PubSubClient mqtt(net);
Preferences prefs;
String clientId, tPower, tStatus;
double energyKwh = 0;
uint32_t lastSample = 0, lastPublish = 0, lastSave = 0;
float lastIrms = 0, sumIrms = 0;
uint32_t samplesInPeriod = 0;
float readIrms() {
uint32_t n = 0;
double sum = 0, sumSq = 0;
uint32_t start = millis();
while (millis() - start < WINDOW_MS) {
double mv = analogReadMilliVolts(CT_PIN);
sum += mv;
sumSq += mv * mv;
n++;
}
double mean = sum / n;
double variance = sumSq / n - mean * mean;
if (variance < 0) variance = 0;
double vrms = sqrt(variance) / 1000.0; // volts RMS of the AC part
float irms = vrms * AMPS_PER_VOLT * CALIBRATION;
return irms < NOISE_FLOOR_A ? 0.0f : irms;
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(300);
Serial.printf("Wi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId.c_str(), tStatus.c_str(), 1, true, "offline")) {
mqtt.publish(tStatus.c_str(), "online", true);
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT failed, state %d\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
analogReadResolution(12);
prefs.begin("energy", false);
energyKwh = prefs.getDouble("kwh", 0.0);
clientId = "esp32-sct013-" + String((uint32_t)ESP.getEfuseMac(), HEX);
tPower = String(PREFIX) + "/power";
tStatus = String(PREFIX) + "/status";
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(512);
connectMqtt();
lastSample = lastPublish = lastSave = millis();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
// Sample about once per second and integrate energy
uint32_t now = millis();
if (now - lastSample >= 1000) {
float irms = readIrms();
float dtHours = (millis() - lastSample) / 3600000.0f;
lastSample = millis();
energyKwh += (irms * MAINS_VOLTAGE) * dtHours / 1000.0;
lastIrms = irms;
sumIrms += irms;
samplesInPeriod++;
mqtt.loop();
}
if (now - lastPublish >= PUBLISH_MS && samplesInPeriod > 0) {
lastPublish = now;
float avgIrms = sumIrms / samplesInPeriod;
sumIrms = 0;
samplesInPeriod = 0;
char payload[200];
snprintf(payload, sizeof(payload),
"{\"irms\":%.2f,\"irmsAvg\":%.2f,\"apparentPowerVA\":%.0f,"
"\"energyKwh\":%.4f,\"voltageAssumed\":%.0f}",
lastIrms, avgIrms, avgIrms * MAINS_VOLTAGE, energyKwh, MAINS_VOLTAGE);
mqtt.publish(tPower.c_str(), payload);
Serial.println(payload);
}
if (now - lastSave >= SAVE_MS) {
lastSave = now;
prefs.putDouble("kwh", energyKwh); // limited writes protect flash
}
}
Each measurement window blocks the loop for 200 ms, which is harmless for MQTT keep-alives, because the sketch calls mqtt.loop() right after sampling. If you add more clamps, read them one after another in the same pass, or move sampling into its own FreeRTOS task as shown in the ESP32 FreeRTOS MQTT guide, so the network code never waits behind a long series of windows.
Testing and Calibrating
Subscribe to the topic:
mosquitto_sub -h broker.connectmq.com -p 1883 -i sct-view-3m8w -t "connectmq/demo/sct3m8w/#" -v
With nothing running, irms should read 0. Switch on a known resistive load, such as a kettle or a heater, and compare irms with a clamp meter on the same conductor. Adjust CALIBRATION by the ratio between the two readings, for example 1.04 if the ESP32 reads 4 percent low. Calibrate near the middle of the clamp's range for the best overall accuracy.
The public area of the broker is readable by anyone, and power consumption patterns can reveal when a home is occupied, so use it for bench tests only. Its limits are listed on the free public MQTT broker page.
What the Numbers Mean
The sketch publishes apparent power in volt-amperes, calculated from measured current and an assumed mains voltage. For resistive loads like kettles and heaters, apparent power is close to real power in watts. For motors, compressors, power supplies and LED drivers, the power factor is below 1, so the real power is lower than the VA figure. Mains voltage also varies through the day.
Real power needs a voltage measurement too, typically from an isolated AC-AC transformer feeding a second ADC pin, plus sample-by-sample multiplication. That is a worthwhile next step, but the current-only version is already enough to see which appliance runs when, spot standby loads and compare days.
Accuracy Checklist
- Clamp around one conductor only. Around a whole cable, live and neutral currents cancel and you read near zero.
- Pick the smallest clamp range that covers your maximum current.
- Keep the analog wires short and away from Wi-Fi antennas and switching supplies.
- Measure the midpoint every window instead of assuming 1.65 V.
- Apply a noise floor so an idle circuit reads 0, not a few hundred milliamps.
- Calibrate against a trusted meter with a resistive load.
Building an Energy Dashboard
On the receiving side, a subscriber turns messages into charts. Telegraf's MQTT consumer can parse the JSON and write it to InfluxDB for Grafana, Node-RED can calculate daily totals and costs, and Home Assistant can treat energyKwh as an energy sensor with an MQTT sensor definition. All of them connect to the broker as normal MQTT clients. If you monitor several circuits or temperatures at once, combine this node with the DS18B20 multi-probe project, or read existing meters with the ESP32 Modbus RS485 gateway.
For production, create a broker at app.connectmq.com, switch to WiFiClientSecure on port 8883 with the ISRG Root X1 certificate, and connect with YOUR_CLIENT_ID, YOUR_USERNAME and YOUR_PASSWORD. The ConnectMQ portal's threshold alert rules can then warn you when current exceeds a limit, such as a circuit approaching its breaker rating.
Get Started with ConnectMQ
Energy data is most valuable when it is collected continuously and kept private. ConnectMQ is a managed MQTT broker with TLS, MQTT 3.1.1 and 5.0, a live topic explorer and alerting, and the free Serverless plan needs no credit card. Point this sketch at your broker and see the ESP32 IoT tutorial hub for more measurement projects.
Frequently Asked Questions
Can the ESP32 SCT-013 current sensor measure real power in watts?
With current alone it estimates apparent power in VA. Real power needs a voltage measurement as well, so the code can account for power factor.
Why does my reading show current with nothing switched on?
That is ADC noise and pickup. Apply a noise floor, keep wires short, and check that the midpoint capacitor is fitted.
Which ESP32 pins can I use for the SCT-013?
Use ADC1 pins (GPIO 32 to 39 on a classic ESP32). ADC2 pins do not work while Wi-Fi is active.
Do I need a burden resistor?
Only for the current-output SCT-013-000. The voltage-output versions such as the SCT-013-030 have a built-in burden.



