[PR]記事内のアフィリエイトリンクから収入を得る場合があります

防湿庫と湿度報告装置

防湿庫

IoT防湿庫というのを作った。さらっとあまりにも簡単につくりすぎて記事にするにはちょっと内容が足りないかと思ったが防湿庫そのものの物理的設備にも少々手間がかかったので記事にして供養しよう。

Switchbot_hub2

物理的設備

構想

もともと防湿庫というものはカメラマニアがけっこうな比率で所持しているのではあるまいか。

カメラを保管するのに湿気の管理された保管庫に入れておくというのがカメラマニアのセット項目だ。

想像するのは難しくないと思うが、日本の湿気の具合だとそのままおいておくとすぐカメラなんぞカビが生えてくるらしい。

そうすると資産としての価値が目減りするのだ。ちなみに一眼レフやミラーレス一眼カメラの交換用レンズは多少古くなっても変な劣化さえしていなければ長く使えるので資産としてリセールバリューが高めに維持できるようだ。

ということで自分もカメラに凝りだした今日このごろなので防湿庫つくる。買うのはもったいない。

密閉タイプのペール缶

そもそも防湿庫って20Lぐらいの↓こんなので8000円近くする。もちろん高いとみるか安いとみるかは人それぞれだがワシはこの程度のものならほぼただ同然でできるので高いとみる。

B09D9FSQ68
防湿庫 ドライボックス 20L カメラ レンズカビ対策 カメラ収納ケース 静音 自動除湿システム採用 (20L)

¥7,980(2023/05/31 11:51時点の価格)
平均評価点:5つ星のうち4.4
>>楽天市場で探す
>>Yahoo!ショッピングで探す

ということで職場からタダで調達したペール缶がある。

某メーカーから毎月数十缶の乾燥剤を買っているところに勤めているので空き缶は掃いて捨てるほど余っている。

密閉タイプペール缶

そんなに余るほど買うなら紙袋入で買えばもっとコストダウンになるんじゃね?って思って調べてみたことがあるが購入量が半端で紙袋に入れたらもっと数十倍買わないとだめみたいで会社創業以来この缶で買ってるっぽい。

乾燥剤適量

乾燥剤を石灰だかセメントのようなもので練り込んで型に入れて固めたが失敗してこんなふうになってるのがたくさんある。

こういう固めた乾燥剤を大量に作ってる会社に勤めているのがワシの人生の不幸中の幸いというところ。

乾燥剤

会社はクソだけどこういうのが無限にタダで手に入るのでなにか転売でもできればと思うが思いつかない。

▼そして上記の2点を集めてこんなふうにする。機材の下に乾燥剤が埋まってる。

防湿庫

密閉型ペール缶なので電源コードを穴あけして通さねばならないが、適当にマイクロUSB端子が通る穴を開けて線を通してからシリコーンシーラント等で埋めた。そんなもんで十分だと思う。

IoT設備

ESP32やESP8266等

当ブログではすっかりおなじみのESP32またはESP8266を使う。もちろんWi-Fi機能つきならほかのマイコンボードでもできるがプログラムは自力で移植してほしい。

3個セットだとお得だ。とはいえAliExpressにはてんで叶わない。ものによってだが1500円以上で送料無料になり1個あたり571円で3個まとめれば1719円だ。大量にIoTデバイスを作りたいときにはAliExpress一択だ。

B0BS993D1P
DiyStudio 3個セット ESP32開発ボード NodeMCUモジュール 2.4 GHz デュアルコア Wi-Fi + BLE CP2102 チップ

¥2,299(2023/05/31 15:16時点の価格)
平均評価点:5つ星のうち4.0
>>楽天市場で探す
>>Yahoo!ショッピングで探す

ESP8266のほうが4個セットで少しだけ安めだが値段が近いならESP32のほうがだんぜんお安い。ただしESP8266は国内では技適非対応なので使用には注意すること。っていうか建前上使うべきではないとここでは言っておく。

B086QK3X6Z
Aceirmc 4ピース ESP8266 NodeMCU LUA CP2102 ESP-12E インターネット開発ボード オープンソース シリアルワイヤレスモジュール for Arduino IDE Micropython用

