返回作品集
嵌入式系統精選專案

智能電源監控插座

支援即時功耗監測、遠端開關控制、異常用電告警的智慧插座,整合 Matter 協定,相容所有主流智慧家庭平台

使用技術

ESP32-C3PZEM-004TMQTTInfluxDBGrafanaReactMatterThread

專案詳情

客戶節能科技公司
開發時程6個月

專案概述

為節能科技公司開發的智能電源監控插座,採用 ESP32-C3 RISC-V 晶片,整合 PZEM-004T 高精度電力計量模組,實現即時功率監測(誤差 < 1%)遠端開關控制用電歷史分析異常告警等功能。

產品支援最新 Matter 智慧家庭標準,可無縫整合 Apple Home、Google Home、Amazon Alexa、SmartThings 等平台,無需額外橋接器。

已量產銷售 100,000+ 台,平均為用戶節省 15-20% 電費支出。

核心技術挑戰

1. 高精度電力計量

挑戰

  • 需要同時量測電壓、電流、功率、功率因數
  • 量測精度需達 Class 1(誤差 < 1%)
  • 支援負載範圍:5W - 3680W(16A@230V)

解決方案 - PZEM-004T 整合

#include "driver/uart.h"
#include "esp_log.h"

#define TAG "PZEM004T"

// UART 配置(PZEM-004T 使用 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 指令
#define PZEM_CMD_READ       0x04    // 讀取暫存器
#define PZEM_ADDR_VOLTAGE   0x0000  // 電壓暫存器地址
#define PZEM_ADDR_CURRENT   0x0001  // 電流暫存器地址
#define PZEM_ADDR_POWER     0x0003  // 功率暫存器地址
#define PZEM_ADDR_ENERGY    0x0005  // 電量暫存器地址
#define PZEM_ADDR_FREQUENCY 0x0007  // 頻率暫存器地址
#define PZEM_ADDR_PF        0x0008  // 功率因數暫存器地址

typedef struct {
    float voltage;       // 電壓 (V)
    float current;       // 電流 (A)
    float power;         // 功率 (W)
    float energy;        // 累計電量 (kWh)
    float frequency;     // 頻率 (Hz)
    float power_factor;  // 功率因數 (0.0-1.0)
    uint32_t timestamp;  // 時間戳記
} pzem_data_t;

// 計算 CRC16 Modbus 校驗碼
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 初始化
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");
}

// 讀取電力參數
bool pzem_read_data(pzem_data_t *data) {
    // Modbus RTU 讀取指令
    // 格式: [Device Addr][Function][Reg Addr High][Reg Addr Low][Reg Count High][Reg Count Low][CRC Low][CRC High]
    uint8_t cmd[8] = {
        0x01,           // 設備地址
        PZEM_CMD_READ,  // 功能碼:讀取保持暫存器
        0x00, 0x00,     // 起始暫存器地址(0x0000 = 電壓)
        0x00, 0x0A,     // 讀取 10 個暫存器(涵蓋所有參數)
        0x00, 0x00      // CRC16(待計算)
    };

    // 計算並添加 CRC16
    uint16_t crc = calculate_crc16(cmd, 6);
    cmd[6] = crc & 0xFF;
    cmd[7] = (crc >> 8) & 0xFF;

    // 發送指令
    uart_write_bytes(PZEM_UART_NUM, cmd, sizeof(cmd));

    // 接收回應(最多等待 500ms)
    uint8_t response[64];
    int len = uart_read_bytes(PZEM_UART_NUM, response, sizeof(response), pdMS_TO_TICKS(500));

    if (len < 25) {  // 完整回應為 25 bytes
        ESP_LOGW(TAG, "PZEM response too short: %d bytes", len);
        return false;
    }

    // 驗證 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;
    }

    // 解析數據(Modbus 大端序,2 bytes per register)
    data->voltage = ((response[3] << 8) | response[4]) / 10.0;  // 0.1V 精度
    data->current = ((response[7] << 8) | response[8]) / 100.0; // 0.01A 精度
    data->power = ((response[11] << 8) | response[12]) / 10.0;  // 0.1W 精度
    data->energy = ((response[15] << 8) | response[16]);        // 1Wh 精度
    data->frequency = ((response[19] << 8) | response[20]) / 10.0; // 0.1Hz 精度
    data->power_factor = ((response[23] << 8) | response[24]) / 100.0; // 0.01 精度
    data->timestamp = esp_timer_get_time() / 1000000;  // 轉換為秒

    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;
}

