改造了储藏间

在之前关于房间介绍的博文里提到过我有个很小的储藏间,与其说它是个储藏间不如说是个嵌在墙里的柜子。
这里以前可能真是个可以通往院子的门,靠院子一侧墙的厚度很薄,门上被房东加了泡沫塑料。这里一年四季都能保持在20度以下,但冬天不至于太冷,最低也不会低过10度,关上门里面又是一片漆黑。唯一的缺点是有时候湿度会很高,夏天顶上还发了霉,花了好久才清理干净。各种产品的包装上不都写着阴凉干燥处储存么,要是能解决湿度问题,这简直就是教科书式的储藏环境。说整就整,三十欧不到,搞了台很小的除湿机,拆开看过,里头就一半导体制冷片加风扇。除湿能力当然比不上动辄上千的大干燥机,不过这么小的空间,够用。
设备到我手上,自然都是要经过一番改造,让30欧的机器发挥50欧的价值。之前搞了不少单片机的项目,这波就是轻车熟路,直接改成智能储藏间。

IMG_1367.jpg
IMG_1367.jpg

祖传OLED显示温湿度,干燥机和LED照明的工作状态。目前显示湿度很高是因为拍照前手摸了传感器。
IMG_1368.jpg
IMG_1368.jpg

核心部分是这样,乍一看很乱,实则乱中有序,走线还是尽量的规整。关键问题是这说底还是租的房子没法随便钉钉子,线材都是靠装修胶带固定。
上方可以看到群晖的NAS,这里面现在是两个4T的酷狼。仔细看的话,在NAS左边还有个WD的Mybook。这个是黑五入手的12T的盘,本来打算买14T的,然而还在犹豫的时候就缺货了。12T也差不了多少,都是降速的企业级氦气盘。这个超大的硬盘现在用来备份用了快10年的移动硬盘里的数据,直接连在NAS上,非常方便。
ScreenShot_2020-12-07_23-27-19.png
ScreenShot_2020-12-07_23-27-19.png

现在所有盘都恢复了蓝色,非常舒服。
NAS右边的白盒子里是镜头和冷储存的硬盘,盒子里放了很多吸水硅胶,常年保持湿度在30左右。
墙上就是这套智能硬件的核心,贴在木柱子上的是个DHT22 温湿度传感器,下来是Node MCU版8266,之前用Wemo D1, 今天加了个屏幕,针脚不够了。
再往下亮红灯的是双路继电器模块和降压模块,很魔性的被我用热熔胶粘在一起。
地面上的插头是WD Mybook的供电,这个不太好看,但还是那个原因,别人的房子,不能乱打洞。
立在地上的那玩意儿就是干燥机,真的很小,对吧。
IMG_1371.jpg
IMG_1371.jpg

整体的感觉是这样,架子上层是存粮,罐头泡面这些保质期超过一年的我都放在这里,时刻准备开启御宅模式。食物之外的都是工具和DIY的材料,满满当当。单侧贴了LED灯带,这个可以RGB,但最后还是选择了白光,炫彩储藏间就太土嗨了。
这个柜子最大的难题是走线,墙上的洞是房东打的,另一侧是个插销。这个洞不大不小,刚好只能通过它这一根线,于是我只能另寻路子了。意外发现门角处有点空隙,线就通过这里走到外面。但他毕竟只是个缝隙,容不得那么多线,光是我着一根CAT7网线就给填上了一大半。我有想过直接弄个插排进来,问题在于最便宜的插排要六欧元,线还特别粗,再改线成本更高。嗯,没错,爷就是不想花这六欧。
我的解决方案是这样的,我把干燥机的供电线剪了,获得了一个9V 2.5A的电源,9V电源经过降压模块,输出5V给单片机和继电器供电,两个继电器再分别控制这个9V电源给led灯带还有干燥机。可以明显感觉到开关干燥机LED灯的亮度有区别,好在灯带和干燥机都不是什么精密玩意儿,不用太在意电源的稳定。
硬盘就不一样了,里头的数据都是无价之宝,NAS和硬盘盒的线我都没有改造,稳定大过天。
在装灯带之前,这个储藏间的照明用的是充电宝加USB灯,每次开门要按一下开关,关灯也要按一下。这个操作对于懒人极不友好,手臂重量少说有个一千克,每次把手举高半米按开关都要消耗5焦耳的能量,太浪费了。这个灯,他必须自动开启。
IMG_1369.jpg
IMG_1369.jpg

暂时用原始办法做了个关门检测,已经订购了单簧管模块,到时候就换成磁场检测,更耐用。
IMG_1370.jpg
IMG_1370.jpg

另一侧贴了点铝箔,单片机反正只要知道这儿接通了就行。
要是就到这儿,那顶多算是个准智能,8266都用上了,自然是要联网。
ScreenShot_2020-12-07_23-59-44.png
ScreenShot_2020-12-07_23-59-44.png

这次这个东西要长期运行,就没有去连数据库,能在本地网络中控制就够了。
最上面显示一下温度湿度数据,往下来的两个开关是灯和干燥机,最后一个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">&deg;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();
  
    }        
  }

}

}

评论区

评论列表