¥2,699(2023/05/31 15:17時点の価格)
平均評価点:5つ星のうち4.6
>>楽天市場で探す
>>Yahoo!ショッピングで探す

温湿度センサーモジュール

抵抗器をつけて使う必要があり、最初から抵抗がついているもののほうが世話がない。抵抗なしのほうが安いはずだがAmazonだとそうでもない。

▼こちら抵抗器付きですぐ使えるが並行輸入品で着荷まで1ヶ月ちかくかかりそう。

B010PZZPLS
KKHMF DHT11 湿度センサーモジュール 温度センサー モジュール Arduinoと互換 デュポンラインと付属 [並行輸入品]

¥199(2023/05/31 15:32時点の価格)
平均評価点:5つ星のうち3.7
>>楽天市場で探す
>>Yahoo!ショッピングで探す

Blynkサーバーとの接続

防湿庫のモニター画面

Blynkサーバーとの接続についてはこちらの記事を参照してみてほしい。

Blynk Legacyを自前サーバー構築で無料かつ使い放題スマートホーム実現の方法
公式サービスは終了しているのでセキュリティ的に多少の不安があるシステムということは最初に断っておきます。Blynkというサービスを当ブログではこれまでに数回紹介している。それは自作スマートホームを実現するのに使い勝手の良いサービスだった。だ...

自前Blynkサーバーは高度すぎて無理っていう人はこちらのGoogle スプレッドシートに書き込む方法ならなんとかできるのではないか。