// 重置累計電量
bool pzem_reset_energy(void) {
    uint8_t cmd[4] = {
        0x01,  // 設備地址
        0x42,  // 功能碼:重置電量
        0x00, 0x00  // CRC(待計算)
    };

    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. 繼電器控制與安全保護

16A 高功率繼電器控制

#include "driver/gpio.h"

#define RELAY_PIN           GPIO_NUM_2
#define RELAY_ON            1
#define RELAY_OFF           0

// 過載保護參數
#define MAX_CURRENT         16.0    // 最大電流 16A
#define MAX_POWER           3680.0  // 最大功率 3680W (230V * 16A)
#define OVERLOAD_DURATION   5000    // 過載持續時間 5 秒觸發保護

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);

    // 初始狀態:關閉
    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;
}

// 過載保護檢查(每秒調用)
void relay_check_overload(pzem_data_t *pzem_data) {
    if (!relay_ctrl.overload_protection_enabled) {
        return;
    }

    bool overload = false;

    // 檢查電流過載
    if (pzem_data->current > relay_ctrl.current_limit) {
        ESP_LOGW(TAG, "Current overload: %.2fA > %.2fA", pzem_data->current, relay_ctrl.current_limit);
        overload = true;
    }

    // 檢查功率過載
    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) {
            // 過載超過 5 秒,觸發保護
            ESP_LOGE(TAG, "Overload protection triggered! Turning off relay.");
            relay_set_state(false);
            relay_ctrl.overload_triggered = true;

            // 發送 MQTT 告警
            mqtt_publish_alert("overload", pzem_data);
        }
    } else {
        relay_ctrl.overload_start_time = 0;
    }
}

// 重置過載保護(需手動解除)
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 智慧家庭標準支援

ESP-Matter SDK 整合

#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 節點句柄
static node_t *node = NULL;
static endpoint_t *endpoint = NULL;

// On/Off 插座端點配置
void matter_socket_init(void) {
    // 創建 Matter 節點
    node::config_t node_config;
    node = node::create(&node_config, NULL, NULL);

    // 創建插座端點(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);

    // 添加電力監測 Cluster(Matter 1.2 新增)
    cluster_t *power_cluster = cluster::create(endpoint, ElectricalPowerMeasurement::Id, CLUSTER_FLAG_SERVER);

    // 電壓屬性
    attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::Voltage::Id,
                     ATTRIBUTE_FLAG_NONE, esp_matter_uint16(0));

    // 電流屬性
    attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::ActiveCurrent::Id,
                     ATTRIBUTE_FLAG_NONE, esp_matter_uint16(0));

    // 功率屬性
    attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::ActivePower::Id,
                     ATTRIBUTE_FLAG_NONE, esp_matter_int16(0));

    // 累計電量屬性
    attribute::create(power_cluster, ElectricalPowerMeasurement::Attributes::CumulativeEnergyImported::Id,
                     ATTRIBUTE_FLAG_NONE, esp_matter_uint64(0));

    ESP_LOGI(TAG, "Matter socket endpoint created");
}

// Matter 屬性變更回調
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 狀態變更
        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");

            // 控制繼電器
            relay_set_state(new_state);
        }
    }

    return ESP_OK;
}

