在之前关于房间介绍的博文里提到过我有个很小的储藏间,与其说它是个储藏间不如说是个嵌在墙里的柜子。
这里以前可能真是个可以通往院子的门,靠院子一侧墙的厚度很薄,门上被房东加了泡沫塑料。这里一年四季都能保持在20度以下,但冬天不至于太冷,最低也不会低过10度,关上门里面又是一片漆黑。唯一的缺点是有时候湿度会很高,夏天顶上还发了霉,花了好久才清理干净。各种产品的包装上不都写着阴凉干燥处储存么,要是能解决湿度问题,这简直就是教科书式的储藏环境。说整就整,三十欧不到,搞了台很小的除湿机,拆开看过,里头就一半导体制冷片加风扇。除湿能力当然比不上动辄上千的大干燥机,不过这么小的空间,够用。
设备到我手上,自然都是要经过一番改造,让30欧的机器发挥50欧的价值。之前搞了不少单片机的项目,这波就是轻车熟路,直接改成智能储藏间。
祖传OLED显示温湿度,干燥机和LED照明的工作状态。目前显示湿度很高是因为拍照前手摸了传感器。
核心部分是这样,乍一看很乱,实则乱中有序,走线还是尽量的规整。关键问题是这说底还是租的房子没法随便钉钉子,线材都是靠装修胶带固定。
上方可以看到群晖的NAS,这里面现在是两个4T的酷狼。仔细看的话,在NAS左边还有个WD的Mybook。这个是黑五入手的12T的盘,本来打算买14T的,然而还在犹豫的时候就缺货了。12T也差不了多少,都是降速的企业级氦气盘。这个超大的硬盘现在用来备份用了快10年的移动硬盘里的数据,直接连在NAS上,非常方便。
现在所有盘都恢复了蓝色,非常舒服。
NAS右边的白盒子里是镜头和冷储存的硬盘,盒子里放了很多吸水硅胶,常年保持湿度在30左右。
墙上就是这套智能硬件的核心,贴在木柱子上的是个DHT22 温湿度传感器,下来是Node MCU版8266,之前用Wemo D1, 今天加了个屏幕,针脚不够了。
再往下亮红灯的是双路继电器模块和降压模块,很魔性的被我用热熔胶粘在一起。
地面上的插头是WD Mybook的供电,这个不太好看,但还是那个原因,别人的房子,不能乱打洞。
立在地上的那玩意儿就是干燥机,真的很小,对吧。
整体的感觉是这样,架子上层是存粮,罐头泡面这些保质期超过一年的我都放在这里,时刻准备开启御宅模式。食物之外的都是工具和DIY的材料,满满当当。单侧贴了LED灯带,这个可以RGB,但最后还是选择了白光,炫彩储藏间就太土嗨了。
这个柜子最大的难题是走线,墙上的洞是房东打的,另一侧是个插销。这个洞不大不小,刚好只能通过它这一根线,于是我只能另寻路子了。意外发现门角处有点空隙,线就通过这里走到外面。但他毕竟只是个缝隙,容不得那么多线,光是我着一根CAT7网线就给填上了一大半。我有想过直接弄个插排进来,问题在于最便宜的插排要六欧元,线还特别粗,再改线成本更高。嗯,没错,爷就是不想花这六欧。
我的解决方案是这样的,我把干燥机的供电线剪了,获得了一个9V 2.5A的电源,9V电源经过降压模块,输出5V给单片机和继电器供电,两个继电器再分别控制这个9V电源给led灯带还有干燥机。可以明显感觉到开关干燥机LED灯的亮度有区别,好在灯带和干燥机都不是什么精密玩意儿,不用太在意电源的稳定。
硬盘就不一样了,里头的数据都是无价之宝,NAS和硬盘盒的线我都没有改造,稳定大过天。
在装灯带之前,这个储藏间的照明用的是充电宝加USB灯,每次开门要按一下开关,关灯也要按一下。这个操作对于懒人极不友好,手臂重量少说有个一千克,每次把手举高半米按开关都要消耗5焦耳的能量,太浪费了。这个灯,他必须自动开启。
暂时用原始办法做了个关门检测,已经订购了单簧管模块,到时候就换成磁场检测,更耐用。
另一侧贴了点铝箔,单片机反正只要知道这儿接通了就行。
要是就到这儿,那顶多算是个准智能,8266都用上了,自然是要联网。
这次这个东西要长期运行,就没有去连数据库,能在本地网络中控制就够了。
最上面显示一下温度湿度数据,往下来的两个开关是灯和干燥机,最后一个AUTO是自动控制开启的开关,就是用来实现门和灯的联动,以及干燥机根据湿度的开关。关掉AUTO则可以手动控制,万一需要关门在里头维护的时候不至于灯开不起来,啊,那为什么非要用这个灯维护。好吧,其实还没讲完,为了让其他设备能更好的控制干燥机和灯,这里可以用http的get请求来直接操作,接口都留好了,顺便放个开关呗。
30 2 * * * curl -s "http://192.168.233.177/update?output=isauto&state=0"
30 22 * * * curl -s "http://192.168.233.177/update?output=isauto&state=1"
30 3 * * * curl -s "http://192.168.233.177/update?output=isauto&state=1"
30 6 * * * curl -s "http://192.168.233.177/update?output=isauto&state=0"
31 22 * * * curl -s "http://192.168.233.177/update?output=14&state=1"
31 3 * * * curl -s "http://192.168.233.177/update?output=14&state=1"
二级路由我刷了个Padavan,就当小半个服务器来用,设置Crontab命令来定时控制设备开关,就是要这么强行联动。
最后还是放上代码,这次代码没有太多需要解释的地方,网页服务器的部分是在是在网上代码的基础上改的,缝合了网页显示传感器数据和网页操作开关的代码。其他是自己写的。
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#define OLED_RESET 16 //D0
#define OLED_CLK 5 //D1
#define OLED_MOSI 4 //D2
#define OLED_DC 0 //D3
#define OLED_CS 2 //D4
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
const char* ssid = "FKUN_LAB";
const char* password = "bilibili233";
const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";
#define DHTPIN 13
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
float t = 0.0;
float h = 0.0;
const int relayPin1 = 14;
const int relayPin2 = 12;
const int doorpin = 15;
int isauto = 0;
int light = 0;
int Dehumidifier = 0;
int doorstat = 0;
AsyncWebServer server(80);
unsigned long previousMillis = 0;
const long interval = 2000;
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<style>
html { font-family: Arial; display: inline-block; margin: 0px auto; text-align: center; }
h2 { font-size: 3.0rem; }
p { font-size: 3.0rem; }
.units { font-size: 1.2rem; }
.dht-labels{ font-size: 1.5rem; vertical-align:middle; padding-bottom: 15px; }
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>FKUN_Archive_Control</h2>
<p>
<i class="fas fa-thermometer-half" style="color:#059e8a;"></i>
<span class="dht-labels">Temperature</span>
<span id="temperature">%TEMPERATURE%</span>
<sup class="units">°C</sup> </p>
<p>
<i class="fas fa-tint" style="color:#00add6;"></i>
<span class="dht-labels">Humidity</span>
<span id="humidity">%HUMIDITY%</span>
<sup class="units">%</sup>
</p>
</body>
<script>
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("temperature").innerHTML = this.responseText;
}
};
xhttp.open("GET", "/temperature", true);
xhttp.send(); }, 5000 ) ;
setInterval(function ( ) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200){
document.getElementById("humidity").innerHTML = this.responseText;
}
};
xhttp.open("GET", "/humidity", true);
xhttp.send();
}, 5000 ) ;
</script>
%BUTTONPLACEHOLDER%
<script>
function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
else { xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
xhr.send();
}
</script>
</html>
)rawliteral";
String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
buttons += "<h4>Light</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"12\" " + outputState(12) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Dehumidifier</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"14\" " + outputState(14) + "><span class=\"slider\"></span></label>";
buttons += "<h4>AUTO</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"isauto\" " + outputState(isauto) + "><span class=\"slider\"></span></label>";
return buttons;
}
return String();
//Serial.println(var);
if (var == "TEMPERATURE") {
return String(t);
}
else if (var == "HUMIDITY") {
return String(h);
}
return String();
}
String outputState(int output){
if(digitalRead(output)){
return "";
}
else {
return "checked";
}
}
void setup()
{
Serial.begin(9600);//串口波特率
dht.begin();
pinMode(relayPin2, OUTPUT);
digitalWrite(relayPin2, HIGH);
pinMode(relayPin1, OUTPUT);
digitalWrite(relayPin1, HIGH);
pinMode(doorpin, INPUT);
if(!display.begin(SSD1306_SWITCHCAPVCC)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
Serial.println("OLED Ready");
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 2);
display.println(F("FKUN Archive"));
display.display();
delay(1000);
display.setCursor(0, 15);
display.println(F("OLED........Ready!"));
display.display();
delay(500);
display.setCursor(0, 25);
display.println(F("SYSTEM......Ready!"));
display.display();
delay(500);
display.setCursor(0, 35);
display.print(F("WiFi info"));
display.display();
delay(100);
display.println(F(":"));
display.display();
delay(500);
display.setCursor(0, 45);
display.print(F("SSID:"));
display.display();
delay(200);
display.println(ssid);
display.display();
delay(500);
display.setCursor(0, 55);
display.print(F("PASSWORD:"));
display.display();
delay(200);
int cnct=10;
while(cnct--)
{
display.print(F("*"));
display.display();
delay(40);
}
delay(800);
Serial.print("Connecting WiFi...");
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 21);
display.print(F("Connecting WiFi..."));
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED){
display.display();
delay(500);
Serial.print(".");
display.print(F("."));
}
Serial.println("");
Serial.print("WiFi connected");
display.clearDisplay();
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(WHITE);
display.setCursor(5, 25);
display.print(F("Connected!"));
display.display();
delay(300);
display.clearDisplay();
display.display();
delay(100);
display.clearDisplay();
display.setCursor(5, 25);
display.print(F("Connected!"));
display.display();
delay(300);
display.clearDisplay();
display.display();
delay(100);
display.clearDisplay();
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(WHITE);
display.setCursor(5, 25);
display.print(F("Connected!"));
display.display();
delay(1000);
display.clearDisplay();
display.display();
delay(300);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 2);
display.println(F("IP address:"));
display.display();
delay(500);
display.setCursor(0, 15);
display.println(WiFi.localIP());
display.display();
delay(1000);
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html, processor);
});
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/plain", String(t).c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/plain", String(h).c_str());
});
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage1;
String inputMessage2;
if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
}
else {
inputMessage1 = "No message sent";
inputMessage2 = "No message sent";
}
Serial.print("GPIO: ");
Serial.print(inputMessage1);
Serial.print(" - Set to: ");
Serial.println(inputMessage2);
if(inputMessage1=="isauto"){
if(inputMessage2=="0"){
isauto = 1;
Serial.print(" - is AUTO: ");
Serial.println(isauto);
}else{
isauto = 0;
Serial.print(" - is AUTO: ");
Serial.println(isauto);
}
}
request->send(200, "text/plain", "OK");
});
// Start server
server.begin();
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
float newT = dht.readTemperature();
float newH = dht.readHumidity();
if (isnan(newH)) {
display.clearDisplay();
Serial.println("Failed to read from DHT sensor!");
display.setCursor(0, 25);
display.println(F("DHT Sensor Failed!"));
display.display();
}
else {
display.clearDisplay();
h = newH;
t = newT;
Serial.println(t);
Serial.println(h);
display.clearDisplay();
display.setCursor(0, 0);
display.print(F("Temperature:"));
display.setCursor(0, 10);
display.print(t);
display.println(F("C"));
display.setCursor(0, 20);
display.print(F("Humidity:"));
display.setCursor(0, 30);
display.print(h);
display.println(F("%"));
display.display();
}
light = digitalRead(12);
Dehumidifier = digitalRead(14);
if(isauto==1){
if(digitalRead(doorpin)== HIGH){
pinMode(relayPin2, OUTPUT);
digitalWrite(relayPin2, HIGH);
display.setCursor(0, 40);
display.print(F("Light:"));
display.println(F("OFF (A)"));
display.display();
}else{
pinMode(relayPin2, OUTPUT);
digitalWrite(relayPin2, LOW);
Serial.println("Door Open!");
Serial.println("Light ON");
display.setCursor(0, 40);
display.print(F("Light:"));
display.println(F("ON (A)"));
display.display();
}
if(h<45){
pinMode(relayPin1, OUTPUT);
digitalWrite(relayPin1, HIGH);
display.setCursor(0, 50);
display.print(F("Dehumidifier:"));
display.println(F("OFF (A)"));
display.display();
}else{
pinMode(relayPin1, OUTPUT);
digitalWrite(relayPin1, LOW);
display.setCursor(0, 50);
display.print(F("Dehumidifier:"));
display.println(F("ON (A)"));
display.display();
}
}else{
if(light==0){
display.setCursor(0, 40);
display.print(F("Light:"));
display.println(F("ON (M)"));
display.display();
}else{
display.setCursor(0, 40);
display.print(F("Light:"));
display.println(F("OFF (M)"));
display.display();
}
if(Dehumidifier==0){
display.setCursor(0, 50);
display.print(F("Dehumidifier:"));
display.println(F("ON (M)"));
display.display();
}else{
display.setCursor(0, 50);
display.print(F("Dehumidifier:"));
display.println(F("OFF (M)"));
display.display();
}
}
}
}
大佬牛逼!
温湿度传感器可以换成SHTC3的,价格便宜精度又高,这个OLED屏我也用过长时间亮着好像容易烧屏,我已经烧屏了两个了
可以,DHT还有几个,先用完。OLED我会注意一下,目前还没有出状况。(或者说你是接了5v么。。)
3.3V,这种屏确实容易烧,我看淘宝评论都很多人说烧了,你可以改一下程序,图像位置定期偏移一下
现在让他每循环几次停一下了