Motor current monitoring turns the power cable of any electric motor into a health sensor, without touching the machine itself. In this tutorial you will clip a non-invasive CT clamp onto a motor lead, measure RMS current with an ESP32, publish it over MQTT and run a Python service that flags overloads, excessive starts and abnormal load patterns.
Why Motor Current Is Such a Useful Signal
Current tells you how hard a motor is working. Because the clamp sits around the cable in the electrical cabinet, you can retrofit it on old machines that have no data interface at all, and you never need to mount anything on rotating parts.
From a single current reading per second you can derive a surprising amount:
- Running state: off, idle (motor turning with no load) or loaded. This is often the cheapest way to feed machine downtime tracking.
- Overload: current above the nameplate full-load amps for longer than a short start-up period.
- Load drift: a conveyor drawing gradually more current may have a dragging bearing, a misaligned belt or product build-up. A pump drawing less may be running dry or cavitating.
- Start frequency: too many starts per hour overheat windings, and a sudden rise often reveals a control problem.
- Phase imbalance: with one clamp per phase, unequal currents point to supply problems or winding faults.
More advanced techniques such as motor current signature analysis look at the frequency spectrum of the current to detect broken rotor bars and bearing defects. They need much faster sampling and dedicated hardware. Everything in this article works with a few RMS values per second and still catches the most common operational problems.
Safety First
A CT clamp is non-invasive: it wraps around a single insulated conductor and never makes electrical contact. Even so, the clamp is installed inside a cabinet with live parts. Have a qualified electrician install it with the cabinet isolated, clamp around one phase conductor only (never the whole multi-core cable, where the fields cancel), and keep the low-voltage sensor wiring separated from power wiring.
Hardware
| Part | Purpose | Notes |
|---|---|---|
| ESP32 DevKit | Sampling, RMS calculation, Wi-Fi and MQTT | Any ESP32 with an ADC1 pin |
| SCT-013-030 CT clamp | Measures current up to 30 A | Voltage-output version, 1 V at rated current |
| 2 x 10 kOhm resistors | Bias the signal to mid-supply | Forms a voltage divider from 3.3 V |
| 10 uF capacitor | Stabilises the bias point | Between the divider midpoint and GND |
| 3.5 mm jack socket | Connects the clamp | Optional, but tidier than cutting the plug |
Pick a clamp rated just above the motor's full-load current for the best resolution. A 30 A clamp on a 3 A motor will work, but the signal will be small and noisy.
Wiring
The clamp outputs an AC voltage centred on zero, while the ESP32 ADC only reads positive voltages. Bias it to the middle of the range:
- Connect the two 10 kOhm resistors in series between 3.3 V and GND.
- Connect the 10 uF capacitor from the midpoint to GND.
- Connect one clamp lead to the midpoint and the other to GPIO 34.
GPIO 34 belongs to ADC1, which keeps working while Wi-Fi is active. The ESP32 SCT-013 current sensor guide covers calibration and wiring variants in more detail.
Firmware: Motor Current Monitoring on the ESP32
The sketch samples the ADC 2,000 times over 200 ms, which covers ten mains cycles at 50 Hz and twelve at 60 Hz. It removes the DC bias by subtracting the mean, computes the RMS voltage and converts it to amps. Every second it publishes the current and a simple state.
#include <WiFi.h>
#include <PubSubClient.h>
#include <math.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const char* CLIENT_ID = "motor-ct-9be2c4";
const char* TOPIC = "connectmq/demo/9be2c4/line1/conveyor1/current";
const int CT_PIN = 34;
const float AMPS_PER_VOLT = 30.0f; // SCT-013-030: 1 V RMS = 30 A
const float CAL = 1.00f; // adjust after comparing with a clamp meter
const int SAMPLES = 2000;
const uint32_t SAMPLE_US = 100; // 10 kHz, 200 ms window
const float IDLE_A = 0.5f; // below this the motor is off
const float LOADED_A = 2.0f; // above this the motor is under load
WiFiClient net;
PubSubClient mqtt(net);
uint16_t samples[SAMPLES];
float measureAmps() {
uint32_t next = micros();
for (int i = 0; i < SAMPLES; i++) {
while ((int32_t)(micros() - next) < 0) { }
next += SAMPLE_US;
samples[i] = analogReadMilliVolts(CT_PIN);
}
double mean = 0;
for (int i = 0; i < SAMPLES; i++) mean += samples[i];
mean /= SAMPLES;
double sumSq = 0;
for (int i = 0; i < SAMPLES; i++) {
double d = samples[i] - mean;
sumSq += d * d;
}
double vrms = sqrt(sumSq / SAMPLES) / 1000.0;
return vrms * AMPS_PER_VOLT * CAL;
}
void ensureConnected() {
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
}
while (!mqtt.connected()) {
if (!mqtt.connect(CLIENT_ID)) delay(2000);
}
}
void setup() {
Serial.begin(115200);
analogSetPinAttenuation(CT_PIN, ADC_11db);
WiFi.mode(WIFI_STA);
mqtt.setServer(MQTT_HOST, 1883);
ensureConnected();
}
void loop() {
ensureConnected();
mqtt.loop();
static uint32_t lastPub = 0;
if (millis() - lastPub < 1000) return;
lastPub = millis();
float amps = measureAmps();
if (amps < 0.1f) amps = 0; // suppress noise floor
const char* state = amps < IDLE_A ? "off" : amps < LOADED_A ? "idle" : "loaded";
char payload[128];
snprintf(payload, sizeof(payload),
"{\"current_a\":%.2f,\"state\":\"%s\",\"uptime_s\":%lu}",
amps, state, millis() / 1000);
mqtt.publish(TOPIC, payload);
Serial.println(payload);
}
Calibrate once with a trusted clamp meter: run the motor at steady load, compare both readings and set CAL to the ratio. Then set IDLE_A and LOADED_A by observing the real machine running empty and loaded.
Check the stream from any terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ct-viewer-9be2c4 -t "connectmq/demo/9be2c4/#" -v
Detecting Overloads and Abnormal Patterns
The device reports raw facts. Interpretation belongs in a service that can see history. The Python subscriber below (paho-mqtt 2.x) implements four checks that cover most practical needs:
- Overload: current above the nameplate rating for more than a grace period, which ignores normal inrush at start-up.
- Start counting: transitions from off to running, counted over a sliding hour.
- Load drift: the loaded-state average over the last ten minutes compared with a learned normal.
- Stale data: no message for 30 seconds means the sensor or network is down.
import json
import time
from collections import deque
import paho.mqtt.client as mqtt
PREFIX = "connectmq/demo/9be2c4"
RATED_A = 8.5 # motor nameplate full-load amps
OVERLOAD_GRACE_S = 10
MAX_STARTS_PER_HOUR = 6
DRIFT_LIMIT = 0.20 # 20 % away from learned normal
LEARN_SAMPLES = 1800 # 30 minutes of loaded running
over_since = None
last_state = "off"
starts = deque()
loaded = deque(maxlen=600) # last 10 minutes of loaded samples
learn = []
normal = None
last_seen = time.time()
def alert(client, kind, detail):
msg = {"ts": int(time.time()), "kind": kind, **detail}
client.publish(f"{PREFIX}/line1/conveyor1/alert", json.dumps(msg), qos=1)
print("ALERT", msg)
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(f"{PREFIX}/line1/conveyor1/current", qos=1)
def on_message(client, userdata, msg):
global over_since, last_state, normal, last_seen
d = json.loads(msg.payload)
amps, state, now = d["current_a"], d["state"], time.time()
last_seen = now
if amps > RATED_A:
over_since = over_since or now
if now - over_since >= OVERLOAD_GRACE_S:
alert(client, "overload", {"current_a": amps, "rated_a": RATED_A})
over_since = now # re-alert at most once per grace period
else:
over_since = None
if last_state == "off" and state != "off":
starts.append(now)
while starts and now - starts[0] > 3600:
starts.popleft()
if len(starts) > MAX_STARTS_PER_HOUR:
alert(client, "frequent_starts", {"starts_last_hour": len(starts)})
starts.clear()
last_state = state
if state == "loaded":
if normal is None:
learn.append(amps)
if len(learn) >= LEARN_SAMPLES:
normal = sorted(learn)[len(learn) // 2]
print(f"learned normal loaded current: {normal:.2f} A")
else:
loaded.append(amps)
if len(loaded) == loaded.maxlen:
avg = sum(loaded) / len(loaded)
if abs(avg - normal) / normal > DRIFT_LIMIT:
alert(client, "load_drift", {"avg_a": round(avg, 2), "normal_a": round(normal, 2)})
loaded.clear()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="motor-analytics-9be2c4")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
while True:
time.sleep(5)
if time.time() - last_seen > 30:
alert(client, "stale", {"seconds_silent": int(time.time() - last_seen)})
last_seen = time.time()
For the overload rule alone, you do not even need this service. A threshold alert rule in the ConnectMQ portal on the current_a field will log every crossing in the alerts log. Keep the Python service for the stateful checks such as drift and start counting.
Interpreting What You See
| Pattern | Likely causes | Typical action |
|---|---|---|
| Current slowly rising under the same load | Bearing drag, misalignment, belt tension, product build-up | Inspect mechanics, confirm with vibration |
| Current dropping on a pump | Dry running, closed valve, worn impeller, cavitation | Check process conditions and suction side |
| Frequent short overloads | Jams, undersized motor, process changes | Review process and motor sizing |
| Many starts per hour | Control loop hunting, sensor chatter | Tune hysteresis in the control logic |
| Unequal phase currents | Supply imbalance, loose connection, winding fault | Electrical inspection |
Current is strongest when combined with other signals. A rising current with rising vibration points to mechanics, while rising current with normal vibration and rising bearing temperature monitoring readings narrows the search further. The predictive maintenance with IoT article shows how these signals combine into a maintenance strategy.
Choosing Sensible Thresholds
Start from the motor nameplate, not from guesses. The full-load amps value is the upper bound for continuous operation, so the overload rule should use it directly, with a grace period long enough to cover the normal start-up inrush you observe on that machine. Direct-on-line starts draw a large inrush for a second or two, while soft starters and drives ramp up gradually, so the right grace period differs per motor.
For drift, learn the normal loaded current from a period when the machine is known to be healthy and the product mix is typical. If the line runs several products with very different loads, learn one normal value per product or recipe and include the active recipe in the payload. Otherwise every product change will look like an anomaly and operators will learn to ignore the alerts, which defeats the purpose of monitoring.
Going to Production
The public broker is great for testing, but its topics are visible to everyone. For real motors:
- Create a private broker in the ConnectMQ portal and use the generated username, password and client ID.
- Switch to TLS on port 8883 with
WiFiClientSecure. - Use ISA-95 topics such as
acme/plant1/packaging/line1/conveyor1/currentso assets group naturally in the devices view. - Add a Last Will message so a dead sensor is reported as offline immediately.
A hosted MQTT broker keeps that infrastructure patched and available while you focus on the analytics. To see how current monitoring fits a full plant data architecture, read the smart manufacturing guide.
Monitor Your Motors with ConnectMQ
ConnectMQ runs your MQTT broker in the cloud with TLS, MQTT 5, a live topic explorer and threshold alerts built into the portal. Sign up at app.connectmq.com on the free Serverless plan, no credit card required, and connect your first CT clamp today.
Frequently Asked Questions
What can motor current monitoring detect?
It reliably detects running state, overloads, abnormal start frequency, load drift and phase imbalance. Detecting rotor bar or bearing defects from current requires high-speed sampling and spectral analysis.
Is a CT clamp safe to install on a running motor?
The clamp is non-invasive, but it is installed in a cabinet with live parts. A qualified electrician should install it with the cabinet isolated.
How often should I publish motor current readings?
Once per second is a good default for state and overload detection. Slower rates such as every 10 seconds work for pure trend monitoring.
Why does my reading show current when the motor is off?
That is the ADC noise floor. Suppress values below a small threshold in firmware and keep sensor wiring away from power cables.



