Project Overview
A smart power monitoring socket developed for an energy-saving technology company, built on the ESP32-C3 RISC-V chip and integrating the PZEM-004T high-precision power metering module. The device delivers real-time power monitoring (error < 1%), remote on/off control, energy consumption history analysis, and anomaly alerts.
The product supports the latest Matter smart home standard, enabling seamless integration with Apple Home, Google Home, Amazon Alexa, SmartThings, and other platforms — no additional bridge required.
Over 100,000+ units sold, saving users an average of 15-20% on electricity bills.
Core Technical Challenges
1. High-Precision Power Metering
Challenge:
- Must simultaneously measure voltage, current, power, and power factor
- Measurement accuracy must meet Class 1 (error < 1%)
- Support load range: 5W - 3680W (16A@230V)
Solution — PZEM-004T Integration:
#include "driver/uart.h"
#include "esp_log.h"
#define TAG "PZEM004T"
// UART configuration (PZEM-004T uses 9600 baud, 8N1)
#define PZEM_UART_NUM UART_NUM_1
#define PZEM_TX_PIN GPIO_NUM_4
#define PZEM_RX_PIN GPIO_NUM_5
#define PZEM_BAUD_RATE 9600
// PZEM-004T Modbus RTU commands
#define PZEM_CMD_READ 0x04 // Read registers
#define PZEM_ADDR_VOLTAGE 0x0000 // Voltage register address
#define PZEM_ADDR_CURRENT 0x0001 // Current register address
#define PZEM_ADDR_POWER 0x0003 // Power register address
#define PZEM_ADDR_ENERGY 0x0005 // Energy register address
#define PZEM_ADDR_FREQUENCY 0x0007 // Frequency register address
#define PZEM_ADDR_PF 0x0008 // Power factor register address
typedef struct {
float voltage; // Voltage (V)
float current; // Current (A)
float power; // Power (W)
float energy; // Cumulative energy (kWh)
float frequency; // Frequency (Hz)
float power_factor; // Power factor (0.0-1.0)
uint32_t timestamp; // Timestamp
} pzem_data_t;
// Calculate CRC16 Modbus checksum
uint16_t calculate_crc16(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
// UART initialization
void pzem_uart_init(void) {
uart_config_t uart_config = {
.baud_rate = PZEM_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
uart_param_config(PZEM_UART_NUM, &uart_config);
uart_set_pin(PZEM_UART_NUM, PZEM_TX_PIN, PZEM_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(PZEM_UART_NUM, 256, 256, 0, NULL, 0);
ESP_LOGI(TAG, "PZEM-004T UART initialized");
}
// Read power parameters
bool pzem_read_data(pzem_data_t *data) {
// Modbus RTU read command
// Format: [Device Addr][Function][Reg Addr High][Reg Addr Low][Reg Count High][Reg Count Low][CRC Low][CRC High]
uint8_t cmd[8] = {
0x01, // Device address
PZEM_CMD_READ, // Function code: read holding registers
0x00, 0x00, // Starting register address (0x0000 = voltage)
0x00, 0x0A, // Read 10 registers (covers all parameters)
0x00, 0x00 // CRC16 (to be calculated)
};
// Calculate and append CRC16
uint16_t crc = calculate_crc16(cmd, 6);
cmd[6] = crc & 0xFF;
cmd[7] = (crc >> 8) & 0xFF;
// Send command
uart_write_bytes(PZEM_UART_NUM, cmd, sizeof(cmd));
// Receive response (wait up to 500ms)
uint8_t response[64];
int len = uart_read_bytes(PZEM_UART_NUM, response, sizeof(response), pdMS_TO_TICKS(500));
if (len < 25) { // Complete response is 25 bytes
ESP_LOGW(TAG, "PZEM response too short: %d bytes", len);
return false;
}
// Verify CRC
uint16_t received_crc = (response[len-1] << 8) | response[len-2];
uint16_t calculated_crc = calculate_crc16(response, len - 2);
if (received_crc != calculated_crc) {
ESP_LOGE(TAG, "CRC mismatch! Received: 0x%04X, Calculated: 0x%04X", received_crc, calculated_crc);
return false;
}
// Parse data (Modbus big-endian, 2 bytes per register)
data->voltage = ((response[3] << 8) | response[4]) / 10.0; // 0.1V precision
data->current = ((response[7] << 8) | response[8]) / 100.0; // 0.01A precision
data->power = ((response[11] << 8) | response[12]) / 10.0; // 0.1W precision
data->energy = ((response[15] << 8) | response[16]); // 1Wh precision
data->frequency = ((response[19] << 8) | response[20]) / 10.0; // 0.1Hz precision
data->power_factor = ((response[23] << 8) | response[24]) / 100.0; // 0.01 precision
data->timestamp = esp_timer_get_time() / 1000000; // Convert to seconds
ESP_LOGI(TAG, "V=%.1fV, I=%.2fA, P=%.1fW, E=%.2fkWh, PF=%.2f",
data->voltage, data->current, data->power, data->energy/1000.0, data->power_factor);
return true;
}
// Reset cumulative energy counter
bool pzem_reset_energy(void) {
uint8_t cmd[4] = {
0x01, // Device address
0x42, // Function code: reset energy
0x00, 0x00 // CRC (to be calculated)
};
uint16_t crc = calculate_crc16(cmd, 2);
cmd[2] = crc & 0xFF;
cmd[3] = (crc >> 8) & 0xFF;
uart_write_bytes(PZEM_UART_NUM, cmd, sizeof(cmd));
uint8_t response[4];
int len = uart_read_bytes(PZEM_UART_NUM, response, sizeof(response), pdMS_TO_TICKS(500));
return (len == 4 && response[1] == 0x42);
}
2. Relay Control and Safety Protection
16A High-Power Relay Control:
#include "driver/gpio.h"
#define RELAY_PIN GPIO_NUM_2
#define RELAY_ON 1
#define RELAY_OFF 0
// Overload protection parameters
#define MAX_CURRENT 16.0 // Maximum current 16A
#define MAX_POWER 3680.0 // Maximum power 3680W (230V * 16A)
#define OVERLOAD_DURATION 5000 // Overload duration of 5 seconds triggers protection
typedef struct {
bool relay_state;
bool overload_protection_enabled;
uint32_t overload_start_time;
bool overload_triggered;
float current_limit;
float power_limit;
} relay_controller_t;
relay_controller_t relay_ctrl = {
.relay_state = false,
.overload_protection_enabled = true,
.overload_triggered = false,
.current_limit = MAX_CURRENT,
.power_limit = MAX_POWER
};
void relay_init(void) {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << RELAY_PIN),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
// Initial state: off
gpio_set_level(RELAY_PIN, RELAY_OFF);
ESP_LOGI(TAG, "Relay initialized");
}
void relay_set_state(bool state) {
if (relay_ctrl.overload_triggered) {
ESP_LOGW(TAG, "Cannot turn on relay: overload protection triggered");
return;
}
gpio_set_level(RELAY_PIN, state ? RELAY_ON : RELAY_OFF);
relay_ctrl.relay_state = state;
ESP_LOGI(TAG, "Relay state: %s", state ? "ON" : "OFF");
}
bool relay_get_state(void) {
return relay_ctrl.relay_state;
}
// Overload protection check (called every second)
void relay_check_overload(pzem_data_t *pzem_data) {
if (!relay_ctrl.overload_protection_enabled) {
return;
}
bool overload = false;
// Check current overload
if (pzem_data->current > relay_ctrl.current_limit) {
ESP_LOGW(TAG, "Current overload: %.2fA > %.2fA", pzem_data->current, relay_ctrl.current_limit);
overload = true;
}
// Check power overload
if (pzem_data->power > relay_ctrl.power_limit) {
ESP_LOGW(TAG, "Power overload: %.1fW > %.1fW", pzem_data->power, relay_ctrl.power_limit);
overload = true;
}
if (overload) {
uint32_t now = esp_timer_get_time() / 1000; // ms
if (relay_ctrl.overload_start_time == 0) {
relay_ctrl.overload_start_time = now;
} else if ((now - relay_ctrl.overload_start_time) > OVERLOAD_DURATION) {
// Overload exceeded 5 seconds — trigger protection
ESP_LOGE(TAG, "Overload protection triggered! Turning off relay.");
relay_set_state(false);
relay_ctrl.overload_triggered = true;
// Send MQTT alert
mqtt_publish_alert("overload", pzem_data);
}
} else {
relay_ctrl.overload_start_time = 0;
}
}
// Reset overload protection (requires manual reset)
void relay_reset_overload_protection(void) {
relay_ctrl.overload_triggered = false;
relay_ctrl.overload_start_time = 0;
ESP_LOGI(TAG, "Overload protection reset");
}
3. Matter Smart Home Standard Support
ESP-Matter SDK Integration:
#include "esp_matter.h"
#include "esp_matter_console.h"
#include "esp_matter_ota.h"
using namespace esp_matter;
using namespace esp_matter::endpoint;
static const char *TAG = "MATTER_SOCKET";
// Matter node handle
static node_t *node = NULL;
static endpoint_t *endpoint = NULL;
// On/Off socket endpoint configuration
void matter_socket_init(void) {
// Create Matter node
node::config_t node_config;
node = node::create(&node_config, NULL, NULL);
// Create socket endpoint (On/Off Plug-in Unit)
on_off_plugin_unit::config_t socket_config;
socket_config.on_off.on_off = false;
socket_config.on_off.lighting.start_up_on_off = nullptr;
endpoint = on_off_plugin_unit::create(node, &socket_config, ENDPOINT_FLAG_NONE, NULL);
// Add power monitoring cluster (new in Matter 1.2)
cluster_t *power_cluster = cluster::create(endpoint, ElectricalPowerMeasurement::Id, CLUSTER_FLAG_SERVER);
// Voltage attribute
attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::Voltage::Id,
ATTRIBUTE_FLAG_NONE, esp_matter_uint16(0));
// Current attribute
attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::ActiveCurrent::Id,
ATTRIBUTE_FLAG_NONE, esp_matter_uint16(0));
// Power attribute
attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::ActivePower::Id,
ATTRIBUTE_FLAG_NONE, esp_matter_int16(0));
// Cumulative energy attribute
attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::CumulativeEnergyImported::Id,
ATTRIBUTE_FLAG_NONE, esp_matter_uint64(0));
ESP_LOGI(TAG, "Matter socket endpoint created");
}
// Matter attribute change callback
esp_err_t matter_attribute_update_cb(attribute::callback_type_t type, uint16_t endpoint_id,
uint32_t cluster_id, uint32_t attribute_id,
esp_matter_attr_val_t *val, void *priv_data)
{
if (type == attribute::PRE_UPDATE) {
// On/Off state change
if (cluster_id == OnOff::Id && attribute_id == OnOff::Attributes::OnOff::Id) {
bool new_state = val->val.b;
ESP_LOGI(TAG, "Matter command: Turn %s", new_state ? "ON" : "OFF");
// Control relay
relay_set_state(new_state);
}
}
return ESP_OK;
}
// Update Matter power attributes
void matter_update_power_attributes(pzem_data_t *pzem_data) {
if (!endpoint) return;
uint16_t endpoint_id = endpoint::get_id(endpoint);
// Update voltage (unit: 0.1V)
esp_matter_attr_val_t voltage_val = esp_matter_uint16((uint16_t)(pzem_data->voltage * 10));
attribute::update(endpoint_id, ElectricalPowerMeasurement::Id,
ElectricalPowerMeasurement::Attributes::Voltage::Id, &voltage_val);
// Update current (unit: mA)
esp_matter_attr_val_t current_val = esp_matter_uint16((uint16_t)(pzem_data->current * 1000));
attribute::update(endpoint_id, ElectricalPowerMeasurement::Id,
ElectricalPowerMeasurement::Attributes::ActiveCurrent::Id, ¤t_val);
// Update power (unit: mW)
esp_matter_attr_val_t power_val = esp_matter_int16((int16_t)(pzem_data->power * 1000));
attribute::update(endpoint_id, ElectricalPowerMeasurement::Id,
ElectricalPowerMeasurement::Attributes::ActivePower::Id, &power_val);
// Update cumulative energy (unit: mWh)
esp_matter_attr_val_t energy_val = esp_matter_uint64((uint64_t)(pzem_data->energy * 1000));
attribute::update(endpoint_id, ElectricalPowerMeasurement::Id,
ElectricalPowerMeasurement::Attributes::CumulativeEnergyImported::Id, &energy_val);
}
// Generate Matter pairing QR code
void matter_print_onboarding_info(void) {
ESP_LOGI(TAG, "Matter onboarding information:");
ESP_LOGI(TAG, " Manual pairing code: 34970112332");
ESP_LOGI(TAG, " QRCode URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=MT%3AY.K9042C00KA0648G00");
ESP_LOGI(TAG, "Scan the QR code with your Matter controller (Apple Home / Google Home / SmartThings)");
}
4. Energy Consumption Data Analysis and Visualization
InfluxDB + Grafana Integration:
// Node.js data collection service
const mqtt = require('mqtt');
const { InfluxDB, Point } = require('@influxdata/influxdb-client');
class PowerDataCollector {
constructor() {
// MQTT connection
this.mqttClient = mqtt.connect('mqtt://localhost:1883');
// InfluxDB connection
this.influxDB = new InfluxDB({
url: 'http://localhost:8086',
token: 'your-influxdb-token'
});
this.writeApi = this.influxDB.getWriteApi('smart-home', 'power-monitoring');
this.initMQTT();
}
initMQTT() {
this.mqttClient.on('connect', () => {
console.log('Connected to MQTT broker');
this.mqttClient.subscribe('smarthome/socket/+/power');
});
this.mqttClient.on('message', (topic, message) => {
const data = JSON.parse(message.toString());
const socketId = topic.split('/')[2];
// Write to InfluxDB
this.writePowerData(socketId, data);
});
}
writePowerData(socketId, data) {
const point = new Point('power_measurement')
.tag('socket_id', socketId)
.tag('location', data.location || 'unknown')
.floatField('voltage', data.voltage)
.floatField('current', data.current)
.floatField('power', data.power)
.floatField('energy', data.energy)
.floatField('power_factor', data.power_factor)
.timestamp(new Date());
this.writeApi.writePoint(point);
this.writeApi.flush();
}
// Calculate electricity bill (Taiwan Power tiered rates)
async calculateElectricityBill(socketId, startDate, endDate) {
const queryApi = this.influxDB.getQueryApi('smart-home');
const fluxQuery = `
from(bucket: "power-monitoring")
|> range(start: ${startDate.toISOString()}, stop: ${endDate.toISOString()})
|> filter(fn: (r) => r._measurement == "power_measurement")
|> filter(fn: (r) => r.socket_id == "${socketId}")
|> filter(fn: (r) => r._field == "energy")
|> last()
`;
return new Promise((resolve, reject) => {
let totalEnergy = 0;
queryApi.queryRows(fluxQuery, {
next(row, tableMeta) {
const o = tableMeta.toObject(row);
totalEnergy = o._value;
},
error(error) {
reject(error);
},
complete() {
// Taiwan Power tiered rate calculation (summer)
const bill = calculateTaiwanPowerBill(totalEnergy);
resolve({ energy: totalEnergy, bill });
}
});
});
}
}
// Taiwan Power tiered rate calculation (2024 summer rates)
function calculateTaiwanPowerBill(kwh) {
const rates = [
{ limit: 120, rate: 1.63 }, // Up to 120 kWh
{ limit: 330, rate: 2.38 }, // 121-330 kWh
{ limit: 500, rate: 3.52 }, // 331-500 kWh
{ limit: 700, rate: 4.80 }, // 501-700 kWh
{ limit: 1000, rate: 5.66 }, // 701-1000 kWh
{ limit: Infinity, rate: 6.41 } // 1001+ kWh
];
let bill = 0;
let remaining = kwh;
for (let i = 0; i < rates.length; i++) {
const tier = rates[i];
const prevLimit = i > 0 ? rates[i - 1].limit : 0;
const tierKwh = Math.min(remaining, tier.limit - prevLimit);
if (tierKwh > 0) {
bill += tierKwh * tier.rate;
remaining -= tierKwh;
}
if (remaining <= 0) break;
}
return Math.round(bill);
}
module.exports = PowerDataCollector;
Project Results
Technical Metrics
- Power metering accuracy: Class 1 (error < 1%)
- Measurement range: 5W - 3680W (0-16A)
- Relay lifespan: 100,000 switching cycles
- Matter certification: Passed CSA Matter 1.2 certification
- Data update rate: 1 second (adjustable to 0.5 seconds)
- WiFi connection stability: 99.5%
Business Results
- Mass production sales: 100,000+ units
- Average energy savings: 15-20% reduction in electricity bills
- User rating: 4.7/5.0 (e-commerce platforms)
- Awarded 2024 Computex Innovation Design Award
- Exported to: Taiwan, Japan, South Korea, Southeast Asia
Innovation Highlights
- Native Matter support: Direct pairing with all smart home platforms — no bridge required
- High-precision metering: Industrial-grade PZEM-004T achieving Class 1 measurement accuracy
- Smart energy-saving recommendations: AI analyzes usage patterns and provides personalized energy-saving tips
- Energy consumption history analysis: InfluxDB + Grafana delivering professional-grade data visualization
Technology Stack
Hardware Platform:
- ESP32-C3 (RISC-V 160MHz)
- PZEM-004T (power metering module)
- 16A relay (HF115F-012-1ZS4)
- HLK-PM03 (AC-DC power module)
Firmware Development:
- ESP-IDF 5.1
- ESP-Matter SDK
- Modbus RTU protocol
Backend Services:
- Node.js + Express
- InfluxDB (time-series database)
- MQTT Broker
Frontend Applications:
- React.js (web interface)
- Grafana (data visualization)
- iOS / Android app
Smart Home Integration:
- Matter 1.2
- Thread (low-power mesh network)
- Apple HomeKit
- Google Home
- Amazon Alexa
Client Testimonial
"The smart socket developed by BASHCAT has completely transformed our product line! Matter standard support allows us to be compatible with all major platforms at once, significantly reducing development costs. The power metering precision gives us a strong competitive edge in the market. Most importantly, their firmware is extremely stable — the return rate is under 0.3%."
— Product Manager, Energy-Saving Technology Company
Project Duration: June 2023 - November 2023 Technical Domains: IoT, Smart Home, Matter Protocol, Power Monitoring