專案概述
為節能科技公司開發的智能電源監控插座,採用 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, ¤t_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 台北國際電腦展創新設計獎
- 🌍 出口至:台灣、日本、韓國、東南亞
創新亮點
- Matter 原生支援:無需橋接器,直接配對所有智慧家庭平台
- 高精度計量:採用工業級 PZEM-004T,計量精度達 Class 1 標準
- 智慧節能建議:AI 分析用電模式,提供個人化節能建議
- 用電歷史分析: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 協定、電力監測