// 更新 Matter 電力屬性
void matter_update_power_attributes(pzem_data_t *pzem_data) {
    if (!endpoint) return;

    uint16_t endpoint_id = endpoint::get_id(endpoint);

    // 更新電壓(單位: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);

    // 更新電流(單位: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);

    // 更新功率(單位: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);

    // 更新累計電量(單位: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);
}

// Matter 配對 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. 用電數據分析與視覺化

InfluxDB + Grafana 整合

// Node.js 數據收集服務
const mqtt = require('mqtt');
const { InfluxDB, Point } = require('@influxdata/influxdb-client');

class PowerDataCollector {
    constructor() {
        // MQTT 連線
        this.mqttClient = mqtt.connect('mqtt://localhost:1883');

        // InfluxDB 連線
        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];

            // 寫入 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();
    }

    // 計算電費(台電累進費率)
    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() {
                    // 台電累進費率計算(夏月)
                    const bill = calculateTaiwanPowerBill(totalEnergy);
                    resolve({ energy: totalEnergy, bill });
                }
            });
        });
    }
}

// 台電累進費率計算(2024 夏月費率)
function calculateTaiwanPowerBill(kwh) {
    const rates = [
        { limit: 120, rate: 1.63 },    // 120度以下
        { limit: 330, rate: 2.38 },    // 121-330度
        { limit: 500, rate: 3.52 },    // 331-500度
        { limit: 700, rate: 4.80 },    // 501-700度
        { limit: 1000, rate: 5.66 },   // 701-1000度
        { limit: Infinity, rate: 6.41 } // 1001度以上
    ];

    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;

專案成果

技術指標

  • 電力計量精度:Class 1(誤差 < 1%)
  • 量測範圍:5W - 3680W(0-16A)
  • 繼電器壽命:100,000 次切換
  • Matter 認證:通過 CSA Matter 1.2 認證
  • 數據更新率:1秒(可調整至 0.5秒)
  • WiFi 連線穩定性:99.5%

商業成果

  • 📦 量產銷售:100,000+
  • 💰 平均節電效果:15-20% 電費支出
  • ⭐ 用戶評分:4.7/5.0(電商平台)
  • 🏆 獲得 2024 台北國際電腦展創新設計獎
  • 🌍 出口至:台灣、日本、韓國、東南亞

創新亮點

  1. Matter 原生支援:無需橋接器,直接配對所有智慧家庭平台
  2. 高精度計量:採用工業級 PZEM-004T,計量精度達 Class 1 標準
  3. 智慧節能建議:AI 分析用電模式,提供個人化節能建議
  4. 用電歷史分析:InfluxDB + Grafana 提供專業級數據視覺化

技術棧

硬體平台

  • ESP32-C3(RISC-V 160MHz)
  • PZEM-004T(電力計量模組)
  • 16A 繼電器(HF115F-012-1ZS4)
  • HLK-PM03(AC-DC 電源模組)

韌體開發

  • ESP-IDF 5.1
  • ESP-Matter SDK
  • Modbus RTU 協定

後端服務

  • Node.js + Express
  • InfluxDB(時序資料庫)
  • MQTT Broker

前端應用

  • React.js(Web 介面)
  • Grafana(數據視覺化)
  • iOS / Android APP

智慧家庭整合

  • Matter 1.2
  • Thread(低功耗網狀網路)
  • Apple HomeKit
  • Google Home
  • Amazon Alexa

客戶回饋

"BASHCAT 開發的智能插座完全改變了我們的產品線!Matter 標準的支援讓我們一次性相容所有主流平台,大幅降低了開發成本。電力計量的精準度也讓我們在市場上極具競爭力。最重要的是,他們的韌體非常穩定,退貨率不到 0.3%。"

產品經理,節能科技公司


專案時間:2023年6月 - 2023年11月 技術領域:物聯網、智慧家庭、Matter 協定、電力監測

相關專案

探索更多 嵌入式系統 領域的技術專案

更多 嵌入式系統 專案

即將推出更多相關技術專案

查看全部專案