我家的房子,开发商送了个阳台壁挂式的太阳能热水器,自己又装了天然气热水器。本着节约能源(抠逼)的原则,自然是应该优先使用太阳能热水器的热水。但这就需手动去开/关两个热水器的阀门,大多数时间都因为懒,一直用的是天然气热水器的热水。
如果能自己切换就好了。阳光充足,太阳能热水器的水温足够高,就用太阳能热水器里的水,否则使用天热气热水器。
结合最近在玩的 ESP8266,又搜到了有电动球阀能符合我的需求,就有了下面这个方案:
ESP8266 上连接了以下设备:
ESP8266 上只连接了 2 路继电器,用以控制电动球阀的打开与闭合。
太阳能热水器侧的功能比较多。首先是检测温度,然后通过 MQTT 协议向 Domoticz 广播温度信息,如果温度达到设置的阀值,就驱动继电器打开/关闭电动球阀,同时通过 http 协议向天然气热水器侧发送控制信息。
为方便以后更新代码,还添加了 OTA 更新功能。访问http://ESP8266IP/ota?action=open即可开启 OTA 更新。
#include <ESP8266WiFi.h>
#include <OneWire.h>
#include <DS18B20.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <OLED.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
// wifi
const char* ssid = "wifi";
const char* password = "wifipassword";
// DS18B20
#define ONE_WIRE_BUS 2
DS18B20 ds(ONE_WIRE_BUS);
// relay
#define OPEN_PIN 14
#define CLOSE_PIN 12
bool ISOPEN = false;
// mqtt
const char MqttServer[] = "10.0.2.11";
const char TOPIC[] = "domoticz/in";
WiFiClient espClient;
PubSubClient client(espClient);
// oled
OLED display(4, 5);
// ota
bool ISOTA = false;
// server
ESP8266WebServer server(80);
// client
HTTPClient http;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(OPEN_PIN, OUTPUT);
pinMode(CLOSE_PIN, OUTPUT);
digitalWrite(OPEN_PIN, LOW);
digitalWrite(CLOSE_PIN, LOW);
setWifi();
display.begin();
client.setServer( MqttServer, 1883 );
ota();
setHttpServer();
}
void loop() {
// put your main code here, to run repeatedly:
server.handleClient();
if(ISOTA){
display.print("OTA...", 4, 5);
ArduinoOTA.handle();
} else{
if (!client.connected()) {
reconnect();
}
client.loop();
changRelay();
delay(2000);
}
}
void setWifi(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
// mqtt reconnect
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect("ESP8266Client")) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(1000);
}
}
}
void changRelay(){
Serial.println(ds.getTempC());
float temp = ds.getTempC();
publishTemp(temp);
updateOled(temp);
if(temp >= 42 && !ISOPEN) {
http.begin("http://10.0.2.200/update?action=close");
http.GET();
http.end();
digitalWrite(OPEN_PIN, HIGH);
delay(15000);
ISOPEN = true;
digitalWrite(OPEN_PIN, LOW);
} else if (temp < 40 && ISOPEN) {
http.begin("http://10.0.2.200/update?action=open");
http.GET();
http.end();
digitalWrite(CLOSE_PIN, HIGH);
delay(15000);
ISOPEN = false;
digitalWrite(CLOSE_PIN, LOW);
}
}
void publishTemp(float temp){
String payload = "{";
payload += "\"idx\": 4,";
payload += "\"command\":\"udevice\",";
payload += "\"nvalue\": 0,";
payload += "\"svalue\":";
payload += "\"";
payload += temp;
payload += "\"";
payload += "}";
char attributes[100];
payload.toCharArray( attributes, 100 );
client.publish(TOPIC, attributes);
}
void updateOled(float temp){
char c[10];
dtostrf(temp,2,2,c);
display.print(c, 4, 5);
}
void ota(){
ArduinoOTA.onStart([]() {
display.clear();
Serial.println("Start");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {nb
char c[10];
float p = progress / (total / 100);
dtostrf(p,2,0,c);
display.print(c, 4, 7);
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
}
void setHttpServer(){
server.on("/ota", [](){
if (server.hasArg("action")){
if(server.arg("action") == "open") {
ISOTA = true;
server.send(200, "text/plain", "start ota update");
} else if(server.arg("action") == "close"){
ISOTA = false;
server.send(200, "text/plain", "stop ota update");
}
}
});
server.begin();
}
天然气热水器侧,只接收太阳能热水器侧的控制信息,驱动继电器打开/关闭电动球阀即可。
其实使用任一设备访问http://ESP8266IP/update?action=open即可打开/关闭球阀了。这是个安全漏洞,不过应该不会有人来恶意控制我的热水器玩吧,在我洗澡的时候给我把热水停了?
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
// wifi
const char* ssid = "wifi";
const char* password = "wifipassword";
// server
ESP8266WebServer server(80);
// relay
#define OPEN_PIN 14
#define CLOSE_PIN 12
bool ISOPEN = false;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(OPEN_PIN, OUTPUT);
pinMode(CLOSE_PIN, OUTPUT);
digitalWrite(OPEN_PIN, LOW);
digitalWrite(CLOSE_PIN, LOW);
setWifi();
setHttpServer();
}
void loop() {
// put your main code here, to run repeatedly:
server.handleClient();
}
void setWifi(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void setHttpServer(){
server.on("/update", [](){
if (server.hasArg("action")){
if(server.arg("action") == "open" && !ISOPEN) {
Serial.print("open");
digitalWrite(OPEN_PIN, HIGH);
delay(15000);
ISOPEN = true;
digitalWrite(OPEN_PIN, LOW);
} else if(server.arg("action") == "close" && ISOPEN){
Serial.print("close");
digitalWrite(CLOSE_PIN, HIGH);
delay(15000);
ISOPEN = false;
digitalWrite(CLOSE_PIN, LOW);
}
}
server.send(200, "text/plain", "this works as well");
});
server.begin();
}
代码托管于 https://gitee.com/stillyu/esp8266_heater_switch
序号 | 名称 | 单价 | 数量 | 总价 |
---|---|---|---|---|
1 | ESP8266 模块 | 14.60 | 2 | 29.20 |
2 | 2 路继电器 | 5.88 | 2 | 11.76 |
3 | OLED 显示屏 | 10.80 | 1 | 10.80 |
4 | 5V 降压模块 | 1.35 | 2 | 2.70 |
5 | DS18B20 温度传感器 | 4.70 | 1 | 4.70 |
6 | 电动球阀 | 63.00 | 2 | 126.00 |
7 | 4 分水管活接头 | 5.60 | 2 | 11.20 |
8 | 4 分水管对丝 | 2.80 | 2 | 5.60 |
9 | 12V 电源适配器 | 10.00 | 2 | 20.00 |
10 | 接线端子 | 1.75 | 4 | 7.00 |
11 | 亚克力外壳 | 20 | 1 | 20.00 |
合计 | 248.96 |
为什么管钳工也是 Level UP 呢?因为第一个电动球阀我安装了三遍,学会了生料带怎么缠,生料带缠不好是真的会漏水的。
放一张手机端 HomeKit 的图
可以看到热水器的水温,实际也可以控制两个球阀的开关,但没什么意义,自动控制就好了。
有同学注意到猫窝温度吗?这还是个半成品,因为人体猫体传感器的原因,只能检测到活动的人体猫体,不能检测到睡觉的人体猫体,准备用电子秤模块解决。