Embedded·2024

Smart Power Monitoring Socket

A smart socket with real-time power monitoring, remote on/off control, and abnormal power consumption alerts — integrated with the Matter protocol for compatibility with all major smart home platforms

clientEnergy-Saving Technology Company
duration6 months
categoryEmbedded
stack
ESP32-C3PZEM-004TMQTTInfluxDBGrafanaReactMatterThread

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, &current_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

  1. Native Matter support: Direct pairing with all smart home platforms — no bridge required
  2. High-precision metering: Industrial-grade PZEM-004T achieving Class 1 measurement accuracy
  3. Smart energy-saving recommendations: AI analyzes usage patterns and provides personalized energy-saving tips
  4. 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

$ ls projects/embedded/

More work in Embedded.