車中泊用 自作クーラー ラジエーター式クーラーの作成 その11 完成編 温度制御、リレー制御編 Arduino ESP32 温度コントローラー LCD BLE通信 ChatGPT

変な題名ですが、要するに車中泊用の温度コントローラーを自作した話がありますが、その続きになります。

ようやく、自分の中で納得の行くものが完成しました。

前回までは、プログラムで設定した温度(27.5℃)になると車中泊用のラジエター式クーラーの電源がON/OFF制御され、あと温度と湿度をSDカードに記録するという仕様でした。

前回、やっつけで作ったので、使って見ると色々と不満があります。

1.大気温度と湿度は分かるが、氷が入っているクーラーボックスの温度が分からない

2.温度、湿度がどの位か表示できない。

3.設定温度(27.5℃)がプログラミングされたもので変更できない。

という不満がありました。

温度コントローラーも単純に設定温度を下回るか、上回るかだけの判断なので、PID制御とかではないのもちょっと不満ではありますが、PID制御は面倒なので、宿題としておきます。

この先、使って行って、不満だったら、PID制御を取り入れようかな。という感じですね。

ということで、今年(2023年)は上記1~3に対しての仕様変更となりました。

1.クーラーボックス用のセンサー増設

2.LCDモニターを増設して常時温度と湿度を見れるようにする。そしてバックライトはオンオフできるようにする。

3.設定温度を何らかの外部からの方法で変更できるようにする。

という方針で再作成しました。

完成したので、そろそろユニバーサル基板に起こしても良いですね。

1.クーラーボックス用のセンサー増設

今回の仕様に対しての図

上にセンサーが二つあります。DHT11とDS18B20ですが、DS18B20をクーラーボックスへ入れるつもりです。

2.LCDモニターを増設して常時温度と湿度を見れるようにする。そしてバックライトはオンオフできるようにする。

これも上図の通りLCD(1602AのI2C付き)を取り付けます。

これは実際簡単で良かったです。

なんてことなくコントロールできますね。

3.設定温度を何らかの外部からの方法で変更できるようにする。

最大の難関はこれでした。

当初、ボタンを二つ付けて、設定温度のUP/DOWN用にしようと思っていましたが、ESP32でボタンが複数になると誤動作するので、抵抗をいっぱいつけないとならないという事態になりました。

これ、あんまりメリット少ないなぁ。。と思い、Webサーバーがいいかな?やったことあるし、と思ってましたが、ちょっとHTMLでボタンとパラメーター入力する画面を作成するのが面倒だなぁ。。となり、BLE(BlueTooth通信)にすることにしました。

BLEはスマホに、アプリをダウンロードしました。

Serial Bluetooth Terminal というBlueTooth通信をマニュアルで行えるアプリです。

使うには、スマホのBlueToothの設定にて一度ペア設定しておくと良いです。(しなくても繋がりますけど。)

実際の操作ですが、初めに繋げる相手(ESP32)を探します。

なので、左側からスワイプすると上の画面が出て、Devicesをクリックします。

で、右上のSCANを押します。

で、プログラミングしたときに、名付けた名前(UART Service)を選択します。

で、実際に繋げると上のようになります。

1でバックライトON

0でバックライトOFF

28.5で設定温度の変更

ということを上の画面で行っています。

動作も確認して思惑通りに動いてくれるのを確認しました。

できて良かったです。

以下がそのプログラムです。

/*    車中泊用温度コントロール機
 *    DHT11にて外気温と湿度を計測
 *    DS18B20にて水中温度計測
 *    温度と湿度をSDカードにCSVにて書く、
 *    設定温度にてリレーをオンオフする。
 *    設定温度はBLEにて変更可能する
 *    LCDのバックライトもBLEにてオンオフする 0=off 1=on
*/

#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Ticker.h>
#include <DHT.h>                   // DHTセンサー用
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);  // I2C addr、16x2 

//BLE
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

/* DHTセンサー*/
const int DHTPin = 27;             // DHTセンサーの接続ピン
DHT   dht(DHTPin, DHT11);          // DHTクラスの生成

#define ONE_WIRE_BUS 33   // Ds18b20 温度センサーで使用するポート番号
#define SENSER_BIT    9   // 精度の設定bit
#define RELAY 4           // リレーのピン番号

double i = 0;             // void loop の準備
float MT = 27.5;          //温度設定の初期値

unsigned char lcdRefresh = 0;  // リフレッシュ

Ticker ticker;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

float receivedValue = 0.0; // グローバルスコープで宣言

class MyCallbacks: public BLECharacteristicCallbacks {
  public:
    void onWrite(BLECharacteristic *pCharacteristic) {
      // 受信データを取得
      std::string rxValue = pCharacteristic->getValue();
      // データ有り
      if (rxValue.length() > 0) {
        // 受信データをデバッグターミナルに表示
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);

        Serial.println();
        Serial.println("*********");
        // ★ここを追加。
        pTxCharacteristic->setValue(rxValue);
        pTxCharacteristic->notify();
        // GetValue = rxValue;
        delay(10); // bluetooth stack will go into congestion, if too many packets are sent
        // ★ここまで。

        // rxValueをfloatに変換してreceivedValueに格納
        receivedValue = atof(rxValue.c_str());
      }
    }
};