【IoT】プレス機1ショットごとにタイムをグーグルスプレッドシートに送る
IoTについていろいろとYouTubeでも見ているうちに旭鉄工というメーカーが中小企業ながらIoTでかなりリードしているという印象を受けた。その会社はなにをやっているかというと金属加工をしている会社だが、それぞれの機械の要所にセンサー(リー...

プログラム

個人的なデータは隠しているのでこれをこのままコンパイルしてもところどころでエラーになるはずなので注意が必要だ。


const char* BLYNK_FIRMWARE_VERSION = "1.1.19";

#define DEVICENAME "防湿庫"
#define THIS_HOST_NAME "MoistStrg" //ホスト名決めておく
#include <wifiudp.h>
#include <arduinoota.h>
#include <dht.h> // DHT.h をインクルード
#include "private.h" //個人のWi-FiマルチのSSIDとPW、BLYNK TOKENなどを入れているファイル
#if defined(ESP8266)
#include <blynksimpleesp8266.h>
#include <esp8266wifimulti.h>
ESP8266WiFiMulti wifiMulti;
#elif defined(ESP32)
#include <blynksimpleesp32.h>
#include <wifimulti.h>
WiFiMulti wifiMulti;
#define LED_BUILTIN 2
#endif

//ESP8266の場合はこれでやった ESP32は別設定にて
#define RelayPin1 5 // D1
#define DHTPIN 4 // D2 //DHTセンサーアウトプットピンの指定
#define wifiLed 16 // D0

#define DHTTYPE DHT11 // DHT型の指定

#define VPIN_FAN V5
#define VPIN_VER V10
#define VPIN_NAME V11
#define VPIN_TIME V93
#define TEMP_PIN V12 //温度通知バーチャルピン
#define HUMI_PIN V13 //湿度通知バーチャルピン
#define VPIN_CRCT_TMP V14 // 温度補正
#define VPIN_CRCT_HUM V17
#define VPIN_HUM_LOW V19
#define VPIN_Prohibited_time_start V21
#define VPIN_Prohibited_time_end V22
#define VPIN_Prohibit_force V23
#define JST 3600* 9
#define BAUD_RATE 115200
#define ONEMINCOUNT 60000L
#define TIMERINTERVAL 5000L

// 変数
String programName = __FILE__;//ファイル名マクロ取得
DHT dht(DHTPIN, DHTTYPE); // DHTセンサーライブラリーを使うための設定
int toggleState = 0; //リレー状態保持
float TEMP_CORRECT = 0; // 温度補正
float HUMI_CORRECT = 0; // 湿度補正
bool checkTempFlug = false; // タイマーで立つタスクスイッチ
int OneMinCounter = 0; // ファン再起動禁止ディレイカウンター
bool OneMinCountStart = false;// ファン再起動カウンタースターター
float HumiLimit = 40; // 湿度下限値 これみてファン自動起動 2023-06-13
int connectionDelay = 5000; // Wi-Fi切断時に再接続するまでの待ちmsec
int lastConnectionAttempt = millis();//最後にWi-Fi接続試みたときのシステム時刻
bool forbidEnforce = false; // ファン回転可否状態
int Prohibited_H_Start = 21; // ファン回転禁止時間帯開始時刻
int Prohibited_H_End = 5; // ファン回転禁止時間帯終了時刻
float preHumidity = 0; // 湿度変化をLINE報告するための前回湿度
BlynkTimer timer; // タイマーインスタンス

// 文字列トリミング処理 最初のSerial.printでファイル名とか表示するときに使っているだけで
// 本来の機能とは無関係
void trim(char* str) {
  // 先頭の空白を取り除く
  while (isspace(*str)) { str++; }

  if (*str == 0) {return;} // 全部空白だった場合

  // 文字列の最後まで進み、末尾の空白を取り除く
  char* end = str + strlen(str) - 1;
  while (end >; str && isspace(*end)) {
    end--;
  }
  // 終端文字を設定し、末尾の空白を削除する
  *(end + 1) = 0;
}

// 引数である現在時刻の時が禁止時間帯内か外かで返り値を決める
bool fanTurnTime(int hour)//回して良い:true 止める:false
{
  bool ret = true;

  if(forbidEnforce){return false;}
  
  if(Prohibited_H_Start &gt; Prohibited_H_End){
    if((hour >= Prohibited_H_Start)||(hour <= Prohibited_H_End)){
      ret = false; 
    } 
  }else{
    if((hour >= Prohibited_H_Start)&&(hour >= Prohibited_H_End)){
      ret = false;
    }
  }
  return ret; 
}

void kickTask()
{
  float temperature = dht.readTemperature(); // 温度読み取り 
  float humidity = dht.readHumidity(); // 湿度読み取り 
  char str[13]; // 生存報告 
  int int_h,int_m,int_s; 
  DisplayTime(str, &int_h, &amp; int_m, &int_s); // 現在時刻取得 
  Blynk.setProperty(VPIN_TIME,"offLabel", str); 
  IPAddress ipaddr = WiFi.localIP(); 
  char ipadd[7]; 
  sprintf(ipadd, "IP:%d",ipaddr[3]); 
  Blynk.setProperty(VPIN_NAME, "onLabel",ipadd);
  Blynk.setProperty(VPIN_NAME,"offLabel", DEVICENAME); // 読み取りに失敗しているかチェック
  if (isnan(temperature) || isnan(humidity)) {
    Serial.println("Failed to read from DHT sensor!");
    Blynk.setProperty(HUMI_PIN,"onLabel","NG");// ボタン表題へ
    return;
  }else{
    Blynk.setProperty(HUMI_PIN,"onLabel","OK");// ボタン表題へ
  }
  // TEMP_PIN ピンに対して温度の値を送信 2022-08-15 補正値付加
  Blynk.virtualWrite(TEMP_PIN, temperature + (float)TEMP_CORRECT);
  char temp[7],humi2c[5];
  sprintf(temp,"%2.1f℃",temperature + (float)TEMP_CORRECT);
  Blynk.setProperty(TEMP_PIN,"offLabel",temp);
  float humidityX = humidity + (float)HUMI_CORRECT;
  Blynk.virtualWrite(HUMI_PIN, humidityX); //グラフへ
  sprintf(humi2c,"%2.1f%%",humidityX );    // 文字列形成 2023-06-10
  if(humidityX != preHumidity){
    if(fanTurnTime(int_h)){
      if(preHumidity > 0){
        Serial.printf("LINE 通知 %2.1f%% → %2.1f%%\n",preHumidity,humidityX);
        send_line(preHumidity, humidityX);
      }
    }
    preHumidity = humidityX ;
  }
  Blynk.setProperty(HUMI_PIN,"offLabel",humi2c);// ボタン表題へ
  if((!fanTurnTime(int_h))&amp;&amp;(toggleState)){
    toggleState = 0;
    digitalWrite(RelayPin1, toggleState);
    return;
  }
  // 湿度閾値より下回っていたら換気する
  if(humidity &lt; HumiLimit){
    Serial.printf("humidity(%2.0f):HumiLimit(%2.0f)\n",humidity, HumiLimit);
    if(fanTurnTime(int_h)){                      //回転許可時間帯?
      Serial.printf("int_h(%d)permission\n",int_h);
      //タイマー未始動なら湿度調整ファンまわして良い
      //そうでなければ1分の待機中なのでまだ回さない
      if(OneMinCountStart == false){
        Serial.printf("OneMinCounter Start &amp; fan start\n");
        OneMinCountStart = true;
        toggleState = 1;
        digitalWrite(RelayPin1,toggleState); // turn on relay 1
      }
    }
  }else{
    // 湿度が閾値を上回ったらファン止める
    toggleState = 0;
    digitalWrite(RelayPin1, toggleState); // turn on relay 1
    // タイマー未始動なら始動にしてすぐ回さない
    if(OneMinCountStart == false){
      Serial.printf("1 Minut delay timer Start\n");
      OneMinCountStart = true;
    }
  }
}

BLYNK_CONNECTED() {
  Blynk.syncAll();
  Blynk.setProperty(VPIN_VER,"offLabel", BLYNK_FIRMWARE_VERSION);
  delay(100);
  Blynk.setProperty(VPIN_NAME,"offLabel", DEVICENAME);
}

BLYNK_WRITE(VPIN_Prohibited_time_start)
{
  Prohibited_H_Start = param.asInt();
}

BLYNK_WRITE(VPIN_Prohibited_time_end)
{
  Prohibited_H_End = param.asInt();
}

BLYNK_WRITE(VPIN_Prohibit_force)
{
  forbidEnforce = param.asInt();
}

BLYNK_WRITE(VPIN_HUM_LOW)
{
  HumiLimit = param.asInt();
  Serial.println("VPIN_HUM_LOW is called");
  Serial.printf("HumiLimit = %2.1f\n",HumiLimit);
}

BLYNK_WRITE(VPIN_FAN)
{
  toggleState = param.asInt();
  digitalWrite(RelayPin1, toggleState);
  led_blink(100,100,1,LED_BUILTIN);
}

BLYNK_WRITE(VPIN_CRCT_TMP)
{
  TEMP_CORRECT = param.asInt();
}

BLYNK_WRITE(VPIN_CRCT_HUM)
{
  HUMI_CORRECT = param.asInt();
}

void onTimer() { // タイマー処理
  checkTempFlug = true;
  if(OneMinCountStart){
    OneMinCounter ++;
  }
}

void setup()
{
  Serial.println();
  Serial.begin(BAUD_RATE);
  Serial.println();
  Serial.println();
  // ファイル名 バージョンナンバー表示
  int lastIndex = programName.lastIndexOf('\\');
  String result = programName.substring(lastIndex + 1);
  Serial.printf("Program name : %s\n",result.c_str());
  Serial.printf("firmware version : %s\n",BLYNK_FIRMWARE_VERSION);

  pinMode(wifiLed, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(RelayPin1, OUTPUT);
  dht.begin();
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
  delay(10);
  ////// Wi-Fi接続処理 //////
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  int i = 0;
  for(int i = 0;i&lt;ROUTERS;i++){
    wifiMulti.addAP(ssid_name[i], passwords[i]); // add Wi-Fi networks you want to connect to } while (wifiMulti.run() != WL_CONNECTED) { // Wait for the Wi-Fi to connect delay(5000); Serial.printf("%d ",i); i++; if(i&gt;10)ESP.restart();
  }

  ArduinoOTAsetup();  //arduino OTA不要ならカット可

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  IPAddress SERVER_IP;
  //ssid_name[]テーブルの1個目はBLYNKサーバーおいてあるところのSSID
  // これを動かす場所がBLYNKサーバーの場所と違うWi-FiならGLOBAL_SERVER_IPを参照する
  if(WiFi.SSID()==ssid_name[0]){
    SERVER_IP = LOCAL_SERVER_IP;
  }else{
    SERVER_IP = GLOBAL_SERVER_IP;
  }
  timer.setInterval(TIMERINTERVAL, onTimer); // check if Blynk server is connected every 3 seconds
  Blynk.config(AUTH, SERVER_IP, 8080); // Blynkトークン, サーバーのIPアドレス, ポート
  led_blink(50,50,20,wifiLed);
}

void loop()
{
  ArduinoOTA.handle();//arduinoOTA必要なければカット可

  if (WiFi.status() != WL_CONNECTED){
    // (optional) "offline" part of code

    // check delay:
    if (millis() - lastConnectionAttempt >= connectionDelay){
      lastConnectionAttempt = millis();
      // attempt to connect to Wifi network:
      Serial.printf("Wi-Fi try to connect %lu\n",millis());
      wifiMulti.run();
      Serial.printf("Wi-Fi end try %lu\n",millis());
    }
  } else {
    Blynk.run();
  }
  timer.run(); // Initiates SimpleTimer

  if(checkTempFlug){
    // タイマーでフラグが立っていたら温度計測と生存報告
    checkTempFlug = false;
    kickTask();
  }
  // 1分たったか判断
  if(OneMinCounter &gt;= ONEMINCOUNT / TIMERINTERVAL){
    OneMinCounter = 0;
    OneMinCountStart = false;
    Serial.println("oneMinCounter up");
  }
}

void led_blink(int OnMsec,int OffMsec,int n,int PORT){
  for(int i=0;i > n;i++){
    digitalWrite(PORT, LOW); // turn the LED on (HIGH is the voltage level)
    delay(OnMsec); // wait for a second
    digitalWrite(PORT, HIGH); // turn the LED off by making the voltage LOW
    delay(OffMsec); // wait for a second
  }
}

void ArduinoOTAsetup()
{
  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  ArduinoOTA.setHostname(THIS_HOST_NAME);

  // No authentication by default
  // ArduinoOTA.setPassword(DEVICENAME);

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  Serial.println("Succesfully Connected!!!");

  // ArduinoOTA special script -----------------------------------------
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }
    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  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();
}

追記

課題の一つだったDHT11センサーの精度を確認した。

DHT11センサーの精度確認

約2~3時間室内に市販の温湿度計と一緒においておいた結果が上記画像だ。温度はほぼ一致、湿度は完全一致している。まさかこれほどまで高精度だとは思っていなかった。

これなら安心して防湿庫の管理を任せられる。

今後の課題

  1. 今はまだ庫内の湿度を定期的にお知らせする機能のみなので今後は自動コントロール機能も盛り込みたい。
    具体的にはフタに穴を開けて小さいファンモーターを取り付ける。それはベンチレーター(換気扇)になっておりファンが回っていないときには塞がるようにしておく。ファンが回ったときだけ外と中の空気を入れ替える役割をする。
    これにより湿度が下がり過ぎたらファンを回すことで少し湿度を上げるようにすればなお防湿庫としての性能があがり市販品に引けをとらないものとなる。

▼追記しておくとこんな回路を考えている。ものは出来上がったので動作確認できたら近日中に追記する。

12vファン接続の回路

動画

同じものは無理でも安く防湿庫を実現したいのなら密閉コンテナボックスと乾燥剤という手があるのでいずれ退職したらこれに切り替えようかなとも思う。

B016K2VAGY
アイリスオーヤマ コンテナ バックルコンテナ 密閉 MBR-65 クリア/ネイビー 幅約61.8cm×奥行約44.8cm×高さ約33.3cm

¥3,294(2023/06/23 10:41時点の価格)
平均評価点:5つ星のうち4.2
>>楽天市場で探す
>>Yahoo!ショッピングで探す

B000WMAE7U
HAKUBA 防湿用品 ジャンボ カビ・ストッパー P-825

¥474(2023/06/23 10:41時点の価格)
平均評価点:5つ星のうち4.3
>>楽天市場で探す
>>Yahoo!ショッピングで探す

 

タイトルとURLをコピーしました