基于光伏发电的智能家居能耗控制:Shelly EM与Node.js实现
1. 项目概述与核心价值
家里装了太阳能光伏板的朋友,估计都琢磨过同一个问题:白天发的电用不完,眼睁睁看着它卖回给电网,价格可能只有自用电价的一半甚至更低;晚上没太阳了,又要从电网买高价电来用。这种“发电时用不完,用电时不够发”的错配,让光伏系统的经济性大打折扣。我自己家装了8千瓦的光伏系统后,就一直在想,能不能让那些“可调节”的大功率电器——比如电热水器、充电桩、蓄热电暖器——只在光伏发电有富余的时候工作,实现真正的“自发自用,余电不上网”?
这就是今天要聊的“基于太阳能发电的智能家居能耗控制”方案的核心思路。它不是什么高深莫测的黑科技,而是一套非常务实、可落地的自动化系统。其核心逻辑很简单: 实时比较“光伏发电功率”和“家庭实时用电功率”,当发电有足够富余时,自动开启预设的大功率电器,消耗掉这部分多余的电能;当发电不足或家庭用电激增时,则自动关闭该电器,避免从电网购电。
我选择的硬件核心是 Shelly EM 这款智能电能监测模块。它自带两个电流互感器(CT钳),可以同时、独立地测量两路电路的实时功率,完美契合我们“一路测全家用电,一路测光伏发电”的需求。软件逻辑部分,则用 Node.js 写一个轻量级的后台服务,定期从 Shelly EM 读取数据,进行计算判断,并通过网络控制一个智能开关(如 Shelly 1)来操作电器。
这套方案特别适合有一定动手能力、希望最大化光伏自发自用率、降低电费的家庭用户或极客。它不依赖复杂的家庭自动化中枢(如 Home Assistant),直接通过简单的脚本实现核心逻辑,稳定且响应迅速。接下来,我将从硬件接线、数据获取、逻辑编写到部署优化的全流程,拆解每一个步骤和背后的原理。
2. 系统架构与硬件选型解析
2.1 整体工作流程设计
在动手之前,我们需要把整个系统的工作流想清楚。一个健壮的自动化系统,其流程必须是清晰且闭环的。
- 数据采集 :Shelly EM 通过两个CT钳,分别钳在家庭总进线和光伏逆变器输出线上,以每秒数次的频率测量实时功率(单位:瓦特),并通过Wi-Fi将数据上传至本地网络或Shelly云。
- 数据获取与计算 :Node.js 服务以固定时间间隔(例如每60秒)通过HTTP请求,从 Shelly EM 的API接口获取最新的功率数据。计算逻辑为:
可用富余功率 = 光伏发电功率 - 家庭总用电功率。 - 逻辑判断与执行 :
- 如果
可用富余功率 > 目标电器额定功率 + 安全阈值,则向智能开关发送“开启”指令。 - 如果
可用富余功率 < 0(即家庭用电已超过光伏发电,开始从电网取电),则向智能开关发送“关闭”指令。 - 如果富余功率介于0和目标电器功率之间,则保持开关现有状态不变。
- 如果
- 设备控制 :智能开关(如 Shelly 1)接收到指令后,控制其继电器吸合或断开,从而接通或切断目标电器的电源。
这个流程的核心在于 “可用富余功率” 的计算。这里有一个关键点:Shelly EM 测量功率时是有方向的。通常我们将“用电”定义为正功率,“发电”定义为负功率。所以,如果光伏板正在发电,其测量值会是一个负数。在我们的计算中,需要将其转换为正数来表示“发电量”。
2.2 硬件清单与选型考量
核心硬件:Shelly EM
- 功能 :双通道电能监测,支持实时功率、电压、电流、电量统计,Wi-Fi连接,提供本地和云端API。
- 选型理由 :相比单通道的电能监测模块,双通道的Shelly EM可以同时独立监测两路电流,无需额外设备,接线和逻辑都更简洁。其API文档完善,社区支持好,是完成此项目的性价比之选。
- 关键配件 :务必确认包装内包含 两个电流互感器(CT钳) 。根据你家总进线和逆变器输出线的电流大小选择合适量程,家庭用户一般选择 50A 或 100A 的规格足够。我的进线是63A空开,使用50A的CT钳完全没问题,但要注意留有余量。
执行硬件:Wi-Fi智能开关
- 选项1(推荐) : Shelly 1 或 Shelly 1PM 。1PM自带功率测量,可以额外验证被控电器的实际功耗,但本项目不是必须。选择Shelly系列的好处是生态统一,控制API类似。
- 选项2 :其他支持HTTP/API控制的智能插座或开关,如Sonoff Basic(需刷Tasmota固件)、TP-Link Kasa系列等。你需要事先查明其远程控制的URL地址。
- 安全警告 :被控电器功率必须 严格小于 智能开关继电器的额定功率(Shelly 1是16A)。控制电热水器、电暖器等大功率设备前,务必核对电器铭牌上的额定电流和功率。
辅助硬件:
- 网络 :稳定的2.4GHz Wi-Fi网络。Shelly设备对5GHz支持不佳,务必确保安装位置2.4GHz信号强度良好。
- 供电 :为Shelly EM和Shelly 1提供220V电源。通常可以从被控电器所在的插座或配电箱取电。
- 安装工具 :螺丝刀、电工胶布、可能需要的线缆等。
注意:电气安全是第一位的!所有接线操作必须在断开总闸或相应回路空开的前提下进行。如果你对家庭电路不熟悉,强烈建议请专业电工协助完成硬件安装部分。
3. 硬件安装与Shelly EM配置详解
3.1 电路接线与CT钳安装
这是整个项目最具实操性也最需要谨慎的一步。错误的接线可能导致数据错误,甚至设备损坏。
第一步:断电与准备 关闭家庭配电箱的总开关,用验电笔确认无电后再操作。规划好Shelly EM的安装位置,最好靠近配电箱,方便取电和布线。
第二步:为Shelly EM供电 找到一条稳定的220V电源线(可以从配电箱的一个空闲空开下引,或从附近一个常通电的插座引),将其 火线(L) 接入Shelly EM的 L 端子, 零线(N) 接入 N 端子。这一步是给设备本身供电。
第三步:安装CT钳1(监测家庭总用电)
- 目标线路 :找到从电网接入你家电表,再进入你家配电箱总开关的那根 火线 。通常是一根较粗的导线。
- 安装方法 :将第一个CT钳(对应Shelly EM的 P1 通道)套在这根火线上。CT钳上通常有一个箭头标识, 箭头方向应指向用电负载(即指向你家的配电箱) 。这是保证功率测量符号正确的关键。
- 接线 :将CT钳的引出线(两根细线)连接到Shelly EM上标有 P1+ 和 P1- 的端子。通常不分正负,但如果后续数据符号不对,可以交换这两根线。
第四步:安装CT钳2(监测光伏发电)
- 目标线路 :找到从光伏逆变器交流输出端接入你家配电箱的那根 火线 。
- 安装方法 :将第二个CT钳(对应 P2 通道)套在这根线上。此处的 箭头方向应指向电网方向(即从逆变器指向配电箱/电网) 。这是因为从逆变器的角度看,它是在向电网/家庭负载“输出”电能。
- 接线 :将CT钳引出线接入Shelly EM的 P2+ 和 P2- 端子。
重要概念澄清 :有朋友问CT钳是夹在直流侧(太阳能板到逆变器)还是交流侧(逆变器输出)。 必须夹在交流侧! 因为Shelly EM只能测量交流电参数。我们监测的是逆变器之后、送入家庭电网的“可用交流电能”。
第五步:安装执���器Shelly 1 将Shelly 1串联到你要控制的电器(如电暖器)的供电回路中。即:墙壁插座的火线 -> Shelly 1的 L 端子(供电入);Shelly 1的 O 端子(开关出) -> 电暖器插头的火线;零线直接贯通。为Shelly 1本身供电的 N 端子也需要接上零线。确保电暖器本身的物理开关处于打开状态,其通断将由Shelly 1的继电器控制。
3.2 Shelly设备网络配置与数据验证
所有硬件接好后,闭合总开关上电。
- 手机App配置 :下载“Shelly”官方App。按照提示,将Shelly EM和Shelly 1分别接入你的Wi-Fi网络。建议为它们设置静态IP或在路由器中做DHCP保留,方便Node.js服务稳定访问。
- 验证数据方向 :打开Shelly App,查看Shelly EM的设备页面。你应该能看到两个通道的实时功率。
- 理想情况 :当家里有负载用电,光伏不发电时(比如晚上),P1(家庭用电)显示为正功率(如+500W),P2(光伏)显示为0或接近0。
- 当光伏发电且发电量大于家庭用电时,P1可能显示一个较小的正数(家庭基础用电),而 P2应显示为一个负数(如-3000W) 。这个 负数 正是我们需要的“光伏发电功率”。
- 如果符号相反 :如果P2显示为正数,说明CT钳2的箭头方向反了或者接线反了。最安全的校正方法是 在断电后,将CT钳2的物理方向旋转180度 (即箭头指向反方向),这比交换接线端子更可靠。
- 记录关键参数 :在Shelly App中,进入Shelly EM的设备设置,找到“设备信息”,记录下:
- 设备ID :一串字母数字组合,如
AABBCCDDEEFF。 - 设备通道 :通常是
0。 - 云端访问密钥 :在App的“用户设置”里,可以生成一个“API密钥”(Auth Key)。同时注意你的 服务器地址 ,如果你使用Shelly云服务,通常是
https://shelly-XX-eu.shelly.cloud(XX代表地区编号)。 更推荐使用本地局域网API ,地址为http://[Shelly-EM的本地IP],这样速度更快,不依赖外网。
- 设备ID :一串字母数字组合,如
4. Node.js控制逻辑实现与代码精讲
硬件就绪后,我们开始编写“大脑”——Node.js脚本。我将用一个增强版的脚本来演示,它包含错误处理、状态防抖和本地API调用。
4.1 项目初始化与依赖
首先,确保你的电脑或服务器(比如树莓派)上安装了Node.js。新建一个项目目录并初始化:
mkdir solar-auto-control && cd solar-auto-control
npm init -y
我们主要使用 node-fetch 库来发起HTTP请求。安装它:
npm install node-fetch
4.2 核心控制脚本详解
创建一个名为 solar-controller.js 的文件。下面我逐段解释代码:
// 引入 fetch 库,用于调用API
import fetch from 'node-fetch';
// ============ 配置区域 ============
// 方式一:使用Shelly本地API(推荐,速度快,不依赖互联网)
const SHELLY_EM_IP = '192.168.1.100'; // 替换为你的Shelly EM本地IP
const SHELLY_EM_URL = `http://${SHELLY_EM_IP}/rpc`;
// 方式二:使用Shelly云端API(备用,需要外网)
// const SHELLY_CLOUD_SERVER = 'https://shelly-XX-eu.shelly.cloud';
// const SHELLY_AUTH_KEY = '你的云端AuthKey';
const SHELLY_EM_ID = '你的设备ID'; // 从App中获取
const SHELLY_EM_CHANNEL = 0; // 通常是0
// 智能开关的控制URL(以Shelly 1为例)
const SWITCH_ON_URL = 'http://192.168.1.101/relay/0?turn=on'; // 替换为你的Shelly 1 IP
const SWITCH_OFF_URL = 'http://192.168.1.101/relay/0?turn=off';
// 被控电器的额定功率(瓦特),务必填写准确,并考虑启动电流
const DEVICE_RATED_POWER = 2000; // 例如:2000W 的电暖器
// 动作迟滞阈值,防止在临界点频繁开关
const HYSTERESIS_THRESHOLD = 100; // 单位:瓦特
// 检查间隔(毫秒)
const CHECK_INTERVAL_MS = 60 * 1000; // 60秒
// 全局状态记录,用于防抖和日志
let lastSwitchState = 'unknown'; // 'on', 'off', 'unknown'
// ============ 配置结束 ============
配置要点解析 :
- 本地API vs 云端API :优先使用本地IP访问。你可以在路由器管理页面或Shelly App中查看设备的IP地址。本地访问延迟极低(毫秒级),且断网也能工作。
- 设备功率 :
DEVICE_RATED_POWER务必填写电器铭牌上的 额定功率 。对于电热类设备,运行功率通常很稳定。对于电机类(如空调压缩机),启动功率可能数倍于额定功率,本项目逻辑可能不适用,需特别谨慎。 - 迟滞阈值 :
HYSTERESIS_THRESHOLD非常重要。假设没有迟滞,当富余功率在2000W上下轻微波动时,开关会在一分钟内反复动作。设置一个100W的迟滞,意味着:开启条件变为富余功率 > (2000 + 100)W,关闭条件变为富余功率 < 0W。这样就形成了一个“缓冲区”,避免了频繁开关,保护电器和继电器。
/**
* 控制智能开关的函数
* @param {string} action - 'on' 或 'off'
*/
async function controlSwitch(action) {
const url = action === 'on' ? SWITCH_ON_URL : SWITCH_OFF_URL;
try {
const response = await fetch(url);
const text = await response.text();
console.log(`[${new Date().toLocaleTimeString()}] 开关 ${action} 指令已发送。响应: ${text}`);
lastSwitchState = action;
} catch (error) {
console.error(`[${new Date().toLocaleTimeString()}] 控制开关时出错:`, error.message);
}
}
/**
* 从Shelly EM获取功率数据(使用本地RPC API)
*/
async function getPowerDataFromLocal() {
try {
// Shelly EM的RPC调用格式
const requestBody = {
id: 1,
method: "EM.GetStatus",
params: { id: SHELLY_EM_CHANNEL }
};
const response = await fetch(SHELLY_EM_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Shelly EM返回的数据结构
const emeters = data.result.emeters;
// 通道0:总用电(通常为正)
const homeConsumption = emeters[0].power; // 单位:瓦特
// 通道1:光伏发电(发电时为负值)
const solarProduction = -emeters[1].power; // 转换为正数表示发电量
return {
home: homeConsumption,
solar: solarProduction,
success: true
};
} catch (error) {
console.error(`[${new Date().toLocaleTimeString()}] 从Shelly EM获取数据失败:`, error.message);
return { home: 0, solar: 0, success: false };
}
}
/**
* 核心决策逻辑
*/
async function checkAndControl() {
console.log(`\n[${new Date().toLocaleTimeString()}] 开始执行检查...`);
const powerData = await getPowerDataFromLocal();
if (!powerData.success) {
console.log(' 数据获取失败,本次跳过。');
return;
}
const { home, solar } = powerData;
const availableSurplus = solar - home; // 可用富余功率
console.log(` 家庭实时用电: ${home.toFixed(1)} W`);
console.log(` 光伏实时发电: ${solar.toFixed(1)} W`);
console.log(` 可用富余功率: ${availableSurplus.toFixed(1)} W`);
console.log(` 电器额定功率: ${DEVICE_RATED_POWER} W (迟滞阈值: ${HYSTERESIS_THRESHOLD} W)`);
console.log(` 开关上次状态: ${lastSwitchState}`);
// 决策逻辑
if (availableSurplus < 0) {
// 情况1:用电大于发电,正在从电网买电
console.log(` 决策: 可用功率为负,正在使用电网电。`);
if (lastSwitchState !== 'off') {
console.log(` 执行: 关闭电器。`);
await controlSwitch('off');
} else {
console.log(` 执行: 状态未变(已关闭),无需操作。`);
}
} else if (availableSurplus > (DEVICE_RATED_POWER + HYSTERESIS_THRESHOLD)) {
// 情况2:富余功率足够大(考虑了迟滞),可以开启电器
console.log(` 决策: 富余功率超过阈值,可以开启电器。`);
if (lastSwitchState !== 'on') {
console.log(` 执行: 开启电器。`);
await controlSwitch('on');
} else {
console.log(` 执行: 状态未变(已开启),无需操作。`);
}
} else {
// 情况3:富余功率为正但不足以开启电器,或处于迟滞区间,保持现状
console.log(` 决策: 富余功率不足或处于稳定区间,保持当前状态。`);
}
}
/**
* 主函数:设置定时器
*/
function main() {
console.log('=== 太阳能智能控制器已启动 ===');
console.log(`配置:检查间隔 ${CHECK_INTERVAL_MS / 1000} 秒,电器功率 ${DEVICE_RATED_POWER}W`);
// 立即执行一次
checkAndControl();
// 设置定时循环
setInterval(checkAndControl, CHECK_INTERVAL_MS);
}
// 启动程序
main();
4.3 脚本运行与后台常驻
保存好脚本后,在终端运行:
node solar-controller.js
你应该能看到控制台每60秒输出一次当前的功率数据和决策日志。测试时,你可以用手遮挡部分光伏板模拟发电量下降,或者打开一个大型电器模拟用电上升,观察脚本是否能正确发出开关指令。
要让这个脚本在服务器(如树莓派)上 24小时不间断运行 ,有几种方法:
- 使用
screen或tmux:在终端会话中启动脚本,然后断开连接,脚本会在后台运行。 - 使用
pm2进程管理器 (推荐):npm install -g pm2 pm2 start solar-controller.js --name solar-control pm2 save pm2 startup # 设置开机自启(根据提示操作)pm2可以管理进程状态、查看日志、监控资源,是生产环境更可靠的选择。 - 创建系统服务 :在树莓派上可以创建一个
systemd服务单元文件,实现更专业的开机自启和守护。
5. 高级优化与常见问题排查
基础功能实现后,我们可以从稳定性、准确性和用户体验方面进行优化。
5.1 逻辑优化策略
-
多时段策略 :电暖器在深夜开启可能毫无意义(即使有光伏余电,但家庭已入睡,无需供暖)。可以在脚本中集成时间判断,例如只在上午8点到晚上10点之间执行自动化逻辑。
const currentHour = new Date().getHours(); const ENABLE_HOUR_START = 8; const ENABLE_HOUR_END = 22; if (currentHour < ENABLE_HOUR_START || currentHour >= ENABLE_HOUR_END) { console.log(' 非启用时段,强制关闭电器。'); await controlSwitch('off'); return; } -
平滑滤波与数据有效性检查 :Shelly EM的瞬时功率可能有小幅跳动。可以改为读取过去1分钟的平均功率,或者连续读取3次数据取中位数,避免单次波动误触发。
async function getStablePowerData(sampleCount = 3, intervalMs = 2000) { let readings = []; for (let i = 0; i < sampleCount; i++) { const data = await getPowerDataFromLocal(); if (data.success) { readings.push(data); } if (i < sampleCount - 1) await delay(intervalMs); } // 计算中位数或平均值 const avgHome = readings.reduce((sum, d) => sum + d.home, 0) / readings.length; const avgSolar = readings.reduce((sum, d) => sum + d.solar, 0) / readings.length; return { home: avgHome, solar: avgSolar, success: readings.length > 0 }; } -
状态持久化与容错 :脚本重启后,
lastSwitchState会丢失。可以每次操作后,将状态写入一个本地文件或小型数据库(如SQLite)。重启时先读取文件,并向智能开关发送一次状态查询请求,同步实际状态,避免误操作。
5.2 常见问题与排查清单
在部署过程中,你可能会遇到以下问题。这里提供一个排查思路:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| Node.js脚本无法获取数据 | 1. IP地址错误 2. 网络不通 3. Shelly EM未启用本地RPC |
1. 在浏览器访问 http://[Shelly-EM-IP]/status ,看能否返回JSON数据。 2. 在Shelly App中确认设备在线。 3. 确保Shelly固件已更新,本地HTTP/RPC访问已开启(默认是开启的)。 |
| 功率数据符号错误(如光伏显示为正) | CT钳安装方向错误 | 1. 在光伏发电时,检查Shelly App中P2通道数值。应为负数。 2. 如果为正数, 断电后 将CT钳2的物理方向旋转180度重装。 |
| 开关不动作或动作相反 | 1. 控制URL错误 2. 继电器逻辑相反 |
1. 手动在浏览器访问 SWITCH_ON_URL 和 SWITCH_OFF_URL ,测试开关是否能正常响应。 2. 检查Shelly 1的设置,确认继电器模式是“开关”模式而非“瞬间”模式。 |
| 电器频繁开关 | 1. 功率阈值设置不当 2. 功率数据波动大 |
1. 增大 HYSTERESIS_THRESHOLD 迟滞阈值,如从100W调到200W。 2. 实现上述的“平滑滤波”逻辑,使用平均功率而非瞬时功率做判断。 |
| 夜间或阴天电器误开启 | 逻辑缺陷,未考虑发电量为零的情况 | 在决策逻辑中增加条件: if (solarProduction < 50) { // 假设小于50W视为无有效发电,直接关闭 } 。 |
| 脚本运行一段时间后停止 | 1. 未捕获的异常导致进程退出 2. 内存泄漏(少见) |
1. 用 pm2 管理,它会自动重启崩溃的进程。 2. 在 setInterval 和 fetch 外围添加 try...catch ,确保单个循环错误不会导致整个脚本停止。 |
5.3 方案扩展思路
这个基础框架可以衍生出很多有趣的玩法:
- 多设备优先级控制 :如果你有电热水器、电动汽车充电桩、游泳池水泵等多个可调负载,可以给它们设定不同的功率等级和优先级。当富余功率达到2000W时开启热水器,达到6000W时再开启充电桩。
- 集成到家庭自动化平台 :将Shelly EM的数据通过MQTT协议接入 Home Assistant 或 Node-RED 。在这些可视化平台上,你可以更灵活地设计自动化流程,创建漂亮的能源仪表盘,并与其他智能设备联动(如晴天自动打开窗帘)。
- 增加储能电池逻辑 :如果你家有储能电池,逻辑可以更复杂:优先用光伏给电池充电,电池充满后再用富余电力启动电器;在电价高峰时段,优先使用电池供电,并关闭非必要大功率电器。
- 数据记录与分析 :让Node.js脚本将每次读取的功率数据和开关动作记录到数据库(如InfluxDB),然后用Grafana绘制成图表。你可以清晰地看到光伏发电曲线、家庭用电曲线以及自动化控制的效果,为后续优化提供数据支持。
这个项目的魅力在于,它用相对低的成本和一定的动手能力,解决了家庭能源管理中的一个真问题。看到电暖器在阳光明媚的午后自动启动,消耗着“免费”的太阳能,而在阴天或夜晚自动关闭,那种“物尽其用”的满足感,是单纯购买成品设备无法比拟的。
更多推荐

所有评论(0)