《太阳能热水器控制器用esp8266接入HASS》
本人家在南方小县城里面,家里装了两套热水器,一套太阳能,一套燃气。
当然一开始只有一套,后面冬天没热水只能每天用烧水壶烧水洗澡,后面实在受不了了,就加了一套燃气热水器。
那么问题来了:
①:家里是自建房,一共四个房间加客厅要用热水,只能集中供水,那么太阳能和燃气热水器要如何切换?
②:燃气热水器只有冬天才用,冬天水温太冷,直接烧燃气消耗太快,怎么省?
③:如果能切换,能不能不要手动切换,那样多麻烦?
先给大家看看成果吧
在HASS里面可以看到水位,水温,外界温度。可以手动切换是否使用燃气热水器,加水操作。
天然气余量是另一个模块,跟控制器没有关系。
=======构思=======
既然有了需求就有了折腾的动力,既然要满足集中供水,就要公用一条管路,那么燃气热水器就只能连接到太阳能管路后面。
但是如果直接对接的话每次用水燃气热水器就会启动,这样不行,要加分流,让燃气热水器在我们需要开启的时候才能开启。
于是我设计了下面这样的水路切换:
正常使用时AB点互通,当水温太低需要使用燃气热水器时转换三通阀使AC点互通。
这样在太阳能提升了一定温度的水就可以经过燃气热水器加热到指定温度。
因为太阳能提前加热了一些,所以燃气热水器加热时就不需要消耗太多燃气,也就达到了节约的目的。
过滤器是为了过滤掉在太阳能热水器里面产生的水垢,避免堵塞花洒。
水路设计好了,电路也得跟上。
原先的控制器接线都是分开的,太分散了!思来想去,使用网线连接最适合。控制器这边就不设置接线端子了,直接使用网口输出。
热水器这边使用网口转接,具体是这样的↓
网口用AB胶水粘在继电器上固定,继电器是用来控制三通阀切换和燃气热水器的。
这样在热水器和控制器之间只需要拉一条网线就可以互相通讯了,比原来的一大坨线好多了。
当然改造过之后也可以吧控制器放在顶楼6楼,不过我现在是放在二楼,方便调试。
首先三通阀必须要控制,我选用的三通阀是220V切换控制,控制逻辑见下图。
三通阀全貌长这样:
燃气热水器选用强排式需要220V供电的热水器,16升基本满足两人同时用水的需求。
那么如何操控三通阀和燃气热水器的切换呢,思来想去,决定从太阳能控制器下手。
传统的控制器可以自动上水,加热(这玩意儿贼费电,我从来没用过),保温,防冻(南方不需要)。
就这....也好意思说智能
拆开控制器,几个继电器控制加热保温,显示,供电,传感器.......好像不是很复杂
那就自己动手做一个控制器吧!
要满足温度水温测量,水路切换,定时,与ha通讯,单靠8266应该是比较难做的,所以找了个帮手-->STC单片机。
有了单片机,测控可以比较完美的进行,ha通讯就交给8266就行了。
=======选型=======
直接在马云家搜STC单片机,出来了好多型号,前面几个是新出的STC8系列单片机,在stc烧录程序里面查看功能参数。
综合考量,选定了STC8H1K17-TSSOP20封装,单价约2元。
要定时搞事当然要配实时时钟芯片啦,就选简单易懂的DS1302,做diy的小伙伴都知道
显示嘛,原机使用的数码管是定制的,使用TM1628驱动,改造的话还是尽量适配原机的数码管,最终选用了TM1668驱动原机数码管。
最后是知道水位水温的关键啦,太阳能水位水温传感器~~~
嗯,这玩意儿原理其实挺简单的。
选好器件,就要开始合体♂了(
=======原理图和板子=======
要读取传感器的数据,首先要了解传感器的原理。
这种通用的传感器其实是由两个二极管分离的热敏电阻和水位分割电阻(应该是这么说吧)组成的。具体见下图:
这样在AB两端施加不同的电位就可以分别获取到水位以及水温了,根据这个原理,画出对应的采样电路:
D9-D12是TVS保护管,可以忽略。20脚是AD采样引脚,19和18是激励切换引脚。RED对应A点,YELLO对应B点。
19脚高电平,18脚低电平时:电流可以通过上方的二极管,下方二极管截止,于是采集到了热敏电阻的AD值,保存转换得到温度。
18脚高电平,19脚低电平时:电流可以通过下方的二极管,上方二极管截止,四个电阻串联,水位到达一定程度时,下方的电阻相当于短接,于是采集到对应的AD保存转换得到水位。
没错,就是这么简单就能采集到水位水温了,核心点也就这个,其他都比较简单,就是一些电阻电容把器件组合起来而已,跟搭积木一样。
本地计时时钟采用DS1302,编程简单。
因为上次购买的8266还剩下好些,所以也采用了板载的方式集成8266通讯,通过串口连接两个mcu。
驱动数码管的任务就交给了TM1668,也是很简单就能驱动了。
因为是适配原来的太阳能控制器,所以板子外形做了定制
原本打算用NTC检测温度的,后面写程序的时候嫌复杂就放弃了,改用18b20,也能达到要求。
右下角的跳线是调试用的,左下角是网口。
实装后是这样的:
数码管改成了可以拿掉的,方便调试
因为8266部分画图的时候没有画好,瞎画的电路,所以装机后信号特差,后面就加了上个帖子改进后的模块
=======编程=======
单片机部分因为组件比较多,代码也比较多和杂乱(半桶水)
所以先跟大家分享新学的检测传感器的部分
其实也不复杂,两个引脚(P1.0和P3.7)分别切换高低电平,然后ADC在电平切换稳定后转换并数字滤波,然后按照规律转换成温度或者水位就可以了。
if(ADCMode){
/***************温度检测***************/
if(!ADCReady){EADC = 1;YELL = 0;RED = 1;ADC_CONTR |= 0x41;}
else if(ADCReady){
WenDuL[DiT_ConA] = ADCdata / 256;
DiT_ConA ++;
ADCdata = 0;
ADCMode = 0;
ADCReady = 0;
EADC = 0;
}
if(DiT_ConA >= 76) DiT_ConA = 0;
}
else {
/***************水位检测****************/
if(!ADCReady) {EADC = 1;YELL = 1;RED = 0;ADC_CONTR |= 0x41;}
else if(ADCReady){
ShuiWeiL[DiT_ConB] = ADCdata / 256;
DiT_ConB ++;
ADCdata = 0;
ADCMode = 1;
ADCReady = 0;
EADC = 0;
}
if(DiT_ConB >= 32) DiT_ConB = 0;
}
/***************18B20**********************/
if(TestTime >= 4){
EA = 0;
Temp18b = (unsigned int)read_temperature();
EA = 1;
if(Temp18b != 9999) {
Temp18b = Temp18b * 0.625;
TmpNTC_OS = Temp18b / 10;
}
else TmpNTC_OS = 99;
TestTime = 0;
}
/****************温度计算*******************/
for(j=0;j<76;j++) tmp += WenDuL[j];
WenDuADC = tmp / 76;
tmp = 0;
if(WenDuADC > 938)Temp = 9999;
else{
if(WenDuADC < 475) Temp = 99;
else if(WenDuADC < 525){ //81-99度
WenDuADC_P = 525 - WenDuADC;
Temp = WenDuADC_P / 2.6 + 80;
}
else{ //0-80度
WenDuADC_P = 934 - WenDuADC; //原938
Temp = WenDuADC_P / 5.1538;
}
}
/******************水位计算*******************/
for(j=0;j<32;j++) tmp = tmp + ShuiWeiL[j];
ShuiWeiADC = tmp / 32;
tmp = 0;
if(LingMin){
if(ShuiWeiADC > 160) ShuiWei = 4;
else if(ShuiWeiADC > 100) ShuiWei = 3;
else if(ShuiWeiADC > 70) ShuiWei = 2;
else if(ShuiWeiADC > 50) ShuiWei = 1;
else if(ShuiWeiADC < 35) ShuiWei = 99;
else ShuiWei = 0;
}
else{
if(ShuiWeiADC > 220) ShuiWei = 4;
else if(ShuiWeiADC > 150) ShuiWei = 3;
else if(ShuiWeiADC > 90) ShuiWei = 2;
else if(ShuiWeiADC > 60) ShuiWei = 1;
else if(ShuiWeiADC < 35) ShuiWei = 99;
else ShuiWei = 0;
}
后面是中断部分
/******************ADC中断*********************/
void ADC_Isr() interrupt 5{
ADC_CONTR &= ~0x20; //清中断标志
if(RED && !YELL){
ADCdata += (ADC_RES*4+(ADC_RESL >> 6));
ADCon_WD ++;
if(ADCon_WD >= 256){
YELL = 0;
RED = 0;
ADCon_WD = 0;
ADCReady = 1;
}
ADC_CONTR |= 0x41;
}
if(!RED && YELL){
ADCdata += (ADC_RES*4+(ADC_RESL >> 6));
ADCon_SW ++;
if(ADCon_SW >= 256){
YELL = 0;
RED = 0;
ADCon_SW = 0;
ADCReady = 1;
}
ADC_CONTR |= 0x41;
}
}
其他就是自动控制部分,按键检测,实时时钟读取,之类七七八八的。
还有一个重点是通讯部分,使用串口通讯。
STC定时上报状态给8266,然后需要控制时8266下发指令给STC,实现通讯。
下面是一部分代码
SendconTimer++; //定时计数
if(SendconTimer > 400){
SendconTimer = 0;
switch(Sendcon){
case 0:
SendString("GETA");SendNumber(Temp,2);SendEnter();Sendcon++;break; //上报水温
case 1:
SendString("GETB");SendNumber(ShuiWei,1);SendEnter();Sendcon++;break; //上报水位
case 2:
SendString("GETE");SendNumber(Temp18b / 10,2);SendString(".");SendNumber(Temp18b,1);SendEnter();Sendcon++;break; //上报18B20温度
case 3:
if(JiaShuiFlag) {SendString("INWON");SendEnter();Sendcon++;} //上报是否加水
else {SendString("INWOFF");SendEnter();Sendcon++;}
break;
case 4:
if(BaoWenFlag) {SendString("BAWON");SendEnter();Sendcon=0;} //上报是否开启燃气热水器
else {SendString("BAWOFF");SendEnter();Sendcon=0;}
break;
default:break;
}
}
switch(SendFlag){
case 1:
SendString("GETA");SendNumber(Temp,2); //主动查询水温上报
SendEnter();ReadFlag = 0;SendFlag = 0;break;
case 2:
SendString("GETB");SendNumber(ShuiWei,1); //主动查询水位上报
SendEnter();ReadFlag = 0;SendFlag = 0;break;
case 3:
SendString("GETC");SendNumber(timer,4); //主动查询当前本地时间
SendEnter();ReadFlag = 0;SendFlag = 0;break;
case 4:
SendString("GETE");SendNumber(Temp18b / 10,2); //主动查询18b20温度
SendEnter();ReadFlag = 0;SendFlag = 0;break;
case 10:
if(ShuiWei < 4){JiaShuiFlag = 1;ShuiWeiS = 4;} //上水指令,目标水位为4(就是加满)
ReadFlag = 0;SendFlag = 0;BeepCon = 1000;break;
case 11:
JiaShuiFlag = 0;ReadFlag = 0;SendFlag = 0;BeepCon = 1000;break; //停止上水指令
case 12:
SendString("BAWON");SendEnter();
BaoWenFlag = 1;ReadFlag = 0;SendFlag = 0;BeepCon = 1000;break; //打开燃气热水器指令
case 13:
SendString("BAWOFF");SendEnter();
BaoWenFlag = 0;ReadFlag = 0;SendFlag = 0;BeepCon = 1000;break; //关闭燃气热水器指令
case 21:
CaiPin = 1;ReadFlag = 0;SendFlag = 0;break; //告知已联网成功
default:break;
}
下面是串口中断部分:
/*UART 中断服务程序*/
void Uart() interrupt 4{
unsigned char i;
unsigned char j;
unsigned char SerialRead;
if(RI){ //读取中断标志位
RI = 0;
if(!ReadFlag){
SerialRead = SBUF;
if(SerialRead == 0x0a || SerCon > 10){
ReadFlag = 1;
SerCon = 0;
}
else Serialread[SerCon++] = SerialRead;
}
if(ReadFlag){
SerCon = 0;
if(Serialread[0] == 'S' && Serialread[1] == 'E' && Serialread[2] == 'T'){
if(Serialread[3] == 'D'){
Timedata = 0;
for(i=4;i<8;i++){
j = Serialread[i] - 0x30;
Timedata = Timedata * 10 + j;
}
SFlag = 1;
SerReadNum = 1;
}
else if(Serialread[3] == 'A') SendFlag = 1; //读水温
else if(Serialread[3] == 'B') SendFlag = 2; //读水位
else if(Serialread[3] == 'C') SendFlag = 3; //读DS1302时间
else if(Serialread[3] == 'E') SendFlag = 4; //读DS18b20温度
else if(Serialread[3] == 'I') SendFlag = 10; //远程加水
else if(Serialread[3] == 'J') SendFlag = 11; //停止加水
else if(Serialread[3] == 'K') SendFlag = 12; //远程切换水路开
else if(Serialread[3] == 'L') SendFlag = 13; //远程切换水路关
else if(Serialread[3] == 'Z') SendFlag = 21; //ESP8266已联网标志
else ReadFlag = 0;
}
else ReadFlag = 0;
}
}
if (TI)
{
TI = 0; //清除TI位
busy = 0;
}
}
总体写下来居然也用了将近12K的存储,还好选了17K内存的单片机
8266这边就比较简单了,使用esphome自带的自定义串口集成就可以实现:
网页说明在这里:https://www.esphome.io/cookbook/uart_text_sensor.html
首先要把一个自定义的程序库(应该是这么叫吧)放到\config\esphome里面,然后再yaml里面添加头文件
esphome:
name: swh-controller
includes:
- suncon.h
然后创建一个自定义的串口文本传感器,传感器ID设置为uart_readline:
text_sensor:
- platform: custom
lambda: |-
auto my_custom_sensor = new UartReadLineSensor(id(uart_bus));
App.register_component(my_custom_sensor);
return {my_custom_sensor};
text_sensors:
id: "uart_readline"
根据STC发送过来的数据进行解析,比如我设定的上报水温的数据是 GETA58 ,其中GETA是识别码,58是数值。
那么在yaml里面就可以解析成这样:
- platform: template
name: Solar heater Water temperature
id: watertemp
update_interval: 5s
icon: mdi:coolant-temperature
lambda: |-
if (id(uart_readline).state[0] == 0x47 && id(uart_readline).state[1] == 0x45 && id(uart_readline).state[2] == 0x54 && id(uart_readline).state[3] == 0x41){
return {id(uart_readline).state};
}
else {return {};}
filters:
- substitute:
- "GETA -> "
第七行是检测是否是指定的识别码,filters是为了删除掉识别码GETA只输出数字。
这样写后在HASS里面就会有一个传感器。
那么如何控制呢,也是创建一个开关,然后发送指定的指令:
switch:
- platform: template
name: "Gas water heater"
icon: mdi:swap-horizontal
lambda: |-
if (id(uart_readline).state == "BAWON") {
return true;
} else if(id(uart_readline).state == "BAWOFF") {
return false;
} else {
return {};
}
turn_on_action:
- uart.write: "SETK\n"
turn_off_action:
- uart.write: "SETL\n"
lambda里的内容是STC接收到指令后立即上报反馈的指令,可以立即刷新按钮的状态,就不会出现打开后按钮又关闭的情况了。
打开燃气热水器的指令是SETK回车,关闭的话就是SETL回车。
另外因为内置的DS1302走时误差太大了,所以加入了定时校准时间的程序:
time:
- platform: homeassistant
id: sntp_time
on_time:
- days_of_week: 1
then:
- uart.write: !lambda
char str[25];
time_t currTime = id(sntp_time).now().timestamp;
auto time = id(sntp_time).now();
strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", localtime(&currTime));
if(!time.is_valid())
return {0x53,0x53,0x53,0x53,0x0d,0x0a};
else
return {0x53,0x45,0x54,0x44,str[11],str[12],str[14],str[15],str[17],str[18],0x0d,0x0a};
STC设定的校准指令是SETD加时间(时和分),如SETD1800,则校准时间为下午6点整。
另外还加了个联网成功标志,是利用时间源有效作为判断:
interval:
- interval: 1s
then:
- if:
condition:
lambda: |-
auto time = id(sntp_time).now();
return {time.is_valid() && (id(startflag)<1)};
then:
- uart.write: "SETZ\n"
- globals.set:
id: startflag
value: '1'
发送SETZ回车后屏幕左上方的数码彩屏灯电亮,表示联网成功。
总体就是这样:
从上至下依次是:加水,打开燃气热水器,手动同步时间,室外温度,水位,水温。
至于自动化吗,我个人是倾向使用单片机完成,这样不会受到网络影响,不过后期可以使用hass作为辅助,比如下雨天不上水啊,或者是太阳落山开启燃气热水器等等。
=======后记=======
其实这个项目也整了小半年了,一点一点整的,一步一步完善。
现在可以完成自动上水,晚上判断水温自动切换,远程操控也是可以的。
水路实拍图:
因为家里没有通管道燃气,所以要放一瓶液化气到楼顶,所以加了个称连接到HASS,这样就可以知道燃气有没有用完,避免了洗澡洗一半热水的尴尬 ̄□ ̄||
这个也比较好实现,esphome里面的hx711集成,接入hx711模块,采集零点和重量,转换为比值就可以看到剩余量了。有想做的我可以详细解答。
因为白天热水器没开,所以用了个18650作为白天供电,晚上充电。
好了,也就这么多了,有问题的小伙伴可以留言,我看到了也会回复的。打字累死了
哦对了,因为这个项目个性化比较强,我的方案大家不一定适用,所以我并没有放出源文件,只是给大家提供一个思路,交流交流
如果大家也想改的话交流一下意见,看看能不能整出一个通用的方案出来,这样就比较好改了
|