描述
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;">* 1、项目功能介绍</h3>
<hr>
<p style="line-height: 1.8;"> 据资料显示,我国是全球最大的太阳能热水器市场,并且太阳能热水器行业在2023年呈现出稳步增长的趋势,未来发展前景十分广阔。我家也有一个太阳能热水器,2016年安装的,经过多年的使用,我发现了太阳能热水器的两个弊端:</p>
<p style="line-height: 1.8;"> 第一个是在冬天功耗非常高,传统的太阳能热水器控制器工作逻辑是这样的,每天在两个设定的时间点,检测当前水温是否小于设定温度,如果小于,就开启电加热到设定的温度,因为在南方,冬天太阳较少,加上太阳能热水器的保温水箱是在室外,冬天室外温度较低,所以保温效果很差(使用年限越久保温效果越差),这就导致了冬天基本每天都要使用电加热两次,而冬天一般洗澡不频繁,根本用不到每天使用电加热,所以就造成了电能浪费。下图是我家2024年月用电分布图,我家冬天没有开过空调,都是使用煤烤火,1、2月份电耗明显高于3月份,就是因为太阳能热水器,3月份就更换了我自己做的第一版太阳能热水器控制器,所以电耗就有所降低(也有天气转暖的原因);7、8、9是空调制冷导致。</p>
<p style="line-height: 1.8;"><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/AzyX9Q6kQAtBHESe2eHd4EVpxVXF7UlVltyLBGX6.jpeg" width="361" height="358"></strong></p>
<p style="line-height: 1.8;"> 第二个是太阳能热水器的供水问题,不少地区的水资源并不丰富,自来水断水是常态(特别是小县城和农村地区),所以一般安装了太阳能热水器的家庭,都会装一个配套的不锈钢水塔来储水,给太阳能热水器供水(我家那边都是这样的);就是下图这种不锈钢水塔,相信不少人都见过;不锈钢水塔和太阳能热水器都安装在顶楼,由一楼的水泵给不锈钢水塔供水,可想而知,在没有水位反馈的情况下,经常会出现水都用完了放不出水了才往里面抽水,抽水时一不注意就会抽多溢出。</p>
<p style="line-height: 1.8;"><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/d09MbEVzoyddRWTxkAk6KTtIPzuOX99bGCakgxaJ.jpeg" width="233" height="430"></strong></p>
<p style="line-height: 1.8;"> 针对第一个问题,我的想法是开发一个微信小程序加配套的太阳能热水器控制器,来实现预约制度,在冬天需要用热水洗澡时,提前在微信小程序上预约好,控制器会在你预约的时间点之前将水加热到你预约的温度(如果当天太阳很大,水温已经达到了你预约的温度就不会电加热);实测在夏天,基本用不到预约,也用不到电加热,每天水温都非常高,所以只需要在冬天预约。</p>
<p style="line-height: 1.8;"> 针对第二个问题,我的想法是给不锈钢水塔做一个水位检测传感器,将其接入太阳能热水器控制器中,同时将一楼的水泵控制开关也加入到这个控制系统中,这样就能在微信小程序上看到不锈钢水塔中的水量、通过微信小程序控制水泵上水,之所以不做成自动上水,是因为有时候自来水会断水,如果做成自动上水有可能导致水泵干抽而烧掉;另外,为了避免只能使用微信小程序抽水带来的不方便,我在水泵的控制部分加上了按键和数码管,这样不需要打开微信小程序也能看到不锈钢水塔水量、并进行抽水或停止抽水操作。</p>
<p style="line-height: 1.8;"> 本项目在设计时,把系统稳定放在第一位,因为是要正式投入我家使用的;目前使用了一个多月,没有出现任何问题(24/10/14)。</p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;"><strong>*2、项目属性</strong></h3>
<hr>
<p style="line-height: 1.8;"><strong><span style="color: #95a5a6; font-size: 14px;">项目是否首次公开:</span></strong>此项目在我的上一个开源项目中公开过,当时没考虑拿这个项目打立创电赛,后面想想还是用这个来打。</p>
<p style="line-height: 1.8;"><strong><span style="color: #95a5a6; font-size: 14px;">项目是否为原创:</span></strong>是。</p>
<p style="line-height: 1.8;"><strong><span style="color: #95a5a6; font-size: 14px;">项目是否曾经在其他比赛中获奖:</span></strong>无。</p>
<p style="line-height: 1.8;"><strong><span style="color: #95a5a6; font-size: 14px;">项目是否在学校参加过答辩:</span></strong>无。</p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;"><strong>* 3、开源协议</strong></h3>
<hr>
<p style="line-height: 1.8;">本项目使用GPL3.0开源协议。</p>
<p style="line-height: 1.8;">1.本项目开源太阳能热水器控制器电路、水塔上水控制模块电路、不锈钢水塔水位检测模块电路、中继模块电路。</p>
<p style="line-height: 1.8;">2.本项目开源开源太阳能热水器控制器源码、水塔上水控制模块源码、不锈钢水塔水位检测源码。</p>
<p style="line-height: 1.8;">3.考虑到本项目正在投入使用,如果开源微信小程序和中继模块源码会导致正在投入使用的系统有被入侵风险,所以只开源中继模块的固件和开放微信小程序给大家使用,想要复刻的朋友需联系我创建微信小程序密钥。</p>
<p style="line-height: 1.8;"><strong>4. 24/10/20新增,中继模块源码开源</strong></p>
<p style="line-height: 1.8;"><strong>5. 24/10/21新增,微信小程序源码开源(风险都已规避,至此整个项目所有东西全部开源)</strong></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><strong><span style="color: #0093e6;">请在竞赛阶段填写 ↓</span></strong></p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;"><strong>*4、硬件部分</strong></h3>
<hr>
<p>硬件包括太阳能热水器控制器、不锈钢水塔水位检测模块、水塔上水控制模块、中继模块;下面分别介绍这四个部分。</p>
<p><strong> 4.1 太阳能热水器控制器</strong></p>
<p><strong>4.1.1 基本特性</strong></p>
<p><strong><span style="color: #95a5a6; font-size: 14px;"> </span></strong><span style="color: #95a5a6; font-size: 14px;"> 支持太阳能热水器上常用的220伏1500W电加热棒;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持市面上常见的四线水位水温传感器;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持密封盒子内温度和湿度以及室外温度检测;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持RS232通信,可外接RS232传感器(如本项目中的不锈钢水塔水位检测模块);</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> RS232接口双电压供电输出(12V 530mA和5V 500mA</span><span style="color: #95a5a6; font-size: 14px;">);</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持LoRa通信,可接入智能家居中继模块;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持12V 3.6W上水电磁阀;</span></p>
<p><strong>4.1.2 硬件框架</strong></p>
<p><strong> </strong> 供电部分有两个12V 10W的AC-DC电源模块,给系统以及RS232外接传感器提供了稳定的电源,有三个5V LDO芯片分别给RS232的5V电源输出、单片机最小系统、LoRa模块供电,保证了系统的稳定性;ADC检测电路中加入了由LMV358轨到轨运放搭建的电压跟随器,由此可以<strong>支持一些价格较低的高阻抗四线太阳能热水器传感器</strong>,同时也能保护单片机,提高<strong>系统稳定性</strong>;控制器中集成了DHT11模块,能实时检测控制器密封盒内的温湿度,如果出现漏水能第一时间反馈用户;为了系统稳定,MOS管的驱动都加了PC8117光耦。系统硬件框图如下</p>
<p>(RS232提供12V供电的原因:给不锈钢水塔水位传感器中的电机供电)</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/K3YGAyyFheakhoDdqBqY4VtsLi8L9gqzhFuHJ1AG.png" width="675" height="504"></strong></p>
<p> </p>
<p><strong>4.1.3 原理图及PCB</strong></p>
<p><strong> </strong>太阳能热水器控制器的原理图部分很简单普通,没有什么亮点;用的Air001单片机,32K Flash 4K RAM,做这个板子绰绰有余(GPIO引脚还剩余一个),用这个单片机就不适合量产了(懂得都懂),不过想必玩电子的谁手上还没个几块air001啊,就像当初49块钱的CC表;用的是HAL库开发,所以移植起来还是不难的,但程序设计时没考虑移植,所以没做好接解耦,增加了移植难度。单片机最小系统很简单,如下:</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/4nzI1eVYrm85kqfBulrTiu4vhGnFMTWEv8OvMo9y.png" width="302" height="502"></strong></p>
<p>只要复位电路、Boot电路、晶振就搞定了;个人建议还在开发中的板子,Boot按键和复位按键还是要有,能省不少麻烦;这个单片机有内部晶振,之所以用外部晶振还是为了那两个字系统稳定!</p>
<p>RS232转换芯片用的MAX232,原理图部分如下:</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/srHowiFrRacqgF8RTCROjscd9xhxdEHoaZcII67D.png"></strong></p>
<p> </p>
<p>需要注意的是MAX232的ROUT引脚接的是单片机串口的RX引脚,TIN接的是单片机串口的TX引脚,TOUT、RIN引脚需要交叉连接;另外C4、C5、C6、C7这几个电容的作用相当于电荷泵,给它们充电放电来产生RS232协议中所需要的+3V~+15V和-3V~-15V电平。</p>
<p>电压跟随器部分也很简单,在单片机ADC输入通道加了钳位二极管,提高系统稳定性。</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/nNqcgh5taB0r1JrcQURV6nTIYETGKGT5GcNirwqq.png" width="387" height="432"></strong></p>
<p>PCB部分需要注意将220V部分和低压部分做好隔离、220V走线尽可能粗,并开窗处理,因为电加热棒1500w、AC-DC模块下不要敷铜,多打过地孔,注意好这几点应该就没什么大问题。</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/BnmIzG1h2XygCTZGSGQRuwtrWreKz6KyVNd2hSGN.png" width="477" height="524"></strong></p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/7y94QXk4vCNChJb02gEkyugFtHaHSzUiIxvpIxCH.png" width="601" height="518"></strong></p>
<p> </p>
<p><strong> 4.2 不锈钢水塔水位检测模块</strong></p>
<p>技术创新点:用编码电机+浮球开关来实现水位测量;</p>
<p>水位测量原理:</p>
<p><strong><span style="color: #95a5a6; font-size: 14px;"> </span></strong><span style="color: #95a5a6; font-size: 14px;"> 1.水塔上水时,水位上涨,只要浮球开关碰到水,编码电机就控制转盘把浮球开关往上收,直到离开水停止,如此循环,根据编码器输出的脉冲个数计算水位。<br> 2.水塔没有上水时,此时随着生活用水,水塔中的水位会下降,只要浮球开关离开水,编码电机就控制转盘把浮球开关往下放,直到碰到水停止,如此循环,根据编码器输出的脉冲个数计算水位。<br> 3.一旦水位改变,立即记录当前编码器的值,保存到eeprom中,在系统每次上电时,从eeprom中读取出编码器的值,用这个值计算出编码器定时器CNT寄存器的值,然后检测是否碰到了水,如果浮球开关碰到了水,就上升至离开水;如果没有碰到水,就下降至碰到水;这样就实现了断电后水位变化,再次上电后,水位依然能准确测量</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 4.如何知道水塔是否上水?给水塔上水的水泵也在我们的控制系统中,当然能知道水塔是否上水。</span></p>
<p>在哔哩哔哩发布了介绍视频,经评论区网友告知,我这种方法的原理跟伺服液位计是一样的,也就是说我这个就是一个简易版的私服液位计;</p>
<p> </p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/2WW8oihM7OzPiqwhiyLg4P8cYNtDSZWDC9t78713.jpeg" width="465" height="274"></strong></p>
<p>不过相比于传统的伺服液位计(有现成的,但很贵),我这个的成本低的多,普通人更容易复刻;在设计之初,我尽可能的避免机构设计,花了很多时间找现成的能用又便宜的方案,因为对于一般人来说(包括我自己)机构上的实现往往就意味着更高的成本,为了尽可能简单的造出这个水位检测模块,在 转盘 与 电机轴 的连接上硬生生一个轴承都没用(捂脸),因为我用的是智能小车上常见的电机+轮子</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/NjLrf2IsSSglU5NBxqYm3ONuDjRL4stqloKJ2uQX.jpeg" width="381" height="353"></strong></p>
<p>将电机固定在密封盒内,轴从开孔处伸出来,轮子用M4螺丝固定在轴上,将轮子的橡胶部分取下来,用轮子做绕线盘,但是取下橡胶后的轮子凹槽还是不够深,所以需要在外边再加两块圆形PCB板,如下所示</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/wMAVMO3XADOpv0DE1hJMHnNTRd5ZNxh4AJ7EhFjh.png" width="305" height="315"></strong></p>
<p>用两块这样的圆形PCB板子将轮子夹在中间,然后用螺丝固定,整个装置安装好后的样子如下所示(遗憾密封盒内部结构当时没有拍照)</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/3AeTF8Gc0Jt3703sZgyrjZm4MyaQiMAKBEnUXGsw.jpeg" width="613" height="407"></strong></p>
<p>上图中黄色圈中的是保险浮球开关,蓝色小圈中的是承重的线,下面也吊着一个加了配重的浮球开关;</p>
<p>整个装置 具体安装讲解 和 正式工作的视频如下</p>
<p>【不锈钢水塔水量检测模块安装】 <a href="https://www.bilibili.com/video/BV1KTxLeyEDE/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6" target="_blank">https://www.bilibili.com/video/BV1KTxLeyEDE/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6</a></p>
<p>【电机+编码器测量水位】 <a href="https://www.bilibili.com/video/BV13Zx5eDEHP/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6" target="_blank">https://www.bilibili.com/video/BV13Zx5eDEHP/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6</a></p>
<p>有人会担心浮球开关的导线和拉住浮球开关的普通线会绕在一起,其实我也担心(捂脸),不过现在用了一个多月,没有出现绕在一起的情况。深度两米以下的不锈钢水塔或者水井,用这种方法就行,但是如果你想测量十几二十米的深度,那我建议得用导电滑环,用能承重的导线来吊着浮球开关,这样就能避免绕线问题(DIY难度会更大,得考虑更多机构问题)。</p>
<p>这个简易版的伺服液位计可以独立于这个项目用于其他地方,采用RS232通信,通信协议在第5章中。</p>
<p> </p>
<p>在此之前,我还用过 超声波测距 和 测气压 的方法来测量水位。</p>
<p>超声波测距用的是防水的超声波测距模块,如下图,虽然价格只要十几块钱,</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/cHsflxDcMbeLLVECg3GBi9U7N0FWZgbeygpMyBWn.jpeg" width="252" height="207"></strong></p>
<p>但是这种超声波测距模块发射的超声波是发散的,这就导致它的正常测量范围会受限于探头到两边障碍物的距离,如下图所示</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/auNf0RXApCYpx8KgwJUAQf1ewNpukSevo3zoprEK.png" width="384" height="553"></strong></p>
<p>能准确测量的极限水位就是上图中的水位2,只有水位高于水位2如水位1,才能准确测量;当水位 低于水位2 时如水位3,测量结果就一直是<strong>水位2的测量结果(因为超声波碰到水塔壁就返回去了)</strong>;这个水位2的位置,取决于超声波探头所发射的超声波的<strong>发散角度</strong>和水塔的<strong>直径</strong>;实测1.2m深的不锈钢水塔无法用上图中的那种超声波测距模块(实测过这种模块确实无法测量,上述原因是个人推测,如有误欢迎大家指出)。</p>
<p> </p>
<p>除了这种便宜的超声波测距模块,tb上有专门的测量水位用的超声波传感器,如下图,这种传感器发射的超声波发散角度应该很小,所以可以用来测量水位,但是价格很贵,不适合DIY</p>
<p> </p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/iYCgMnutSUZb88pIWNWx5o9IwN6x3rF26pyPKiLI.png" width="214" height="223"></strong></p>
<p> </p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/7hYNL592MVG3pErUIiLKSyfdte4JR8VbMVg4tFlb.png" width="214" height="216"></strong></p>
<p> </p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/SwfFX8IKoSwBjbP1GBfImk3MULgIuKFEjMBGjVk3.png" width="266" height="186"></strong></p>
<p> </p>
<p>气压方案就是用一根软管伸到水塔底部,测量软管中的气压,随着水位变化,气压也会随之变化;气压方案在验证阶段确实没问题,水位随气压线性变化,所以我做的第一版就是用的气压方案,</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/RrgVG8OklJa97qjUVyT8DD1M7K0vMjQmKwaUNh8S.jpeg" width="519" height="388"></strong></p>
<p>当时在家做完安装好,就正好寒假结束返校了,但我到学校后立马就出问题,白天水位还是80%,没有用水,到晚上一看水位变成了63%,这是由于昼夜水温变化导致的,做的时候没用考虑到压强会随温度变化;就这样一个学期 整个系统都没法工作,因为当时太阳能热水器水位测量也是用的这种方法(捂脸),水塔和太阳能热水器都没法正常上水,我在学校无法回家解决,给爸妈造成了很大的困扰(天天被他们数落),这也是为啥这次设计时把<strong>系统稳定</strong>放在第一位的原因。</p>
<p>关于气压测水位,需要加入温度等其他因素来校准,我个人觉得用那种便宜的气压模块还是比较难实现的,所以第二版就放弃了这个方案。</p>
<p> </p>
<p><strong>4.2.1 简易伺服液位计基本特性</strong></p>
<p><strong><span style="color: #95a5a6; font-size: 14px;"> </span></strong><span style="color: #95a5a6; font-size: 14px;"> 供电电压12V;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 采用RS232通信;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 实现0%~100%水位量化;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持一键校准;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 测量精度高,测量误差不超过2cm(取决于用的浮球开关);</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 测量范围大(适用于两米以下的不锈钢水塔或者水井);</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持断电水位记忆(就算断电后水位变化,下次上电仍能准确测量);</span></p>
<p> </p>
<p><strong>4.2.2 硬件框架</strong></p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/PoXGAH6mGuCDFpNwwZkStm7GdPRi8n3Aa9D7Mw81.png" width="662" height="349"></strong></p>
<p>单片机外设资源分配如下:</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/tL33zrskTNCYtKZC6uKQRi9qnelTzjlXAKn9Iy5K.png" width="1002" height="167"></strong></p>
<p><strong>4.2.3 原理图及PCB</strong></p>
<p><strong> </strong>主控还是用的Air001单片机,单片机总共20个引脚,减去晶振两个、复位一个、boot一个、电源两个、程序下载两个,还剩12个可用GPIO,电机驱动用掉2个引脚、编码器用掉2个引脚、eeprom用掉2个引脚、rs232用掉2个引脚、两个浮球开关用掉2个引脚、调试串口用掉2个引脚,这里刚好就把所有GPIO都用完了,之所以需要用串口来调试,是因为我发现这个单片机在MDK5中虽然能进debug,<em>但是寄存器或者变量中的值无法实时检测</em>,所以我就用了串口调试;但是为了方便水位校准,我们还需要一个校准按键,GPIO不够了怎么办?我们可以只使用调试串口的TX引脚,RX引脚可以用来检测按键,但这种方法就不能在调试时改变运行参数;所以我这里用的方法是将调试用的串口引脚在程序上进行分时复用,也就是需要校准的时候就将其中一个引脚设置为普通GPIO,不需要校准时就设置为调试串口,我们只需要在单片机刚上电运行程序的时候校准,之后就能作为调试串口使用。这样说可能比较难理解,结合下面的校准方法应该就好理解了:</p>
<p><strong><span style="color: #95a5a6; font-size: 14px;"> </span></strong><span style="color: #95a5a6; font-size: 14px;">校准方法:在断电的情况下,用手转动绳盘将浮球开关收回来,最后浮球开关的位置就代表水塔水量100%(要低于保险浮球开关的位置),</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 然后按住校准按键,再通电,通电后松开按键,浮球开关会自动下降,降到你认为的代表水塔水量0%的地方后,再按一下校准按键就完成</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 了校准。在校准过程中,水塔中最好不要有水,校准完成后不需要重新上电。</span></p>
<p>从校准方法中可以看出,只需要在上电瞬间检测校准按键有没有被按下就行了,之后就能一直作为调试串口使用。</p>
<p> </p>
<p>需要注意Air001单片机虽然有4个通用定时器和1个高级定时器,如下图</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/9YfqDBynl7rV0t0QWLQu6GmgnVRpcqCcHx9L07C5.png"></strong></p>
<p>按理来说,通用定时器以上的定时器都有编码器功能,但是实测这个单片机中有编码器功能的定时器只有TIM1和TIM3,这个手册中没说明,就有点坑,好在我是先用最小系统验证后才画的板;另外编码器功能都是会指定通道的,正如STM32的CubeMX在配置编码器模式时,就会自动指定通道,经测试TIM1和TIM3的编码器功能都是ch1和ch2生效,ch3和ch4不生效,ch1和ch2的多个复用引脚都是可以的;</p>
<p> </p>
<p>电机驱动用的是RZ7899,这个芯片在我之前的平衡摆项目中也用过,体积小、价格便宜、驱动电流大,外围电路简单,如下图,用过的都说好,在玩具车中很常见,唯一的缺点就是驱动的PWM频率不能太高,否则发热严重。</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/Sgn1aRJAc7dBymw99ECZoox5Nsudcz6CGN05C1dX.png"></strong></p>
<p>PCB部分在电机正上方的位置做了挖槽处理,如下图,为了避免电机对电路板产生干扰</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/rgkVbhBhuDJQc7R5DLB5qGWfrsprSA2wUc1gDdNX.png" width="444" height="518"></strong></p>
<p>PCB电路板固定在密封盒的盖子上,电机固定在盒体内,实物如下图(电机只是随便放在盖子上,并不是安装在盖子上)</p>
<p><strong><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/77Ap0MfIs6H5orKJwFoC2oaqTOC0OdeiKsKoUPwP.png" width="643" height="432"></strong></p>
<p style="line-height: 1.8;"><strong> 4. 3 水塔上水控制模块</strong></p>
<p style="line-height: 1.8;">在第1章中有提到,水塔上水控制模块并不单单只控制水泵的开关,还能显示一些数据,并且可以对系统进行一些操作;在我做的第一版中,这个模块只有控制水泵开关的功能,没有任何按键,也不能显示任何数据,我爸妈反馈说每次抽水、看水温水量都需要打开微信小程序,觉得麻烦,所以第二版中这个模块就增加了一些功能。</p>
<p style="line-height: 1.8;">这个模块和微信小程序一样是直接给用户使用的,所以我在设计的时候就是奔着 <strong>简单易用 </strong>去的,搞太复杂了爸妈教不会。显示部分用的是3位0.8英寸数码管,不用屏幕是因为太小的屏他们看不清楚,大屏价格贵;按键部分我尽可能的多做了一些按键,对应不同的水位,按不同的按键就能抽水到不同的水位;有一个专门的按键用来停止抽水;实现了一键抽水、一键停止抽水、一眼看到水量,这三个基本功能还算是做到了简单易用的,其他功能对我爸妈那个年纪的人来说就稍微有点复杂(这个没办法,有限资源下只能这样),具体说明如下:</p>
<ul>
<li>模块上电默认显示不锈钢水塔水量,当水量低于8%时蜂鸣器间隔10分钟报警(提醒用户抽水);如下实物图,六个并排的按键从左往右分别为S1、S2、S3、S4、S5、S6,上面单独的一个按键为S0,S0的功能为 停止不锈钢水塔上水;</li>
<li>短按S1,数码管会闪烁显示"<strong>20.</strong>"(最后一位数码管的小数点会点亮),在闪烁显示的过程中,再次按下S1,就会开始往不锈钢水塔抽水,直到其水量达到20%停止,若在闪烁的过程中,无任何操作,5秒后,将会恢复原样;</li>
<li>短按S2,数码管会闪烁显示"<strong>40.</strong>"(最后一位数码管的小数点会点亮),在闪烁显示的过程中,再次按下S2,就会开始往不锈钢水塔抽水,直到其水量达到40%停止,若在闪烁的过程中,无任何操作,5秒后,将会恢复原样;</li>
<li>短按S3、S4、S5、S6功能同S1、S2,不同的是它们分别代表50%、70%、80%、100%水位 。</li>
<li>数码管在闪烁显示的过程中可以通过短按其他按键来改变选中的值。</li>
<li>上述是按目标水量抽水,除此之外还可以按目标时间抽水,比如抽水10分钟后停止,按目标水量抽水和按目标时间抽水的切换按键为S6,长按S6(数码管闪烁时,长按无效)2.2秒,直到蜂鸣器响一下,就代表切换成功,当从按目标水量抽水切换到按目标时间抽水后,</li>
<li>短按S1,数码管会闪烁显示"<strong>10</strong>"(最后一位数码管的小数<strong>不</strong>会点亮),在闪烁显示的过程中,再次按下S1,就会开始往不锈钢水塔抽水,直到抽水10分钟后停止,若在闪烁的过程中,无任何操作,5秒后,将会恢复原样;</li>
<li>短按S2,数码管会闪烁显示"<strong>15</strong>"(最后一位数码管的小数<strong>不</strong>会点亮),在闪烁显示的过程中,再次按下S2,就会开始往不锈钢水塔抽水,直到抽水15分钟后停止,若在闪烁的过程中,无任何操作,5秒后,将会恢复原样;</li>
<li>短按S3、S4、S5、S6功能同S1、S2,不同的是它们分别代表20min、25min、30min、35min。</li>
<li>同样,数码管在闪烁显示的过程中可以通过短按其他按键来改变选中的值。</li>
<li>不管何种形式的抽水,只要水塔水量达到100%、或者水池中无水(水泵的水来源,有一个浮球开关来检测是否有水),都会立即停止抽水。</li>
<li>长按S1(数码管闪烁时,长按无效)2.2秒,直到蜂鸣器响一下,数码管就会显示太阳能热水器里面的<strong>水温</strong>,松开后,又会恢复原样(显示水塔水量),如果长按2.2秒后,继续按住不松开,持续5秒,蜂鸣器就会再响一下,紧接着会向太阳能热水器控制模块发送关闭太阳能热水器电加热的指令。</li>
<li>长按S2(数码管闪烁时,长按无效)2.2秒,直到蜂鸣器响一下,数码管就会显示太阳能热水器里面的<strong>水量</strong>,松开后,又会恢复原样(显示水塔水量),如果长按2.2秒后,继续按住不松开,持续5秒,蜂鸣器就会再响一下,紧接着会向太阳能热水器控制模块发送关闭太阳能热水器上水电磁阀的指令。</li>
<li>此模块引出了两个LED(实物图中圈出来的部分),分别为粉色和蓝色,分别代表太阳能热水器上水状态和电加热状态。</li>
<li>实物图如下,因为这个模块是装在室内,就没有考虑外壳</li>
<li><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/N5yQ91VBZa1deM0tqtbqVDM6mHpdZj1SDsmvK0Qv.png" width="539" height="437"></li>
</ul>
<p><strong>4.3.1 基本特性</strong></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 5V电源输入</span><span style="color: #95a5a6; font-size: 14px;">;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 能显示不锈钢水塔水量;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 能显示太阳能热水器水温和水量;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 能指示太阳能热水器水箱上水状态和电加热状态;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 能按时间或者按目标水量抽水;</span></p>
<p><span style="color: #95a5a6; font-size: 14px;"> 支持抽水过程中缺水检测;</span></p>
<p> </p>
<p><strong>4.3.2 硬件框架</strong></p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/3uRlDplw7urDUiNnriAbCPeblOQsEMnn30jKvvpo.png" width="741" height="398"></p>
<p><strong>4.3.3 原理图及PCB</strong></p>
<p> 三位数码管驱动用了一块MAX7219来节省单片机引脚,MAX7219占用3个引脚;继电器、蜂鸣器、1个浮球开关、1个ADC通道、2个指示灯、LoRa模块共占用8个引脚,在4.2.3中有提到Air001单片机可用的引脚就12个,将程序下载引脚也复用为普通GPIO使用,所以总共可用的引脚就有14个,除去上述用掉的11个引脚,还剩3个引脚,下面的电路实现了用3个引脚(S0、S1、S2)检测7个按键</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/mErBQ3bSX04Szr2Gpfil2vMSlhbvV6IeN1QiSUij.png" width="321" height="534"></p>
<p> </p>
<p>原理图部分如下</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/KOMQRYexW4mNtKIN4GuHAJSOI59HJfmauxcmbOJ8.png" width="1017" height="718"></p>
<p> </p>
<p>当时第二版设计之初,是想一板多用,用一块电路板来实现 水塔上水控制模块和太阳能热水器控制模块,所以便有了上面较复杂的原理图。</p>
<p>PCB丝印上比较适合我爸妈的口味(捂脸)</p>
<p> </p>
<p><strong> 4.4 中继模块</strong></p>
<p>由一个LoRa模块(HC-14)与一个ESP8266组成,直接将HC-14的串口连接到ESP8266的串口上就行了,中继模块安装在靠近路由器的地方,只负责数据的转发,不需要用引脚控制其他东西。有了中继模块,后续还可以将家里面的灯、插座、窗帘等接入到系统中来。</p>
<p>中继模块太简单,买一块ESP8266 NodeMCU板子加一块HC-14焊接到洞洞板上就行了,不需要打PCB板。</p>
<p>介绍视频如下</p>
<p>【ESP12F中继模块】 <a href="https://www.bilibili.com/video/BV1xaxjejEZL/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6" target="_blank">https://www.bilibili.com/video/BV1xaxjejEZL/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6</a></p>
<p> </p>
<p> </p>
<h3 style="line-height: 1.8;">*5、软件部分</h3>
<hr>
<p style="line-height: 1.8;"><strong> 5.1 模块间通信协议</strong></p>
<p style="line-height: 1.8;">涉及多个模块之间通信的项目,在写程序之前(可以先把驱动部分写好),应该先明确需求,搞清楚各个模块之间要传哪些数据,充分考虑需求之后,再设计各个模块间的通信协议,将其写成文档或者做成表格,然后再根据规定好的通信协议去写程序,效率会高很多,并且能更好的应对后续出现的问题或者增加新的功能。我这里是将通信协议做成说明文档,如下图:</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/78bdb8a6c3954b67b9deaf0590350c4d.jpg" width="1205" height="678"></p>
<p style="line-height: 1.8;">本项目通信框图如下,需要注意的是,本项目中用的LoRa模块是没有节点机制的,也就是任意一方发送数据,其他两方都能接收到,在程序设计时需要考虑这点;另外如果一方在发送数据时,另一方也开始发送数据,这样可能会造成LoRa模块通信故障,所以在设计通信协议时要避免这个问题(任意时刻不能出现两个模块同时发送数据);除此之外传输的数据帧必须加上<strong>校验码</strong>、重要的指令需加入<strong>应答机制</strong>。</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/ce296c8e04064a5690367feb2f443e55.png" width="1240" height="404"></p>
<p style="line-height: 1.8;"><strong> 5.2 太阳能热水器控制模块</strong></p>
<p style="line-height: 1.8;">此模块程序中开启了看门狗,以提高系统稳定性。</p>
<p style="line-height: 1.8;"><strong>5.2.1 四线太阳能热水器传感器驱动</strong></p>
<p> 这种传感器本质上就是可变电阻,水位水温变化会引起电阻变化,用单片机ADC采集传感器的分压值,然后将ADC采集到的值转换成我们需要的水位和水温就行了;原理简单,难就难在怎么找到对应的转换关系。</p>
<p>难点:如何将ADC值转换成我们需要的目标值;tb上卖这种传感器的商家基本上不会提供有用的数据,网上的资料也很少,基本找不到,所以要想使用这种传感器,就必须要我们自己测量数据进行校准,找到对应的转换关系。</p>
<p>传感器实物图如下</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/u3MTXUQyuLmwzLrBdn71TMN8LTeOEninnN6i1mMD.jpeg" width="455" height="349"></p>
<p style="line-height: 1.8;"><strong>水位传感器校准(红蓝线)</strong></p>
<p style="line-height: 1.8;">太阳能热水器水位传感器,是将水位分成5段,分别代表水位0%、20%、50%、80%、100%;我们只需要四个阈值就能划出0%、20%、50%、80%、100%五段水位,如下图所示;ADC采集到的水位传感器分压值如果小于阈值1,就认为当前水位为0%、如果大于阈值1并小于阈值2,就认为当前水位为20%、以此类推。</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/0f5I2nJngcVXUJg72EOfCTWmBJEQFvAGtCRrjEYO.png" width="352" height="491"></p>
<p style="line-height: 1.8;">那么我们怎么准确的找出这四个阈值呢?这四个阈值会不会随水质、水温、容器(金属和非金属)的变化而变化呢?</p>
<p style="line-height: 1.8;">我的测试方法是写一个单片机程序,让单片机以400ms周期采集水位传感器ADC值,输出到串口助手;这样我们只需要在不同的水质下、不同的水温下、以及不同的容器中,让单片机运行程序,同时将水位传感器缓慢放入水中,至完全浸入水中,再缓慢拿出来,至完全离开水,然后将串口助手收到的数据导入Excel中,这样就能绘制出不同 条件 下测出的数据曲线图。</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/9e4d77b4f06e479fae87d449f52b7364.png" width="1104" height="579"></p>
<p style="line-height: 1.8;">上图的曲线中不同的颜色代表不同的条件,可以看出,每条曲线基本都是呈5段的趋势,并且不同情况下,这5段对应的值都比较靠近,也就是说存在四个阈值,能实现不同温度下、不同水质下、不同容器中的五段水位划分。根据曲线图找出四个阈值,如下所示(阈值真实值在附件程序中有)</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/STj42hlkqsjEIbwAQgsIP4F67udPmJYCHve19vWE.png" width="883" height="456"></p>
<p style="line-height: 1.8;">不同条件下测试过程</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/7aEItq3IPerOcQpqqWRiuoRNKWxNI4ef0A0cYShC.jpeg" width="854" height="377"></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/hszeplCiqQzDIgJnquttb1n6v3B3zH8o7z7rbKWg.png" width="952" height="701"></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><strong>水温传感器校准(白绿线)</strong></p>
<p style="line-height: 1.8;">经测试发现,这种太阳能热水器的温度传感器是一种正温度系数的传感器,但是对测量的阻值分析,发现不符合常见的PT100、PT1000等热敏电阻,所以我们没法得出阻值与温度的关系;好在我们对水温的精度要求不高,所以我们可以用一种能正确测量温度的传感器,来校准这个水温传感器,我这里用来校准的温度传感器为10K的B3950 NTC热敏电阻(负温度系数);</p>
<p style="line-height: 1.8;">我的校准方法是将 B3950 NTC热敏电阻 和这个 待校准的温度传感器 一起放在装满冷水的电水壶中,如下图</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/ewzsgYhK7papfj9bI6cHKbfL71ul5zrpwG11jxQe.png" width="904" height="544"></p>
<p style="line-height: 1.8;">然后用单片机从串口以400ms的周期输出同一时刻下,从B3950热敏电阻测量的<strong>温度</strong>和从太阳能热水器水温传感器测量的<strong>ADC原始值</strong>,如下图(第一个数据为NTC热敏电阻测量的温度,第二个为采集到的水温传感器原始ADC值,第三个为水量传感器ADC值)</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/52e387d2eb894ac2a2097b2ce1c0536f.png" width="380" height="401"></p>
<p style="line-height: 1.8;">然后打开电水壶加热,持续将水温加热到76度,然后停止加热;这样我们就得到了不同 <em>真实温度</em> 下 <em>水温传感器ADC原始值</em>,设 <em>真实温度 </em>为y,<em>水温传感器ADC原始值 </em>为x,现在我们只需找出x与y的关系式,就能根据 <em>水温传感器ADC原始值</em> 求出 <em>真实水温</em>。我们将采集到的所有 <em>真实温度</em> 与 <em>水温传感器ADC原始值</em> 导入到线性回归分析工具中,如下图</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/2adfb5412b7f4916afdbcc7485c1e563.jpg" width="1855" height="996"></p>
<p style="line-height: 1.8;">从图中可以看出 <em>真实温度值</em> 与 <em>水温传感器ADC原始值</em> 基本呈线性关系,拟合误差只有0.99;同时线性回归分析工具也给出了对应的函数,这样我们就得到了 <em>温度</em> 与 <em>水温传感器原始ADC值</em> 的关系式,如下</p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/736f91b0c3bc48a1a1c8330294e51790.png"></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><strong> 5.3 不锈钢水塔水位检测模块</strong></p>
<p style="line-height: 1.8;">此模块程序中开启了看门狗,以提高系统稳定性。</p>
<p style="line-height: 1.8;">校准方法和水位测量原理都在4.3中有说明,此模块的程序就是根据测量原理来写的,源码在附件中。</p>
<p style="line-height: 1.8;">需要注意Air001单片机TIM1在编码器模式下存在一些BUG,在TIM1->CNT=40或者其他值 的时候也会触发更新中断,就很无语,定时器配置部分是没问题的,并且各种配置都试了,就是存在这个BUG,不相信的可以自己去试(要在编码器模式下)。好在我这里测量的深度以及电机的减速比都不是很大,电机转动,将浮球开关从水塔顶部下放到水塔底部,编码器输出的个数远小于65535,不至于触发更新中断,所以就没有用到更新中断;如果要测量十几米的范围(不锈钢水塔才1.2米),那肯定需要用到更新中断,不建议用air001这个单片机来做。</p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;">此模块采用RS232通信,波特率9600,通信协议如下</p>
<p style="line-height: 1.8;"><img src="//image.lceda.cn/pullimage/9Gp5eNZaOFzNIg1uFVf6UigFabFjVGYTm3THHixN.png"></p>
<p style="line-height: 1.8;">0x2代表上水,0x0代表用水,"复位故障"为0x1代表复位传感器故障(传感器故障就是保险浮球开关触发了,但是编码电机测量出的水位没有到100,就会产生故障信息)校验和为:("上水/用水"+"复位故障")%256</p>
<p style="line-height: 1.8;">模块一旦接收到上述数据,就会返回下面的数据</p>
<p style="line-height: 1.8;"><img src="//image.lceda.cn/pullimage/KrOYq7NgpQAfrNZkm0LrpmZExSaGWteXoLPNxgEj.png"></p>
<p style="line-height: 1.8;">校验和为:("水量"+"故障信息"+"MCU温度")%256</p>
<p style="line-height: 1.8;"><strong> 5.4 水塔上水控制模块</strong></p>
<p style="line-height: 1.8;">此模块程序中开启了看门狗,以提高系统稳定性。</p>
<p style="line-height: 1.8;">这个模块涉及到数码管闪烁、按键短按、按键长按等,这些实现思路不好说,我觉得不同的人思维不一样,实现方法就不一样,实在不会的可以参考我源码中的方法,我觉得这种方法不是固定的,下次让我写可能就不是这样来实现;</p>
<p style="line-height: 1.8;">对于刚入门的单片机小白,怎么锻炼这种基本的编程逻辑,我建议可以去参加那个蓝桥杯的单片机赛项,这个赛项基本不考察单片机外设,重在考查这种逻辑思维。那玩意虽然就是一个圈钱杯,但是挺适合大一的单片机小白去参加的,我就是大一下学期参加的,那时是第十二届,我觉得参加这个对我后面的学习是有帮助的。</p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><strong> 5.5 中继模块</strong></p>
<p style="line-height: 1.8;">此模块没有用到看门狗,为了系统稳定,我在供电部分加了一个控制板,让中继模块定时断电(隔3天断电60秒)。</p>
<p style="line-height: 1.8;">这个模块没有开源源码,所以就贴上一点关键代码。<strong>(源码已开源10/20更新)</strong></p>
<p style="line-height: 1.8;">本项目没有用到数据库,预约事件、近24小时历史水温、近24小时历史水量、运行模式、阈值温度、执行提前 这些数据都保存在中继模块中;用户信息保存在微信小程序代码的常量中(目前就7个用户);</p>
<p style="line-height: 1.8;">因为数据都保存在中继模块中,所以中继模块的程序 关键部分之一 就是如何对数据进行存储,先看一下用来存储数据的EEPROM是怎么初始化的:</p>
<div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> /*存储结构如下</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 0,1: 是否是初次上电 0x88、0x99说明不是初次上电</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 2,3: 运行模式与阈值温度(真实的)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 4: 预约事件总数(真实的)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 5,6: 序号(0开始)与温度(真实的)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 7,8,9,10 预约时间戳(7是高位,10是低位)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> ...</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 重复5,6,7,8,9,10直至305</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 其他 保留</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 307:执行提前</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 308:太阳能热水器自动上水开关</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 其他 保留</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 310~358:历史水温(最后一个是最新的数据)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 其他 保留</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> 360~408:历史水量(最后一个是最新的数据)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> */</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.begin(410); </span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t dat_0 = EEPROM.read(0);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t dat_1 = EEPROM.read(1);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(dat_0 == eeprom_flg1 && dat_1 == eeprom_flg2){ //说明不是第一次上电</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> tyn_auto_ss = EEPROM.read(308);//太阳能热水器是否自动上水</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> zxtq = EEPROM.read(307);//执行提前时间</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yx_mode = EEPROM.read(2);//运行模式</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> auto_temp = EEPROM.read(3);//阈值温度</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yy_event_num = EEPROM.read(4);//预约事件总数</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t i=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> for(;i<50;i++){//同步event_ram与event_eeprom。我这里用的存储方式最关键的一点就是任何时候都同步event_ram与event_eeprom</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yy_event_list[i].xh = EEPROM.read(5+i*6);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yy_event_list[i].temp = EEPROM.read(6+i*6);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yy_event_list[i].time = EEPROM.read(7+i*6)<<24|EEPROM.read(8+i*6)<<16|EEPROM.read(9+i*6)<<8|EEPROM.read(10+i*6);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> for(i=0;i<48;i++){//同步ls_sw_ram与ls_sw_eeprom 。同步ls_sl_ram与ls_sl_eeprom。</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> ls_sw[i]=EEPROM.read(310+i);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> ls_sl[i]=EEPROM.read(360+i);</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }else{ //说明是第一次上电</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(0,eeprom_flg1);EEPROM.write(1,eeprom_flg2);//修改标志位</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(307,zxtq);//执行提前时间</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(308,tyn_auto_ss);//太阳能热水器是否自动上水</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(2,0);EEPROM.write(3,55);//运行模式,默认预约模式,阈值温度55</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(4,yy_event_num);//预约事件总数为0</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t i=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> for(;i<50;i++){</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(5+i*6,0xFA);//清空所有预约事件,在序号处填0xFA代表此位置为空</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> yy_event_list[i].xh = 0xFA;//同步event_ram与event_eeprom</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> for(i=0;i<48;i++){</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(310+i,1+i);//置零所有历史水温</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.write(360+i,1+i+4);//置零所有历史水量</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> ls_sw[i]=i;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> ls_sl[i]=i+4;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> EEPROM.commit();</span></div>
</div>
<p style="line-height: 1.8;">从上面代码中可以看出申请的410个字节的EEPROM上,存储的数据结构是怎样的,存储结构就不画图示意了;关于预约事件,我这里没有用链表来实现,就是用数组来实现的,如下图</p>
<div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;">struct yy_event{</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t xh;//预约者序号,为0xFA说明此位置为空,它的范围从0开始</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint8_t temp;//预约温度,这里的温度是真实温度,范围40~80,因为发过来的就是这个范围</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> uint32_t time;//预约时间戳</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;">};</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;">uint8_t yy_event_num=0;//预约事件数</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;">struct yy_event yy_event_list[50];//最多50个预约事件,此数组与存储预约事件的eeprom同步</span></div>
</div>
<p style="line-height: 1.8;">规定一次性最多50个预约事件,这样实现起来相对简单。</p>
<p style="line-height: 1.8;">添加预约事件的代码如下</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/ZKxSg5xlPOD8wGs2d5QT1AdVkHK76wiVRQcvHgUn.png" width="1512" height="534"></p>
<p style="line-height: 1.8;">删除预约事件的代码如下</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/vBTttTfp38BcJL5O8ql81x3blOxpxrVXrITuSRgq.png"></p>
<p style="line-height: 1.8;">中继模块向小程序客户端更新预约事件代码如下</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/TFsAtXOBGTHQpY4X7nOfHJ3N90gHzHIB0D8sVQ3w.png"></p>
<p style="line-height: 1.8;">记录历史数据、向微信小程序更新历史数据也比较关键,限于篇幅,就不放出来了。</p>
<p style="line-height: 1.8;">从上述代码中可以看出,预约事件的 存储 是用时间戳来实现的,因为ESP8266无法将时间戳解析成年月日时分秒的形式(试过一些方法,没有实现),所以预约事件的执行也是按时间戳的方式来执行的,好在能从阿里云物联网公共实例直接获取当前时间戳。</p>
<p style="line-height: 1.8;">而一些定时操作,比如每天凌晨1点太阳能热水器定时上水、每0分或30分记录一次历史数据,这些需要用真实的时分秒才能实现,这里用的是 WiFiUdp.h 这个库来获取真实的时分秒。</p>
<p style="line-height: 1.8;"><strong> 5.6 微信小程序</strong></p>
<p>微信小程序名称:那年那月那天9<br>不要问为啥搞这个名字,问就是太阳能热水器相关名字都被占用了(捂脸)。</p>
<p>小程序需要输入密钥验证成功后才能控制我家的设备,为什么用这种原始的验证方式,而不用手机号码一键验证用户信息?因为凡是涉及到用户隐私的,微信官方审核非常严格,我作为个人开发者,没有任何资质担保,就别妄想获取用户的手机号码了。可能有人会问,为啥要验证才能使用?因为微信小程序本身是开放的,人人都能用,如果不需要验证,那我家的设备岂不是所有人都能随便控制了。所以需要复刻的朋友,需要联系我创建密钥(不收费,但是你得自己去注册阿里云物联网公共实例,并创建设备)。</p>
<p> </p>
<p>微信小程序共四个页面,如下图,从左往右分别是实时数据页面、操作页面、历史数据页面、拓展页面</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/6Ke7mq2GxQ1vJFIleB9m7aWsPuUZKYHZv2N3zRfB.png"></p>
<p>乍一看可能会觉得比较复杂,其实多数功能和数据不需要关心,需要人为干涉的就只有两个,一个是往不锈钢水塔上水,另一个是预约事件;太阳能水箱会自动上水(不锈钢水塔水量大于8%时,每天凌晨1点自动上水到100%);太阳能电加热会根据预约事件和实时温度以及”执行提前“来自动开启,”执行提前“就是 实时数据页面 中显示的第一个参数,它的作用就是决定预约事件提前多久执行,比如我预约18点洗澡,执行提前为3h时,太阳能热水器就会在15点开启电加热(如果水温大于你预约的温度就不会开启电加热),这个参数在操作页面中可以修改,并且断电不丢失。历史数据页面中的图标组件显示的是太阳能热水器水箱的历史水温和水量,可以显近24小时的数据,每0分或30分时自动更新;历史数据页面下方的四个按钮是用来复位故障信息的,当产生故障后,管理员将故障排除后,就能用这些按钮来清除故障信息。在拓展页面中,可以看到路由器2.4G芯片、路由器5G芯片,和光猫芯片的温度,页面最上面可以选择 路由器和光猫的自动散热是否开启(这个已经做好了,如下图,暂时没接入进来);</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/xPTvPzQpDk4WB1E8BaHRTUncHO0K0ASJAIBA4tTB.png"></p>
<p>拓展页面下方有一个系统报错信息栏,当系统出现故障后,这里对应的故障信息会变成红色,同时绿色的字体 “当前没有报错信息” 会变成灰色;这样方便用户及时看到系统的故障。</p>
<p> </p>
<p>微信小程序打开后第一个页面就是操作页面,用户需要在这个页面的最上面那一栏输入密钥(可以隐藏输入),验证成功后才能正常使用。预约事件时,需要选择日期和事件,还有你需要的温度,如下图,注意日期时间只能选择当前时刻之后的日期和时间,不能预约已经过去的时间</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/5z5RdvChdRSmr0GzCd3HJyz7sIfOiRJOIAyeVkPK.png" width="1260" height="857"></p>
<p>选择好你需要日期时间和温度后再点击预约按钮,会弹出对话框问再次询问,如下图</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/9oOmBrfVuReOIgTgfFMTn9kjsi48w2hRIiLEYu9a.png"></p>
<p>点击确定后,微信小程序就会向中继模块发送你的预约事件,中继模块收到之后,就会将你的预约事件存储到EEPROM中,然后立即返回当前所有的预约事件给小程序客户端,小程序客户端收到中继模块返回的所有预约事件之后,就会显示操作成功,如下图</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/fZjnzicnls6ly911jN0LDSSjuaxuHLY90K5slHsO.png"></p>
<p>如果超时未收到,小程序客户端就会弹出一个如下图所示的对话框来提醒用户操作失败</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/c2b4a233281547b0b6f7420e2525db99.png"></p>
<p>在操作页面,可以点击 ”查看或删除预约事件”按钮 来查看或删除预约事件,点击后会弹出一个预约事件列表,里面包含当前所有的预约事件,如下图(预约事件由 用户名字+具体日期时间+水温组成,一次性最多存在50个预约事件)</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://image.lceda.cn/oshwhub/50ff435422454cb58de57f67c2ba0741.jpg" width="835" height="684"></p>
<p>当预约事件被执行之后,会自动删除,同时我们可以手动删除选中的预约事件,但是不能删除别人的预约事件,只能删除自己的预约事件,删除预约事件时也会弹出对话框二次确认,如下图</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/zFzU1sghYaNaNlF7WtEXFDLd977Bg4t8AIFOwgEE.png"></p>
<p><strong>删除预约事件、添加预约事件、以及其他的所有操作,都加入应答了应答机制</strong>,如果由于网络原因或其他原因导致中继模块没有给微信小程序客户端产生应答,小程序客户端这边就会弹出对话框来提醒用户操作失败或者获取数据失败。</p>
<p>所有的操作都会弹出对话框二次询问是否执行操作。</p>
<p>在预约模式下,选择阈值温度的按钮为灰色,无法被点击,如下图</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/eB0Avun1ZkRRUOo2VgH2xBalHnsfowCzrbyiHDLC.jpeg" width="985" height="424"></p>
<p> </p>
<p>在自动模式下,预约相关的操作按钮都为灰色,都无法被点击,如下图</p>
<p> </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/BWbHwzPV8n2b2RprX1muUBPUIyrJKN4THrLOwHhC.jpeg" width="987" height="547"></p>
<p>其他还有很多,就不一一赘述了,更多有趣的功能等你复刻之后自己探索。</p>
<p> </p>
<p>实时页面数据中的仪表盘组件和历史数据页面中的表单组件,都是用ECharts来实现的。</p>
<p> </p>
<p>由于微信小程序源码未公开,所以这里贴上一些关键代码。<strong>(源码已开源10/21更新)</strong></p>
<p><strong>1.UI中图表和仪表盘部分的代码,给有需要做微信小程序图表和仪表盘的朋友参考</strong></p>
<p>(受限于篇幅,源码放在附件中——微信小程序UI组件源码.docx)</p>
<p><strong>2.小程序客户端解析中继模块发来的预约事件</strong></p>
<div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;">else if(payload[0]==0x95){//预约事件</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> var str = ''//'旭华 2024-02-13 10:00 40℃'</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> this.globalData.yuyueshijian_data=[]//清空原来的预约事件</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> for(i=0;i<(payload[1]-1);i++){//事件总数,那边是+1发过来的,为了防止有0</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> str=this.globalData.u_n_font[payload[i*7+2]-1]//序号也是+1发过来的</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> let sjc = 0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if((payload[i*7+3]&(0x08))!=0)sjc=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else sjc=payload[i*7+4]<<24;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if((payload[i*7+3]&(0x04))!=0)sjc+=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else sjc+=payload[i*7+5]<<16;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if((payload[i*7+3]&(0x02))!=0)sjc+=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else sjc+=payload[i*7+6]<<8</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if((payload[i*7+3]&(0x01))!=0)sjc+=0;</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else sjc+=payload[i*7+7];</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> let dt = new Date(sjc*1000)//这里传进去的单位要是ms</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if((dt.getMonth()+1)<10)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> str +=' '+dt.getFullYear().toString()+'-0'+(dt.getMonth()+1).toString()+'-'</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> str +=' '+dt.getFullYear().toString()+'-'+(dt.getMonth()+1).toString()+'-'</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(dt.getDate()<10)str += '0'+dt.getDate().toString()</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else str += dt.getDate().toString()</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(dt.getHours()<10)str +=' 0'+dt.getHours().toString()+':' </span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else str +=' ' +dt.getHours().toString()+':' </span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(dt.getMinutes()<10)str += '0'+dt.getMinutes().toString()</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else str += dt.getMinutes().toString()</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> str += ' '+payload[i*7+8]+'℃'//这里是真实温度,范围40~80</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> this.globalData.yuyueshijian_data=this.globalData.yuyueshijian_data.concat(str)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(this.globalData.esp_ack_enable)this.globalData.esp_ack = 2//中继执行完成应答</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> console.log("收到预约事件")</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div><strong>3.微信小程序与中继模块间,应答机制的实现</strong></div>
<div>
<div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wait_esp_ack:function(e) {</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> var tim_task1_count = 0</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> var that = this//下面的定时器里面无法用this</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> this.globalData.esp_ack_enable = true</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> this.globalData.mqtt_client.publish(this.globalData.mqtt_options.pubTopic,e[1])</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> var tim_task1 = setInterval(function(){//等待中继应答的定时器</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(that.globalData.esp_ack != 0){//判断中继模块是否应答(只要不等于0就肯定是应答了)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> clearInterval(tim_task1)//关闭等待应答的定时器</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> console.log("中继应答成功,tim_task1关闭")</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(e[0]==0)//是否需要可视化</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showToast({title:'数据获取成功',icon:'success',duration: 800})</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else if(e[0]==1)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showToast({title:'操作成功',icon:'success',duration: 800})</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> that.globalData.esp_ack = 0//一定要记得及时复位中继应答标志位</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> that.globalData.esp_ack_enable = false//正常退出后,也不允许对esp_ack赋值</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }else{</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> tim_task1_count+=1</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(tim_task1_count==32){//超过3.2s仍未应答,再向中继发送一遍</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> that.globalData.mqtt_client.publish(that.globalData.mqtt_options.pubTopic,e[1])</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> console.log("重新向中继发送命令")</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }else if(tim_task1_count==64){//又过了3.2s仍未应答,则此次通信失败</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> clearInterval(tim_task1)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(e[0]==0)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showModal({</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> title: '获取数据失败',</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> content: "中继无响应!您的网络或者中继的网络不稳定,请稍后重试",</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> showCancel: false,</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> })</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else if(e[0]==1)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showModal({</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> title: '操作失败',</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> content: "中继没有应答!您的网络或者中继的网络不稳定,请稍后重试",</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> showCancel: false,</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> })</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> console.log("中继无应答,tim_task1关闭")</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> that.globalData.esp_ack_enable = false//超时退出后,不允许对esp_ack赋值</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }else{</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> if(e[0]==0)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showToast({title:'加载数据中...',icon:'loading',duration: 100})</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> else if(e[0]==1)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> wx.showToast({title:'等待中继应答...',icon:'loading',duration: 100})</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> }, 100)</span></div>
<div style="padding-left: 80px;"><span style="background-color: #bfedd2;"> },</span></div>
<div><strong>4.微信小程序MQTT开发js库</strong></div>
<div>(受限于篇幅,源码放在附件中——mqtt.js )</div>
<div> </div>
</div>
</div>
</div>
<p>当时考虑过的上位机方案:<br>1.用Flutter做APP<br>2.用 HomeAssistant<br>3.微信小程序<br>这三个技术领域对我来说都是陌生的,我一开始选择的是HomeAssistant 方案,虽然不会,但是学就完了!当时跟着B站 王铭东 老师的视频在学,学到后面感觉用HomeAssistant限制太大,我想要的仪表组件、预约机制都不好实现(可能是我学的不深入),于是就重新选择了微信小程序开发,现在庆幸当时选对了,微信小程序开发对于新手来说真的非常非常友好,官方文档齐全,网上教程很多,非常容易上手;并且当你懂一点微信小程序开发之后,你还能了解到一些前端开发的知识,因为它们技术栈很相似,微信小程序的开发主要使用JavaScript、WXML(类似于前端的HTML)、WXSS(类似于前端的CSS)和JSON文件来构建界面和逻辑,这些都是前端开发中常用的技术。<br>当时寒假二十几天,就能手搓出现在这个微信小程序(没有任何前端知识基础)。我个人认为,学习一种新技术,要以面向<strong>应用</strong>的心态去学,这样会比较好上手;首先确定好你的应用中需要实现哪些功能,实现这些功能需要哪些细分的技术栈,然后去一一学习这些细分的技术栈,逐个击破,就能实现你的应用。</p>
<p> </p>
<p style="line-height: 1.8;"><strong> 5.7 阿里云物联网公共实列</strong></p>
<p style="line-height: 1.8;">阿里云物联网公共实列对个人非常友好,每个人可以免费创建一个能容纳50个设备的公共实例。</p>
<p style="line-height: 1.8;">创建完实例之后就是创建产品、创建设备、配置消息流转规则,这个网上有很多很详细的教程,贴一个我当初学习的视频</p>
<p style="line-height: 1.8;">【开源+手把手教学:微信小程序通过阿里云控制和接收单片机数据】 <a href="https://www.bilibili.com/video/BV1kj41117Rz/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6" target="_blank">https://www.bilibili.com/video/BV1kj41117Rz/?share_source=copy_web&vd_source=68337adbea96c8cef50403a4b2809df6</a></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;">*6、BOM清单</h3>
<hr>
<p style="line-height: 1.8;">LoRa模块选型,LoRa模块用的是广州汇承的HC-14,每个淘宝ID都能购买一次初学者套餐,12.8能买两片</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/ayflAKWKc4akNNw6ugsCYrkGkOePeqdAHj3ZB2Ok.jpeg" width="627" height="390"></p>
<p style="line-height: 1.8;">这个模块标称空旷地区可以传3000米,我试过 用一楼室内的模块与四楼的模块相互传数据,相互连续传了1万字节,丢失0个数据。</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/7cgcWiiwEL9KUDieqmR3V07BdwNhfn17tH1PK0jq.jpeg" width="349" height="620"></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;">太阳能热水器控制模块密封盒尺寸如下</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/tAIZX50EB3xpHyiCY6bYGBz7kqi15fYJQOKiaf5R.png" width="501" height="494"></p>
<p style="line-height: 1.8;">水塔水位检测模块密封盒尺寸如下,tb上很多卖这种防水盒的,照着这个尺寸买就行(买回来需要自己用钻头开孔)</p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/eo3AKYJFL55y4fKmQrFQTkg3W5E5p3fiB32oe7cm.png"></p>
<p style="line-height: 1.8;">其他元器件请从原理图中导出,电机选用直流12V 58转/分 带编码器的长轴(22mm)电机。</p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;">*7、大赛LOGO验证</h3>
<hr>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><img style="display: block; margin-left: auto; margin-right: auto;" src="//image.lceda.cn/pullimage/44CFLj6yAJTytC178IWjpMdbVq1GhFe78yXc8OQj.png"></p>
<p style="line-height: 1.8;"> </p>
<h3 style="line-height: 1.8;">* 8、演示您的项目并录制成视频上传</h3>
<hr>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><span style="color: #95a5a6; font-size: 14px;">视频要求:请横屏拍摄,分辨率不低于1280×720,格式Mp4/Mov,单个视频大小限100M内;</span></p>
<p style="line-height: 1.8;"><span style="color: #95a5a6; font-size: 14px;">视频标题:立创电赛:{项目名称}-{视频模块名称};如立创电赛:《自动驾驶》-团队介绍。</span></p>
<p style="line-height: 1.8;"> </p>
<p style="line-height: 1.8;"><span style="font-size: 14px;"><a href="/posts/de460543d4cf4dacb5f0326612455578" target="_blank">前往查看更多详情 ></a></span></p>
<p style="line-height: 1.8;"> </p>
评论(10)