// SDカードに追記
void appendFile(fs::FS &fs, const char * path, String message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

// 温湿度測定
void measure() {
  // 温湿度を測定してSDカードに保存
    String dataString = "";
    dataString += String(millis() / 1000); // 起動後の経過時間を秒に変換
    dataString += ",";
    
    float t1 = sensors.getTempCByIndex(0); // 水温度測定(℃)
    if(!isnan(t1)){
      dataString += String(t1);
    }else{
      dataString += " ";
    }
    
    float t2 = dht.readTemperature(); // 大気温度測定(℃)
    if(!isnan(t2)){
      dataString += String(t2);
    }else{
      dataString += " ";
    }

     float h = dht.readHumidity(); // 湿度測定(%)
    if(!isnan(h)){
      dataString += String(h);
    }else{
      dataString += " ";
    }
    
    dataString += "\n";      //改行

    // ファイルにデータを追記する
    appendFile(SD, "/LOGGING.CSV", dataString);
    Serial.println(dataString);
}

void setup() {
  pinMode(RELAY, OUTPUT);
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("UART Service");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
                    CHARACTERISTIC_UUID_TX,
                    BLECharacteristic::PROPERTY_NOTIFY
                  );
                      
  pTxCharacteristic->addDescriptor(new BLE2902());

  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
                       CHARACTERISTIC_UUID_RX,
                      BLECharacteristic::PROPERTY_WRITE
                    );

  pRxCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");  

  
  sensors.setResolution(SENSER_BIT);
  while (!Serial) {
  }

  Serial.println("Initializing SD card...");

  // SD カード初期化
  if(!SD.begin()){
      Serial.println("Card Mount Failed");
      return;
  }
  Serial.println("card initialized.");
  ticker.attach_ms(60000, measure); // 60秒毎に”measure”関数を割り込みで呼び出す

  lcd.init(); // LCD初期化 
  lcd.backlight(); // バックライト点灯
//lcd.noBacklight(); // バックライト消灯
  lcd.clear(); // クリア
  lcd.setCursor(0, 0); // カーソル位置
  lcd.print("Welcome Start"); // 文字表示
  lcd.setCursor(0, 1);
  lcd.print("    by yoshiyuki");
  dht.begin();
 
  delay(3000);
  lcdRefresh = 1;
}

void loop() {
  
//設定値変更とバックライトのオンオフ処理
    if (receivedValue == 0.0) {
      // バックライトOFF指令か?
      lcd.noBacklight();  // バックライト消灯
    } else if (receivedValue == 1.0) {
      //バックライトOn指令か?
      lcd.backlight(); // バックライト点灯
    } else if (receivedValue > 20.0) {          // 温度設定値の変更指令か
      MT = receivedValue;
    }

    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }

  sensors.requestTemperatures();              // 温度取得要求
  float t1 = sensors.getTempCByIndex(0);      //
  float h = dht.readHumidity();
  float t2 = dht.readTemperature(); 

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("T:");
  lcd.print(String(t2,1));    
  lcd.print("  H:");
  lcd.print(String(h,1));
  lcd.setCursor(0, 1);
  lcd.print("Wt:");
  lcd.print(String(t1,1));
  lcd.print(" Set:");
  lcd.print(String(MT,1));

  Serial.println("Water Temp " + String(sensors.getTempCByIndex(0)));  //温度の取得
  Serial.println("Room Temp  " + String(dht.readTemperature()));       //温度の取得
  Serial.println("Room Humi  " + String(dht.readHumidity()));          //温度の取得
  Serial.println("Set Temp   " + String(MT));                          //温度の取得
  
  if (i >= 300000){
    // 温度が27.5℃を超えたらon、27.5℃を下回ったらoffする。5分毎にチェックする
    if (dht.readTemperature() >= MT) {
        digitalWrite(RELAY, HIGH);
        Serial.println("Temp over");
      }
      else  {
        digitalWrite(RELAY, LOW);
        Serial.println("Temp under");
      }
    i = 0;
  }
  i++;
  delay(1);
}

で、最後にChatGPTですが、このプログラムを作成するのに、利用しました。

コンパイル時にエラーが出るので、デバッグに利用しました。

Arduino IDEの構文とかの使い方がまだまだ上手くないので、補ってもらうことができたので、思ったよりも早く完成したので良かったです。

自分の場合、ChatGPTはデバッグにしか使わないだろうと思います。

でも、何度も利用しているとコツみたいなものも分かって来て、そうすると早く完成に近づけることが出来ました。

これで今年の車中泊も快適に過ごすことができるようになると思います。

以上、2023年版の車中泊用クーラーの話でした。

参考にさせて頂いたサイト↓

ようやく、自分に満足の行くかたちとなり、ほぼ完成ということになりました。

あとは使っていってどうかですね。

この車中泊用クーラーの話も、足かけ4年ですからねぇ。

作ってはダメ出し、作ってはダメ出し。

ようやく形になったように思います。