DreamerDreamのブログ

夢想家の夢です。〜揚げたてのモヤっとしたものをラフレシアと共に〜

セキスイハイムのベタ基礎って有効活用してますか?? 床下空間に照明を付けるだけで劇的に使えそうな空間になった話

みなさん、家の床下って有効活用してますか?

 

セキスイハイムのベタ基礎って超がつくほどシンプルなんです。

www.816t.jp

ベタ基礎の名の通り、コンクリがベターっと敷かれていて所々にユニットを支えるための柱が立っているだけ。

一般的な木造住宅の基礎のように迷路みたいに入り組んでないんです。

基礎パッキン構造なので通気孔も空いていない。なので床下を覗くとコンクリ打ちっぱなしの倉庫みたい空間なのです。

 

しかし、床下収納兼点検口を覗くと暗い空間が広がっていて気味の良いものではありません。

昼間でも真っ暗。懐中電灯を照らしてもかなり遠い壁がぼんやりと見えるので、とても「入ってみたい」と言える環境ではありません。

長年「この空間をなんとか活用できないものかな?」

とは思っていたのですが、使うかどうかわからない空間に予算をかけるわけにもいかず保留していました。

 

しかし!今僕は趣味で「電気工事士」の資格を保有しています!堂々と電気工事ができるのですよ。

dreamerdream.hateblo.jp

 

ということで、床下に電灯を付けてみることにしました。

 

ほたるスイッチとコンセントを設置!

 

ここで電気工事士の試験の練習に使ったランプレセプタクルが役に立ちました。

LED化して余っていた蛍光ランプを有効活用します!

点灯!

電灯一つああるだけでもいい感じの空間になりましたので+2個追加しました。

 

電灯つけただけで床下空間が見渡しやすくなって不気味な雰囲気が全くなくなりました。

 

しかし、この状態では床にそのまま這わされている給水管や電線などがあるので、ほふく前進しないと移動が難しく、有効活用できるとは言えません。

 

なので給水管などの直径に近い太さの端材を適当に並べます。

 

その上にベニヤ板を置くと!

なんということでしょう!そのまま上を移動しても給水管に負荷がかからない橋が完成しました!

 

では、この橋を活用するために台車を作ります。

といっても、これも端材にホームセンターに売られている一番小さくて安いコロを取り付けただけ。

これにお腹を乗せて手足で漕いで進みます。

 

橋に登るためのスロープも端材で確保。念の為軽く電気コードを止める釘(名前忘れた)で止めておくことにします。

差し込んでるだけなので簡単に外せます。

 

ということで、床下空間への移動手段が完成しました!!

照明に照らされただだっ広い空間が広がっていて、まるで秘密基地のようです!

 

木材は眠っていた端材をそのまま使っているのでチグハグのままです。もし今後必要あれば整えましょうかね。

 

実際に漕いで移動してみた動画を撮ってみました。

なかなか使えそうな広々とした空間が広がっていますね。高さも意外に高くて、高いところは50cmぐらいあります。

我が家はウォームファクトリーとか床下の設備を何も入れていないので、水回りを除いたユニット約3個分の空間がそのまま使えます。
youtu.be

今回は照明と移動手段を確保したたけなんだけど、なにこれ?結構楽しい!笑

無駄に走って楽しんでしまいました。

今回はここまで。

 

コンセントも自由に付けられるし、この広い空間何に使おうかしら?やっぱり収納?それとも・・

こういうおもちゃのコースにするのもありじゃない?これ楽しそう!

 

大人の趣味のプラレールとかでも使えそうだよね。・・・そういう趣味がないからわかんないけど・・

 

これからいろいろ考えることにします。

引き戸のソフトクローザーが壊れたので修理してみた

セキスイハイムの引き戸についているソフトクローザー。

経年劣化により、時折扉が閉まらなくなってしまいます。

 

原因は単純に、クローザーの当たりに付いているゴムローラーの劣化です。

 

このアームの先端にぐるっとチューブ状のゴムが巻かれているのですが、経年劣化により、固くなってそのうち割れてしまいます。

完全に割れて落ちてしまわずとも、ヒビが入った程度でも閉まらなくなることがあります。固くなったときに勢い良く閉めたりすると割れてしまうのでしょうね。

 

長い目で見るとアッセンブリごと交換するのが一番良いのだけど、1つ1万円以上します。

 

原因が簡単なものだと判明しているのに、新しいものを購入するのも悔しいので、手持ちの材料で修理を試みることにします。

完全に割れて部品が無くなってしまっているクローザーです。

 

実は既にいくつか修理をしているので、外さずとも修理が可能だということが判明しています。

 

修理方法は簡単。

ローラーにビニールテープをグルグル巻き付けます。

要するにゴムローラーの高さ分だけ稼げばOKです。

 

ビニールテープをグルグル巻いただけだと後にベタつくし、ズレてくる可能性があるので、その上にアルミテープを巻き付けて補強します。

 

アルミテープが強力なので簡単には剥がれることはないでしょう。かなり丈夫に仕上がりました。

ちなみに、完全に割れていないローラーの場合、ゴムの変形で当たりが悪くなっているだけのようなのでアルミテープで補強するだけで改善しました。

 

今回は以上!です。

 

 

停電後に自動でエアコンON!ESP32でペット用エアコン監視装置を作ってみた 猛暑・熱中症対策として高齢者宅にも使えるかもしれない話

今年の夏もかなり暑くなると言われています。

 

最近は、
✔猫や犬の留守番中
✔高齢者の一人暮らし
✔真夏の停電

など、「留守の間にエアコンが止まってしまうこと」への不安を感じる場面がかなり増えてきました。

 

特に怖いのが、停電後にエアコンが自動復帰しないという問題です。

 

最近の上位エアコンには、高温みまもり機能遠隔制御停電復帰機能を持つ機種も存在するようです。

<見守り機能搭載のお掃除機能無しのシンプルなエアコン↓>

amzn.to

 

しかし、古い機種やシンプルモデルでは非搭載が多いです。

我が家もペットがいてシンプルなエアコンのため、
「外出中に停電が起きて、そのままエアコンが止まったら怖いな…」
と常に感じています。


そこで以前、ESP32を使ったエアコン遠隔操作装置を作って遠隔から監視制御できるようにしていました。

dreamerdream.hateblo.jp

 

 

今回エアコンの買い替えに伴い、このシステムを全面的にリニューアルすることにしました。

というのも、新しいエアコンにしたところ、以前のコードでは赤外線信号が正常に送信できなくなってしまったのです・・・orz

 

最初は、赤外線LEDの出力不足?LED角度?電流不足?
などを疑ったのですが、調べていくうちに、どうやら新しいエアコンの赤外線コードが以前よりかなり長く、ESP32側で正常に扱えていなかった可能性が高そうでした。

そこで今回は、ChatGPTを使ってコード全体を見直してみることにしました。


完成したシステム

今回完成した装置は、単なる「赤外線リモコン」ではなく、

停電復旧
温度異常監視
自動冷房ON
状態監視

まで行う、簡易的な熱中症対策装置のようなものになっています。

 

完成したESP32側のコード

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <SPIFFS.h>
#include "DHT.h"

#define LED_BUILTIN 2

#define IR_RECEIVE_PIN 15
#define IR_SEND_PIN 4
#define INDI_LED 17

#define DHT11_PIN 19
#define DHT11_PW 18
#define DHTTYPE DHT11

#define RAW_BUFFER_LENGTH 3000
#define RECORD_GAP_MICROS 25000
#define MARK_EXCESS_MICROS 20

#include <IRremote.hpp>

const char *ssid = "MyNetworkSSID";
const char *password = "mynetworkpassword";

WiFiServer server(5000);
DHT dht(DHT11_PIN, DHTTYPE);

const char *AUTO_IR_FILE = "/auto.ir";
const char *CONFIG_FILE  = "/config.txt";

const uint32_t AUTO_SEND_DELAY_MS = 120000;

const float TEMP_WATCH_HIGH_MODE = 27.0;

const uint32_t TEMP_INTERVAL_NORMAL_MS = 60UL * 60UL * 1000UL;
const uint32_t TEMP_INTERVAL_HIGH_MS   = 30UL * 60UL * 1000UL;
const uint32_t TEMP_RECHECK_DELAY_MS   = 5UL  * 60UL * 1000UL;
const uint32_t TEMP_WATCH_START_DELAY_MS = 5UL * 60UL * 1000UL;

float limitTemperature = 31.0;

enum IndicatorMode {
  INDI_CONNECTING,
  INDI_STANDBY,
  INDI_LEARN,
  INDI_SEND
};

volatile IndicatorMode indiMode = INDI_CONNECTING;

volatile uint32_t uptimeMinutes = 0;

volatile uint32_t lastIrSendMinute = 0;
String lastIrSendReason = "NONE";
bool hasLastIrSend = false;

int ipNo = 0;
bool serverStarted = false;

static uint16_t rawData[RAW_BUFFER_LENGTH];
static uint16_t rawLen = 0;

SemaphoreHandle_t irMutex;
SemaphoreHandle_t dhtMutex;
SemaphoreHandle_t statusMutex;

// =====================================================
// uptime分カウンタ
// =====================================================
void uptimeTask(void *args) {
  while (true) {
    vTaskDelay(pdMS_TO_TICKS(60000) );
    uptimeMinutes++;
  }
}

// =====================================================
// 最終IR送信記録
// =====================================================
void recordIrSendReason(const String &reason) {
  if (statusMutex) xSemaphoreTake(statusMutex, portMAX_DELAY);

  lastIrSendMinute = uptimeMinutes;
  lastIrSendReason = reason;
  hasLastIrSend = true;

  if (statusMutex) xSemaphoreGive(statusMutex);

  Serial.print("IR send reason recorded: ");
  Serial.print(reason);
  Serial.print(" at ");
  Serial.print(lastIrSendMinute);
  Serial.println(" min");
}

// =====================================================
// インジケータLED
// =====================================================
void indicatorTask(void *args) {
  pinMode(INDI_LED, OUTPUT);

  while (true) {
    if (indiMode == INDI_LEARN) {
      digitalWrite(INDI_LED, HIGH);
      vTaskDelay(pdMS_TO_TICKS(80) );
      digitalWrite(INDI_LED, LOW);
      vTaskDelay(pdMS_TO_TICKS(80) );
      continue;
    }

    if (indiMode == INDI_SEND) {
      digitalWrite(INDI_LED, HIGH);
      vTaskDelay(pdMS_TO_TICKS(50) );
      digitalWrite(INDI_LED, LOW);
      vTaskDelay(pdMS_TO_TICKS(50) );
      continue;
    }

    if (WiFi.status() != WL_CONNECTED || ipNo <= 0) {
      digitalWrite(INDI_LED, HIGH);
      vTaskDelay(pdMS_TO_TICKS(100) );
      digitalWrite(INDI_LED, LOW);
      vTaskDelay(pdMS_TO_TICKS(100) );
      continue;
    }

    digitalWrite(INDI_LED, LOW);
    vTaskDelay(pdMS_TO_TICKS(3000) );

    for (int i = 0; i < ipNo; i++) {
      if (indiMode == INDI_LEARN || indiMode == INDI_SEND) break;

      digitalWrite(INDI_LED, HIGH);
      vTaskDelay(pdMS_TO_TICKS(250) );
      digitalWrite(INDI_LED, LOW);
      vTaskDelay(pdMS_TO_TICKS(250) );
    }
  }
}

// =====================================================
// 設定保存
// =====================================================
void saveConfig() {
  File file = SPIFFS.open(CONFIG_FILE, FILE_WRITE);

  if (!file) {
    Serial.println("Config save failed");
    return;
  }

  file.print("LIMIT=");
  file.println(limitTemperature, 1);
  file.close();

  Serial.print("Config saved. LIMIT=");
  Serial.println(limitTemperature, 1);
}

// =====================================================
// 設定読み込み
// =====================================================
void loadConfig() {
  if (!SPIFFS.exists(CONFIG_FILE) ) {
    Serial.println("No config file. Use default config.");
    saveConfig();
    return;
  }

  File file = SPIFFS.open(CONFIG_FILE, FILE_READ);

  if (!file) {
    Serial.println("Config open failed. Use current default.");
    return;
  }

  while (file.available() ) {
    String line = file.readStringUntil('\n');
    line.trim();

    if (line.startsWith("LIMIT=") ) {
      float value = line.substring(6).toFloat();

      if (value >= 0.0 && value <= 50.0) {
        limitTemperature = value;
      }
    }
  }

  file.close();

  Serial.print("Loaded LIMIT=");
  Serial.println(limitTemperature, 1);
}

// =====================================================
// CSV RAWを配列へ変換
// =====================================================
bool parseRawCsv(const String &data) {
  rawLen = 0;

  int start = 0;

  while (start < data.length() && rawLen < RAW_BUFFER_LENGTH) {
    int comma = data.indexOf(',', start);
    String token;

    if (comma == -1) {
      token = data.substring(start);
      start = data.length();
    } else {
      token = data.substring(start, comma);
      start = comma + 1;
    }

    token.trim();
    if (token.length() == 0) continue;

    uint32_t value = token.toInt();

    if (value > 0 && value <= 65535) {
      rawData[rawLen++] = (uint16_t)value;
    }
  }

  Serial.print("Parsed RAW length: ");
  Serial.println(rawLen);

  return rawLen > 0;
}

// =====================================================
// RAW送信本体
// =====================================================
bool sendRawString(const String &payload) {
  if (!parseRawCsv(payload) ) {
    return false;
  }

  if (irMutex) xSemaphoreTake(irMutex, portMAX_DELAY);

  indiMode = INDI_SEND;

  Serial.print("Sending RAW length: ");
  Serial.println(rawLen);

  IrReceiver.stop();
  delay(50);

  IrSender.sendRaw(rawData, rawLen, 38);

  delay(100);
  IrReceiver.start();

  indiMode = INDI_STANDBY;

  if (irMutex) xSemaphoreGive(irMutex);

  return true;
}

// =====================================================
// SPIFFSから自動送信用IRコードを読む
// =====================================================
bool loadAutoRaw(String &raw) {
  raw = "";

  if (!SPIFFS.exists(AUTO_IR_FILE) ) {
    Serial.println("No auto IR file");
    return false;
  }

  File file = SPIFFS.open(AUTO_IR_FILE, FILE_READ);

  if (!file) {
    Serial.println("Failed to open auto IR file");
    return false;
  }

  raw = file.readString();
  file.close();

  raw.trim();

  if (raw.startsWith("RAW:") ) {
    raw = raw.substring(4);
    raw.trim();
  }

  if (raw.length() == 0) {
    Serial.println("Auto IR file is empty");
    return false;
  }

  return true;
}

// =====================================================
// 冷房ON信号送信 + 10秒後再送
// =====================================================
void sendCoolingTwice(const String &reason) {
  String raw;

  if (!loadAutoRaw(raw) ) {
    Serial.println("Cooling IR code not found");
    return;
  }

  Serial.println("Sending cooling IR...");

  bool ok1 = sendRawString(raw);

  delay(10000);

  Serial.println("Sending cooling IR again...");

  bool ok2 = sendRawString(raw);

  if (ok1 || ok2) {
    Serial.println("Cooling IR sent");
    recordIrSendReason(reason);
  } else {
    Serial.println("Cooling IR failed");
  }
}

// =====================================================
// 自動送信タスク
// WiFi接続を待たずに120秒後に送信
// =====================================================
void autoSendTask(void *args) {
  Serial.println("AutoSend task started");

  if (!SPIFFS.exists(AUTO_IR_FILE) ) {
    Serial.println("No auto IR file");
    vTaskDelete(NULL);
    return;
  }

  Serial.println("Auto IR file exists. Waiting 120 sec...");
  vTaskDelay(pdMS_TO_TICKS(AUTO_SEND_DELAY_MS) );

  sendCoolingTwice("BOOT_AUTO_SEND");

  Serial.println("AutoSend task finished");

  vTaskDelete(NULL);
}

// =====================================================
// DHT 温湿度
// =====================================================
String getTempHumidity() {
  if (dhtMutex) xSemaphoreTake(dhtMutex, portMAX_DELAY);

  digitalWrite(DHT11_PW, HIGH);
  delay(1500);

  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();

  digitalWrite(DHT11_PW, LOW);

  if (dhtMutex) xSemaphoreGive(dhtMutex);

  if (isnan(humidity) || isnan(temperature) ) {
    return "ERROR:DHT_READ_FAILED";
  }

  return String( (int)temperature ) + ":" + String( (int)humidity );
}

// =====================================================
// 温度だけ取得
// =====================================================
float readTemperatureOnly() {
  if (dhtMutex) xSemaphoreTake(dhtMutex, portMAX_DELAY);

  digitalWrite(DHT11_PW, HIGH);
  delay(1500);

  float temperature = dht.readTemperature();

  digitalWrite(DHT11_PW, LOW);

  if (dhtMutex) xSemaphoreGive(dhtMutex);

  if (isnan(temperature) ) {
    Serial.println("Temperature read failed");
  } else {
    Serial.print("Temperature: ");
    Serial.println(temperature);
  }

  return temperature;
}

// =====================================================
// 温度監視タスク
// =====================================================
void tempWatchTask(void *args) {
  Serial.println("TempWatch task started");

  Serial.println("TempWatch waits 5 min before first check...");
  vTaskDelay(pdMS_TO_TICKS(TEMP_WATCH_START_DELAY_MS) );

  uint32_t nextInterval = TEMP_INTERVAL_NORMAL_MS;

  while (true) {
    Serial.println("TempWatch: measuring...");

    float temp = readTemperatureOnly();

    if (isnan(temp) ) {
      Serial.println("TempWatch: read failed. Next check in 30 min.");
      vTaskDelay(pdMS_TO_TICKS(TEMP_INTERVAL_HIGH_MS) );
      continue;
    }

    if (temp > TEMP_WATCH_HIGH_MODE) {
      nextInterval = TEMP_INTERVAL_HIGH_MS;
      Serial.println("TempWatch: over 27C. Next interval = 30 min.");
    } else {
      nextInterval = TEMP_INTERVAL_NORMAL_MS;
      Serial.println("TempWatch: 27C or below. Next interval = 1 hour.");
    }

    if (limitTemperature > 0.0 && temp >= limitTemperature) {
      Serial.print("TempWatch: limit detected. LIMIT=");
      Serial.println(limitTemperature, 1);
      Serial.println("TempWatch: recheck after 5 min.");

      vTaskDelay(pdMS_TO_TICKS(TEMP_RECHECK_DELAY_MS) );

      float recheckTemp = readTemperatureOnly();

      if (limitTemperature > 0.0 &&
          !isnan(recheckTemp) &&
          recheckTemp >= limitTemperature) {

        Serial.println("TempWatch: still over limit. Cooling ON.");

        sendCoolingTwice("TEMP_LIMIT");

        Serial.println("TempWatch: next check in 30 min.");
        vTaskDelay(pdMS_TO_TICKS(TEMP_INTERVAL_HIGH_MS) );
        continue;
      }

      Serial.println("TempWatch: recheck below limit. Next check in 30 min.");
      vTaskDelay(pdMS_TO_TICKS(TEMP_INTERVAL_HIGH_MS) );
      continue;
    }

    if (limitTemperature <= 0.0) {
      Serial.println("TempWatch: LIMIT is OFF. Auto cooling disabled.");
    }

    vTaskDelay(pdMS_TO_TICKS(nextInterval) );
  }
}

// =====================================================
// WiFi接続・再接続
// =====================================================
void connectToWiFi() {
  if (WiFi.status() == WL_CONNECTED) {
    return;
  }

  indiMode = INDI_CONNECTING;
  serverStarted = false;

  WiFi.disconnect(false);
  delay(200);

  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);
  WiFi.setAutoReconnect(true);
  WiFi.persistent(false);

  Serial.print("Connecting WiFi: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  int retry = 0;

  while (WiFi.status() != WL_CONNECTED && retry < 40) {
    delay(500);
    Serial.print(".");
    retry++;
  }

  Serial.println();

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi connect failed. Retry later.");
    indiMode = INDI_CONNECTING;
    return;
  }

  IPAddress ip = WiFi.localIP();
  ipNo = ip[3];

  Serial.print("WiFi connected. IP: ");
  Serial.println(ip);
  Serial.print("IP last number: ");
  Serial.println(ipNo);

  server.begin();
  serverStarted = true;

  Serial.println("TCP server started");

  indiMode = INDI_STANDBY;
}

// =====================================================
// 1行受信
// =====================================================
bool readLine(WiFiClient &client, String &line, uint32_t timeoutMs = 30000) {
  line = "";
  uint32_t start = millis();

  while (millis() - start < timeoutMs) {
    while (client.available() ) {
      char c = client.read();

      if (c == '\r') continue;

      if (c == '\n') return true;

      line += c;

      if (line.length() > 70000) {
        Serial.println("ERROR: input too long");
        return false;
      }
    }

    delay(1);
  }

  return false;
}

// =====================================================
// IR学習
// =====================================================
void learnIR(WiFiClient &client) {
  if (irMutex) xSemaphoreTake(irMutex, portMAX_DELAY);

  indiMode = INDI_LEARN;

  Serial.println("Waiting IR signal...");

  uint32_t start = millis();

  while (millis() - start < 10000) {
    if (IrReceiver.decode() ) {
      rawLen = IrReceiver.irparams.rawlen - 1;

      if (rawLen > RAW_BUFFER_LENGTH) {
        rawLen = RAW_BUFFER_LENGTH;
      }

      Serial.print("Received RAW length: ");
      Serial.println(rawLen);

      client.print("RAW:");

      for (uint16_t i = 1; i <= rawLen; i++) {
        uint32_t microsValue = IrReceiver.irparams.rawbuf[i] * MICROS_PER_TICK;

        if (microsValue > 65535) {
          microsValue = 65535;
        }

        client.print(microsValue);

        if (i < rawLen) {
          client.print(",");
        }
      }

      client.print("\n");

      IrReceiver.resume();

      indiMode = INDI_STANDBY;

      if (irMutex) xSemaphoreGive(irMutex);

      return;
    }

    delay(1);
  }

  client.print("ERROR:IR_TIMEOUT\n");
  indiMode = INDI_STANDBY;

  if (irMutex) xSemaphoreGive(irMutex);
}

// =====================================================
// IR送信
// =====================================================
void sendIR(WiFiClient &client, const String &payload) {
  bool ok = sendRawString(payload);

  if (ok) {
    recordIrSendReason("MANUAL_IR");
    client.print("OK:SENT\n");
  } else {
    client.print("ERROR:RAW_PARSE_FAILED\n");
  }
}

// =====================================================
// 自動送信用コード保存
// =====================================================
void autoSet(WiFiClient &client, const String &payload) {
  String raw = payload;
  raw.trim();

  if (raw.startsWith("RAW:") ) {
    raw = raw.substring(4);
    raw.trim();
  }

  if (raw.length() == 0) {
    client.print("ERROR:AUTOSET_EMPTY\n");
    return;
  }

  File file = SPIFFS.open(AUTO_IR_FILE, FILE_WRITE);

  if (!file) {
    client.print("ERROR:SPIFFS_OPEN_FAILED\n");
    return;
  }

  file.print(raw);
  file.close();

  client.print("OK:AUTOSET\n");

  Serial.print("Auto IR saved. bytes=");
  Serial.println(raw.length() );
}

// =====================================================
// 自動送信用コード削除
// =====================================================
void autoReset(WiFiClient &client) {
  if (SPIFFS.exists(AUTO_IR_FILE) ) {
    SPIFFS.remove(AUTO_IR_FILE);
    client.print("OK:AUTORESET\n");
    Serial.println("Auto IR deleted");
  } else {
    client.print("OK:NO_AUTO_FILE\n");
    Serial.println("No auto IR file to delete");
  }
}

// =====================================================
// LIMITSET
// 例: LIMITSET:31.5
// =====================================================
void limitSetCommand(WiFiClient &client, const String &payload) {
  float value = payload.toFloat();

  if (value < 0.0 || value > 50.0) {
    client.print("ERROR:LIMIT_RANGE\n");
    return;
  }

  limitTemperature = value;
  saveConfig();

  client.print("OK:LIMITSET:");
  client.print(limitTemperature, 1);
  client.print("\n");

  Serial.print("LIMITSET=");
  Serial.println(limitTemperature, 1);
}

// =====================================================
// LIMITOFF
// =====================================================
void limitOffCommand(WiFiClient &client) {
  limitTemperature = 0.0;
  saveConfig();

  client.print("OK:LIMITOFF\n");

  Serial.println("LIMIT OFF");
}

// =====================================================
// STATUS
// =====================================================
void statusCommand(WiFiClient &client) {
  client.print("UPTIME_MIN:");
  client.print(uptimeMinutes);
  client.print("\n");

  client.print("LIMIT_TEMP:");
  client.print(limitTemperature, 1);
  client.print("\n");

  client.print("LIMIT_MODE:");
  client.print(limitTemperature > 0.0 ? "ON" : "OFF");
  client.print("\n");

  client.print("AUTO_IR:");
  client.print(SPIFFS.exists(AUTO_IR_FILE) ? "YES" : "NO");
  client.print("\n");

  client.print("WIFI:");
  client.print(WiFi.status() == WL_CONNECTED ? "CONNECTED" : "DISCONNECTED");
  client.print("\n");

  if (WiFi.status() == WL_CONNECTED) {
    client.print("IP:");
    client.print(WiFi.localIP() );
    client.print("\n");
  }

  if (statusMutex) xSemaphoreTake(statusMutex, portMAX_DELAY);

  if (hasLastIrSend) {
    uint32_t ago = uptimeMinutes - lastIrSendMinute;

    client.print("LAST_IR_AGO_MIN:");
    client.print(ago);
    client.print("\n");

    client.print("LAST_IR_REASON:");
    client.print(lastIrSendReason);
    client.print("\n");

    client.print("LAST_IR_AT_MIN:");
    client.print(lastIrSendMinute);
    client.print("\n");
  } else {
    client.print("LAST_IR_AGO_MIN:NONE\n");
    client.print("LAST_IR_REASON:NONE\n");
    client.print("LAST_IR_AT_MIN:NONE\n");
  }

  if (statusMutex) xSemaphoreGive(statusMutex);

  client.print("FREE_HEAP:");
  client.print(ESP.getFreeHeap() );
  client.print("\n");

  client.print("OK:STATUS\n");
}

// =====================================================
// setup
// =====================================================
void setup() {
  Serial.begin(115200);
  delay(500);

  Serial.println();
  Serial.println("ESP32 IR Bridge starting...");

  pinMode(INDI_LED, OUTPUT);

  pinMode(DHT11_PW, OUTPUT);
  digitalWrite(DHT11_PW, LOW);

  dht.begin();

  irMutex = xSemaphoreCreateMutex();
  dhtMutex = xSemaphoreCreateMutex();
  statusMutex = xSemaphoreCreateMutex();

  if (!SPIFFS.begin(true) ) {
    Serial.println("SPIFFS mount failed");
  } else {
    Serial.println("SPIFFS mounted");
  }

  loadConfig();

  xTaskCreatePinnedToCore(
    uptimeTask,
    "uptimeTask",
    2048,
    NULL,
    1,
    NULL,
    1
  );

  xTaskCreatePinnedToCore(
    indicatorTask,
    "indicatorTask",
    2048,
    NULL,
    1,
    NULL,
    1
  );

  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
  IrSender.begin();

  xTaskCreatePinnedToCore(
    autoSendTask,
    "autoSendTask",
    4096,
    NULL,
    1,
    NULL,
    1
  );

  xTaskCreatePinnedToCore(
    tempWatchTask,
    "tempWatchTask",
    4096,
    NULL,
    1,
    NULL,
    1
  );

  connectToWiFi();
}

// =====================================================
// loop
// =====================================================
void loop() {
  static uint32_t lastWiFiRetry = 0;

  if (WiFi.status() != WL_CONNECTED) {
    if (millis() - lastWiFiRetry > 10000) {
      lastWiFiRetry = millis();
      connectToWiFi();
    }

    delay(10);
    return;
  }

  if (!serverStarted) {
    server.begin();
    serverStarted = true;
  }

  WiFiClient client = server.accept();

  if (!client) {
    delay(5);
    return;
  }

  Serial.println("Client connected");

  String line;

  if (!readLine(client, line, 30000) ) {
    client.print("ERROR:TIMEOUT\n");
    client.stop();
    Serial.println("Client timeout");
    return;
  }

  line.trim();

  Serial.print("Command: ");
  Serial.println(line.substring(0, min( (int)line.length(), 80) ) );

  if (line == "TMP") {
    String tmp = getTempHumidity();
    client.print(tmp);
    client.print("\n");
  }

  else if (line == "LEARN") {
    learnIR(client);
  }

  else if (line.startsWith("IR:") ) {
    sendIR(client, line.substring(3) );
  }

  else if (line.startsWith("RAW:") ) {
    sendIR(client, line.substring(4) );
  }

  else if (line.startsWith("AUTOSET:") ) {
    autoSet(client, line.substring(8) );
  }

  else if (line == "AUTORESET") {
    autoReset(client);
  }

  else if (line.startsWith("LIMITSET:") ) {
    limitSetCommand(client, line.substring(9) );
  }

  else if (line == "LIMITOFF") {
    limitOffCommand(client);
  }

  else if (line == "STATUS") {
    statusCommand(client);
  }

  else {
    client.print("ERROR:UNKNOWN_COMMAND\n");
  }

  client.stop();
  Serial.println("Client disconnected");
}

 

 

前回コードの問題点

以前のコードには、いくつか問題がありました。

  • String型で送受信をしていたため、エアコンの長い赤外線コードを保持しきれない&全受信しきれない
  • WiFi接続失敗時の考慮不足
  • 停電復旧時にWiFi待ちが発生する
  • 温度異常時の対策が存在しない
  • 状態確認機能が無い

 

 

今回、赤外線コードが送信できなくなったというキッカケを得て、実用性をかなり強化してみました。

追加した主な機能はこちらです。

  • 停電復旧後の自動冷房ON
  • 設定温度以上での自動冷房ON
  • 温度監視ON/OFF
  • 状態監視(STATUS表示)
  • 最終送信理由の記録
  • WiFi自動再接続
  • 起動時間管理

 

停電後でもESP32単体で冷房復旧

今回特に重要視したのが、ネットワーク機器が死んでいても動くことです。

 

一般的な遠隔監視&制御のIoT機器と同様に構築するには、

 

WiFi接続

クラウド接続

スマホアプリ

 

のような構成になりがちですが、停電復旧後はルーターやRaspberry Piが故障しているような可能性も考えられます。

 

そこで今回の装置では、

「予め学習した冷房用の赤外線コードをESP32本体へ保存し、WiFi接続を待たずにESP32単体で120秒後に冷房ON信号を送信」

とするようにしました。(120秒というのはまあエアコンが通電開始してそのぐらい待てばスタンバイ状態になっているであろうという適当な考えです。)

これにより、仮にネットワーク機器側が故障していても、最低限エアコンだけは復旧できるようになっています。

 

温度異常時は自動で冷房ON

さらに、設定温度以上を検出した場合、自動で冷房ON信号を送信する機能も追加しました。

例えば、

  • 家族が誤って消してしまった
  • リモコン操作ミス
  • 停電以外の原因で停止した

といったケースにも対応できます。

 

ロジックは

31℃を検出

5分後に再確認

まだ31℃以上なら冷房ON

 

 

という感じで、誤検知しにくいようにしてあります。

ただ、今回使用した温度センサー(DHT11)はそこまで高精度ではなく、長期運用時の信頼性にも少し不安があるため、温度監視機能自体をON/OFFできるようにもしてあります。

 

個人的には、今後もう少し信頼性の高い温度センサー(SHT31など)へ載せ替えたいところですが、既に基板が完成しているのでそのまま流用します。

 

Pythonから簡単操作


また、赤外線コードの学習や設定変更などは、Pythonプログラムに書き、コンソール操作で簡単に操作できるようにしましたので、ラズパイなどへの移植もスムーズに行えます。

 

python側赤外線信号送信プログラム sendIR.py

 

#!/usr/bin/env python3
import argparse
import socket
import sys
from pathlib import Path

DEFAULT_HOST = "192.168.0.15"
DEFAULT_PORT = 500
TIMEOUT = 40


def send_command(host, port, message, timeout=TIMEOUT):
    if not message.endswith("\n"):
        message += "\n"

    with socket.create_connection( (host, port), timeout=timeout ) as sock:
        sock.settimeout(timeout)
        sock.sendall(message.encode("utf-8") )

        chunks = []

        while True:
            try:
                data = sock.recv(4096)
            except socket.timeout:
                break

            if not data:
                break

            chunks.append(data)

            # STATUSは複数行なので OK:STATUS まで読む
            text = b"".join(chunks)
            if b"OK:STATUS\n" in text:
                break

            # それ以外は1行で終了
            if b"\n" in data and not message.startswith("STATUS"):
                break

        return b"".join(chunks).decode("utf-8", errors="replace").strip()


def load_raw_file(file_path):
    path = Path(file_path)

    if not path.exists():
        print(f"ファイルがありません: {file_path}")
        sys.exit(1)

    raw = path.read_text(encoding="utf-8").strip()

    if raw.startswith("RAW:"):
        raw = raw[4:].strip()

    if not raw:
        print("ファイル内のRAWデータが空です")
        sys.exit(1)

    return raw


def learn(args):
    print("ESP32に学習要求を送信中...")
    print("赤外線リモコンをESP32に向けて押してください。")

    res = send_command(args.host, args.port, "LEARN", timeout=30)

    if not res.startswith("RAW:"):
        print("学習失敗:", res)
        sys.exit(1)

    raw = res[4:].strip()

    Path(args.file).write_text(raw + "\n", encoding="utf-8")

    print(f"保存しました: {args.file}")
    print(f"データ長: {len(raw)} 文字")


def send_ir(args):
    raw = load_raw_file(args.file)

    print(f"送信中: {args.file}")

    res = send_command(args.host, args.port, "IR:" + raw, timeout=40)

    print("ESP32:", res)

    if not res.startswith("OK"):
        sys.exit(1)


def tmp(args):
    res = send_command(args.host, args.port, "TMP", timeout=10)
    print(res)


def autoset(args):
    raw = load_raw_file(args.file)

    print(f"自動送信用コードをESP32へ保存中: {args.file}")

    res = send_command(args.host, args.port, "AUTOSET:" + raw, timeout=40)

    print("ESP32:", res)

    if not res.startswith("OK"):
        sys.exit(1)


def autoreset(args):
    print("ESP32内の自動送信用コードを削除中...")

    res = send_command(args.host, args.port, "AUTORESET", timeout=20)

    print("ESP32:", res)

    if not res.startswith("OK"):
        sys.exit(1)


def limitset(args):
    value = args.temperature

    if value < 0.0 or value > 50.0:
        print("リミット温度は 0.0〜50.0 の範囲で指定してください")
        sys.exit(1)

    print(f"リミット温度を {value:.1f}℃ に設定中...")

    res = send_command(args.host, args.port, f"LIMITSET:{value:.1f}", timeout=20)

    print("ESP32:", res)

    if not res.startswith("OK"):
        sys.exit(1)


def limitoff(args):
    print("温度リミット自動送信をOFFにします...")

    res = send_command(args.host, args.port, "LIMITOFF", timeout=20)

    print("ESP32:", res)

    if not res.startswith("OK"):
        sys.exit(1)


def status(args):
    print("ESP32の状態を取得中...")

    res = send_command(args.host, args.port, "STATUS", timeout=20)

    print(res)

    if "OK:STATUS" not in res:
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(
        description="ESP32 IR Bridge controller"
    )

    parser.add_argument(
        "--host",
        default=DEFAULT_HOST,
        help=f"ESP32のIPアドレス default={DEFAULT_HOST}"
    )

    parser.add_argument(
        "--port",
        type=int,
        default=DEFAULT_PORT,
        help=f"ESP32のポート default={DEFAULT_PORT}"
    )

    sub = parser.add_subparsers(dest="command", required=True)

    p_learn = sub.add_parser("learn", help="赤外線コードを学習して保存")
    p_learn.add_argument("file")
    p_learn.set_defaults(func=learn)

    p_send = sub.add_parser("send", help="保存済みコードを送信")
    p_send.add_argument("file")
    p_send.set_defaults(func=send_ir)

    p_tmp = sub.add_parser("tmp", help="温湿度を取得")
    p_tmp.set_defaults(func=tmp)

    p_autoset = sub.add_parser("autoset", help="停電復帰後に自動送信するコードをESP32へ保存")
    p_autoset.add_argument("file")
    p_autoset.set_defaults(func=autoset)

    p_autoreset = sub.add_parser("autoreset", help="ESP32内の自動送信用コードを削除")
    p_autoreset.set_defaults(func=autoreset)

    p_limitset = sub.add_parser("limitset", help="温度リミットを設定")
    p_limitset.add_argument("temperature", type=float, help="例: 32.0")
    p_limitset.set_defaults(func=limitset)

    p_limitoff = sub.add_parser("limitoff", help="温度リミット自動送信をOFF")
    p_limitoff.set_defaults(func=limitoff)

    p_status = sub.add_parser("status", help="ESP32の状態を表示")
    p_status.set_defaults(func=status)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()

 

<送信例>

赤外線コード学習

python sendIR.py learn cool.code

ESP32へ向けてリモコン操作を行い、赤外線コードを保存します。

 

通常送信

python sendIR.py send cool.code

保存した赤外線コードを送信します。

 

停電復旧用コード登録

python sendIR.py autoset cool.code

ESP32本体へ冷房ONコードを保存します。

これにより、停電復旧後にESP32単体で自動送信できるようになります。

 

停電復旧コード削除

python sendIR.py autoreset

ESP32内の自動送信コードを削除します。

 

温度監視ON(31℃へ設定)

python sendIR.py limitset 31.0

31℃以上で冷房ONする設定です。

 

温度監視OFF

python sendIR.py limitoff

温度監視による自動冷房を停止します。

 

状態確認

python sendIR.py status

現在の状態を確認できます。

何分前になぜ送信したか?まで確認できるようにしてあります。

 

(実際に設定値を超えてお昼に自動送信した事例)

実際に動作していたときは「あってよかったー!」と思いました。

 

このPythonコードも含め、ChatGPTへ「こういう感じの機能が欲しい」と相談しながら作っていったのですが、

正直、「ここまで一瞬で実用コードが完成するの!?」とかなり驚きました。

 

以前なら、仕様整理からデバッグまで含めて何日〜何週間も掛かっていたような作業が、対話しながら数分でどんどん形になっていきます。


最近はAIに聞けばほぼほぼ動くプログラムが完成してしまうので、このコードをブログに載せる意味があるのか否か悩みましたが・・・、AIを使った作品例と備忘録として残しておくことにしました。

 

設置した状態。ほぼ基板むき出し状態・・・これもそのうちきちんと作り直したいな

 

最後にお決まりの文句ですが、この作例はあくまで個人の作品例であり、装置の作成を推奨するものではありません。
※上記のコードの動作保証はいたしません。高温環境やペット環境で使用する場合は、必ず実機テストを行い、自己責任で運用してください。

車の謎のエラーの原因は、もしかしてUSB充電器?Type-Cから逆流してる!? (仮説)

フリードのエンジン起動時に稀にイモビライザーランプが点滅してエンジンがかからない。という謎現象がありました。

youtu.be

 

以前の記事で「原因は不適合なバッテリーを使っていたから!」

と結論付けていましたが・・・・

dreamerdream.hateblo.jp

 

調子を取り戻したので安心していた矢先に再発しました。

😨えー!

 

よくよくよくよく考えてみると・・・🤔・・・

スマホをType-Cの充電器で充電しっぱなしの場合に発生しているように感じます。

 

車の調子悪くて預けている時は充電器を外していましたので、ここしばらく繋いでいませんでした。

 

✔朝は発生しない(朝から充電することないから)

✔充電器に繋ぎっぱなしでコンビニ入って再始動時にトラブル多い

 

うーん、実に怪しい・・・・けど、そんなことある??

 

実は、以前からスマホをUSB Type-C端子に接続している時にUSB Type-A端子のLEDランプが点滅しているのを確認していました。

これは、スマホのUSBから他の機器へ電気を送る相手を探している挙動なので、もともとそういう仕様でしょう。

しかし、まさかUSBからシガーソケット側へ逆に電気が送られることは無いだろうと思っていました。

 

シガーソケット側に電気が流れているのかとテスターで観察してみると、なんと4~5V程度の電圧が3秒に1回程度の頻度でシガーソケットに送られています。

(別のスマホを繋いだら6秒に1回程度でLEDが光りました。また光らないスマホもありました。スマホの機種によって挙動が違うようです)

 

動画に纏めました↓

youtube.com

 

 

え?まって!

これって車のECUに影響することないの?

ネットで情報を探しても、そのものの事例が見つからなかったので、chatGPTに聞いてみました。

 

ECUが「半起動状態」になる

通常:

  • キーOFF → ECU完全停止

でも5Vが入ると:

  • 内部回路の一部だけ通電
  • 正常な起動シーケンスが崩れる

👉 これが一番危ない

 

 

 ノイズ源として優秀すぎる

  • 周期的(2〜3秒)
  • GND共有

👉 一番嫌なタイプのノイズ

 

なぜACCがECUに影響するのか

● 電源ラインは完全分離じゃない

車の電源は:

  • 同じバッテリー
  • 同じアース

👉 つまり
ACCに変な電圧が乗る=全体に影響する可能性あり

 

エンジン始動時

  • 正常な起動シーケンスが必要なのに
  • すでに中途半端に起きてる

👉 結果

  • 認証ズレ
  • 通信エラー
  • 始動失敗

 

つまり、エンジンを切ってもACCに微量に電気が流れ、本来OFFになっていないといけないはずの回路に微量に電気が流れて完全OFFにならない。

OFF→ONで起動すべきシステムが完全OFF状態になっていないので、起動時にエラーの原因となっている可能性があるとのことです。

 

なるほど!

よく思い返すと、短時間だけスマホを繋いだまま停めていた場合で頻発していましたか、電気が回路に十分に残っている、ACC回路が放電しきらない状態であったと推測できます。

タイミングによってエンジンかかったりかからなかったりするのは、スマホから間欠的に給電される約3秒間の間にちょうど電圧がかかっている状態だとNG、放電された状態だとOK。という感じだと推測されます。

エンジンがかからない状態でも、一度ドアを開けてからスタートを押すと、比較的エンジンがかかりやすい状態だと感じていたのですが、ドアオープンのセンサーなどで電力を消費したからだと推測されます。

🤔結構いろいろヒントがありますね。

 

あくまで可能性であって確定ではないですが、再現するのか試すとECUに不可逆的な悪影響が出る可能性もあるので、ひとまずしばらくはUSB充電器を使わないようにして、エンジン始動に失敗することがないか様子を見てみることにします。

 

ここまでは完全に仮説なので追加での検証が必要です。

もし同じようにホンダの車で不定期にエンジンがかからない方で、且つダイソーのUSB Type-C充電器を使っている方がありましたら教えてほしいです。

 

追記:<▼▼似たような事例を発見!!▼▼>

USB充電器でエンジン始動困難発生 ご注意を(マツダ CX-5)by ShigeCX-5 - みんカラ

シガーソケットにusb充電器を繋いでスマホを充電しています。ス... - Yahoo!知恵袋

電流が逆流!スマホ充電のまま、エンジン止めたら、ナビが消えない!

知らないと危険!車内スマホ充電でよくある故障リスク - まさしんBLOG

 

やはり、USB充電器によっては車のエンジンがかからなくなる。というトラブルに見舞われることがあるようです。

 

ECUってACC共有とかじゃなく、もっと特別な回路を使ってエンジンONOFF検出しているのかと思っていましたし、そもそも元々ノイズの多い車の電装系だからかなりシビアなテストされているはずなので、当然そういった逆流ノイズ的なものは対処されていると思っていました。

もしこれが原因だったらこれはメーカーさんには対処しておいてもらいたいです!

 

ということで、車のエンジンがかからない、もしくはOFFにできない、ナビが消えないという謎のトラブルが発生して焦ってググっている方に「一度シガーソケットに刺さっているカーチャージャーを抜いて試してみて」というアドバイスを残しておきます。

 

<追記>

6月になり、Type-Cにはスマホを繋がないということを徹底しています。

今の所エンジンがかからないという事態には陥っていないので、ほぼほぼUSB充電器が原因ということは確定でしょう!

直ったはずの警告灯が再発…原因はP0172(燃料濃すぎ)|OBDで特定→エアフロ清掃で解決 備忘録

以前、車の警告灯が一斉に点灯し、その原因が「不適合なバッテリーではないか?」という記事を書きましたが――

dreamerdream.hateblo.jp

 

 

なんと、調子を取り戻してから5日ほどで再発しました。えええ!!?😱

 

すぐにOBDセンサーのスクリーンショットを撮影し、ChatGPTに解析を依頼しました。

🔍 診断結果

P0172(燃料が濃すぎる)

👉 ガソリンが多すぎる状態です。

 

そのまま原因の推測までしてくれました。

🧠 原因の流れ(推定)

① 燃調異常(P0172)

② ECUが補正を繰り返す

③ 発電制御にも影響(※ホンダ車でよくある挙動)

④ 電圧低下(12V台へ)

⑤ ECU間通信が不安定

👉 U0401 / U0416 発生

すごいぞ!chatGPT!!!

🔧 原因候補(優先度順)

1️⃣ エアフロセンサー(最有力)
・汚れ
・誤検知
👉 燃料を多く噴いてしまう

2️⃣ 吸気系の問題
・エアフィルター詰まり
・吸気制限

3️⃣ O2センサー劣化
・空燃比の判断ミス

 

なるほど!怪しいのは分かりました。しかし、車のことは全く知らない僕・・・

吸気グラフなんて見ても全くわかりません!!!

「ところで、センサーのどこを見ればいいの?」という質問をしました。

 

最も重要なのは👇

🥇 短期燃料補正(STFT)

👉 今現在の燃調補正

🥈 長期燃料補正(LTFT)

👉 ECUの学習値

🧠 STFTの見方

  • 0%付近 → 正常
  • +(プラス) → 薄い(燃料を足している)
  • −(マイナス) → 濃い(燃料を減らしている)

 

ということで、さっそくSTFTとLTFTのセンサー値を見ました。

 

走行中:ほぼ0付近なんだけど、アイドリング時:-13%前後、最大で-21%まで落ちてる!?

LTFT:-7%前後

 

へぇ、・・・・そもそも正常値知らない・・・、ということでこれもchatGPTへ投げます(笑

🔥 ここから分かること

👉 ECUは常に「燃料を減らす方向」に補正している

つまり

  • 長期補正(LTFT)で -7%
  • さらに短期補正(STFT)で追加調整

👉 それでも補正しきれずエラー発生

 

そうなんだ!!え?でもバッテリー交換で直ってたよね?🤔???

 

🔄 なぜ一度直ったのか?

バッテリー交換により

👉 ECUの学習値(LTFT)がリセット

一時的に正常に戻るも…

👉 再学習によって

再びズレ発生

👉 エラー再発

 

🛠 対応

まずは原因の本命である

👉 吸気センサー(エアフロ)の清掃

 

とのことです。

そしてエアフロのMAFセンサーとMAPセンサーの清掃はパーツクリーナー(電子部品用)を吹くだけで簡単だと判明しました。

しかし、中身は車種によって異なるし、非常に繊細な部品のため、これは下手に触ると破損リスクあります。

万が一壊したら代車がない!という状況もあり、DIYは断念。今回はリスク回避のため整備工場に依頼しました。

  • 吸気センサー清掃
  • スロットル清掃
  • ECU学習リセット

合計 8,800円なり。

気のせいかもしれませんが、作業を終えた車はアイドリングが非常に安定している感じがします。

 

早速OBDで確認すると…

補正値がほぼ0付近で安定!しかも、調子の良い時は完璧に0に張り付いています。

以前とはまさに雲泥の差!!すご!!

 

さらに

  • 燃費改善

  • アイドリングストップ復活

アイドリングストップから復帰したときのグラフ(※この機能は復活してほくなかったので複雑な気持ちですが…笑)

 

 

今回大活躍したのがODBセンサーと「Car Scanner」アプリです。

 

play.google.com

 

この際有料版にしました!

広告なしで、グラフ最大4つ表示可能となり、診断精度が一気に上がります。

 

まとめ

<今回のポイント>

  • P0172は「燃料が濃い」エラー
  • 原因はエアフロセンサー汚れの可能性大
  • STFT / LTFTを見ると状態が一発で分かる
  • ECUリセットで一時的に直るケースあり
  • chatGPT+OBDアプリはめちゃくちゃ有用

chatgpt.com

 

ここまでいい感じなんだから、さすがにもう直ったんじゃないの??と思いますが、もう少しセンサーは取り付けたまま様子をみることにします。

また何か合ったらこちらに追記します。

 

<追記>6月に入って全くエラー点灯の兆候はありません。アイドリングストップもOFFし忘れると復活しているので、どうやら本当に直っているようです。

しかし、また別の問題が・・・、この前バック時にバックモニターが一瞬固まっててすごく危なかったんだけど・・・、今回バックから→Dレンジにしたところ、上半分がバックモニターのまま固まってる!?度々そんなことあるの??HONDA車やばくないですか??

youtu.be

突然の警告灯5連発!!HONDAフリードが壊れた…と思ったら“まさかの原因”だった 備忘録

【突然の警告灯5連発】HONDAフリードGB5で謎のエラーが発生

HONDAフリード(GB5)に乗っているのですが、ある日駐車場に停めた矢先に突然、複数のエラー警告灯が一斉に点灯しました。

表示された内容はこちら。

・エンジンシステム点検
・パワーステアリングシステム点検
・VSAシステム点検
・ヒルスタートアシストシステム点検
・路外逸脱抑制システム点検

…いや、多すぎて怖い((((;゚Д゚))))

とりあえず整備工場へ

さすがに怖かったので慌てて車屋さんに連絡し、代車を用意してもらい点検をお願いしました。

結果は…

異常再現せず・・・

車屋さんが原因が定かでないとのことで、念のためHONDAにも調査依頼してもらったそうですが、結局原因は不明のまま。

「今度再発したら分解点検しましょう(高額ですが・・・)」ということで、
約2週間後にエラーの消えた車が戻ってきました(費用:3,500円)

 

 

ネットで調べると原因はバラバラ

車屋さんにお預かりしてもらいながらいろいろ自分で調べてみると、同様の症状は意外と多く、

・吸気センサー異常
・バッテリー劣化
・プラグ不良

など、原因はバラバラです。結局なんもわからないまま・・・

 

 

心当たりは「バッテリー」

ここで1つ思い当たることがありました。

以前エンジンが一発でかからなくなったときに、次エンジンがかからなくなったら怖いので急遽ホームセンターでバッテリー交換したことがあったのですが…

アイドリングストップ車に充電制御車用バッテリーを装着していました。

本来はNGですが、夜遅かったので他の店を回る時間が無かったのと「アイドリングストップはOFFにしてるし大丈夫だろ、エンジンかからなくなるよりマシ!」と装着。そのまま1年ほど使用していました。

これ、しばらくは普通にアイドリングストップも使えてたんですけどね。


今思えば、下動画に含めた謎動作不良もこのバッテリーが起こしていたのかもしれません。

youtu.be

 

OBD2で電圧チェックしてみた

今回はせっかくなので検証してみることにしました。

OBD2というコネクタから車のデータを取得できると知り、Amazonで安い装置を購入(約2,000円)

スマホと連動して電圧や水温などがリアルタイムで見れるやつです。

本格的なものは数万円するそうですが、このお値段ならダメ元で試す価値ありですね。

 

Android用アプリはスキャナーの案内に載っているものではなく、評価の良い「Car Scanner」というアプリを使いました。

play.google.com

 

 

1日目にまさかの再発

なんと、車が帰ってきた翌日、ODB2装置を使い方もよくわからないまま装着して電圧グラフを表示しながら走り出しして5分ぐらい?…同じエラーが再発しました!

タイミングは信号待ちで停止した時です。

電圧を見ると14V → 12.9Vに急降下していました。

「これが原因か?」と思ったものの、調べると一応正常範囲らしい…ちがうのかな?

一応、路肩に停めてエラーの読み出しを行いました。

吸気系のエラーやら何やら盛りだくさんです。

 

そして、エラー情報のクリアを試みます。

エラーのクリアは自己責任でどうぞという感じの案内が沢山出ます。

 

エラーが消えたり消えなかったり…

ODB2装置でエラーのクリアを試しました。結果、

・一部だけ消える
・完全には消えない

安物だから?

ひとまず、わからないので帰宅時まで放置していたら帰りには全エラーが消えていました。これでいいのかな???

 

わからないけど、乗り心地も変わらないしそのまま帰宅しました。

 

そして再びエラー発生

翌日は帰宅時まで問題なしでした。

念願のバッテリーが到着しましたが、大雨で交換作業は断念です。

その日の夜、再び警告灯が点灯しました・・・しかもOBD未接続時にかぎって・・・

しかし、この2日間である程度は電圧の変動が予想できるようになっています。

今回は駐車中ですので、やはり電圧が下降したタイミングだと考えられます。

 

 

バッテリー交換を決行

次の日、朝のバタバタの中慌ただしくバッテリー交換をしました。

そしてドキドキしながらエンジン始動すると…

エラー全消え

やった!!

 

 

電圧の違いがヤバい

OBDで比較してみると衝撃の結果。

■旧バッテリー

・アイドリング時は14V〜12Vを常に上下
・エンジン回転も不安定だったみたい(100RPM差ぐらい)

・停止時、エンジン回転数が落ちると同時に電圧が下降してアイドリングで戻す感じ

・始動時、9.6V程度まで電圧下降が起こっています

 

■新バッテリー

・アイドリング時は14V or 12V近辺で常に安定
・回転数も安定(10~30RPM差程度)

・停止時、エンジン回転数が落ちると同時に電圧が下降していますが、アイドリングの回転数は上がらず、そのまま電圧だけ戻っている様子です

・始動時、10V程度まで電圧下降が起こっていますが、アイドリング回転数がすごく安定しています

 

 

全然違う!!もはや別の車かと思うレベルです。

アイドリングも明らかに安定しています。(いままでが不安定だったんだと今更気が付きました)

元々、中古車で手に入れたこの車、最初からアイドリングの若干の不安定を感じていましたが初めてのHONDA車だったので、「HONDA車ってこんな感じなのかー」で済ましていました。

前の充電制御車用バッテリーに交換した後もアイドリングの感じは変わらなかったので、やはり充電制御車用バッテリーはNGだったってことですね。これは深く反省。。

 

さらに驚きの変化→「燃費」

旧バッテリー:13km/L(良くて15km/L)
新バッテリー:17km/L

😯え、バッテリーで燃費変わるの!?

これは完全に予想外でした。一般道でこれ?まじか!?

ガソリン高騰の昨今これは嬉しいです。

アイドリングが安定したから?ECUが正常に働いているから?

 

※あと、フリードはバッテリー交換後に手動でリセットしないとアイドリングストップが機能しないようです。

元々アイドリングストップを常にOFFボタンで機能しないようにしながら乗っていたのでこれは逆にありがたい。このまま乗ることにしました。

potechi-seibi.online

 

 

結論:バッテリー、ナメたらダメ

今回の教訓です。

👉 アイドリングストップ車には必ず専用バッテリーを使うべし

たとえ機能をOFFにしていても、

・電圧制御
・発電制御
・ECU挙動

に影響が出る可能性があるということがわかりました。

 

 

まとめ

・複数エラー=センサーだけとは限らない
・電圧不安定はかなり怪しい
バッテリー不適合で不具合は起きる

ODB2装置は安物でも持っていて損はない

そして何より…

バッテリーの性能で燃費が変わる!という知見

を得ました。

 

もし、同じエラーが今後出たら原因が他にあったということになりますので追記することにします。

 

<追記:原因はバッテリーじゃなかった!!!!>

dreamerdream.hateblo.jp

dreamerdream.hateblo.jp

 

 

今回は以上です。

 

修理30万円と言われた水没ノートPC、分解してみたら復活した話

上司から
「ノートパソコンにお酒をこぼしてしまって起動できなくなった」
と相談を受けました。

話を聞くと、

  • 電源は入る

  • 画面に何やら表示されている

  • しかしキーボードが反応しない

という状態。

 

その時の写真↓

CMOS Date/Time Not Set.

Press F2 to Enter BIOS Setup.

というので、F2キーが効けばBIOS設定に辿り着けそうなのですが・・・全くキーボードが反応しません。連打や長押し、電源ボタン同時押しなどChatGPTに相談しながら起動を試みましたが無理でした。

 

まだ新しいパソコンだったので、いきなり素人分解して取り返しがつかなくなると困ります。
ひとまず メーカー修理に出した方がいいと伝えました。

 

PCはNECのVAVIEのSOLシリーズで、型番は「PC-S1376JAB-J」標準モデル「PC-S1375JAB」の上位モデルのようです。

↓タッチパネル対応2025年式のなかなか良いノートPC

 

メーカー修理の見積もりは30万円

数日後、上司から連絡がありました。

メーカーからの回答は

「内部部品のほとんどを交換する必要があるため、修理費用は約30万円」(厳密には33万と言われたらしい)

とのこと。

 

内訳は

・メインボード交換

・液晶ケーブル交換

・ボトムカバー/トップカバーユニット交換

・RTCバッテリ交換

・SSD交換

・Windows再インストール

嘘みたいな内訳・・・思わず

「それなら新品買えるやん…」(新品で15万ぐらい?)

という話になり、上司は新しいパソコンを購入。

そして、

「どうせ捨てるしかないし、部品取りとかする?」

と言われたので、その壊れたパソコンを譲り受けました。

 

 

「BIOS画面さえ復活したらなんとかなりそうなんだけどなー・・・外付けキーボードとか試せないかしら?画面は生きてるはずだから、画面割れた中古機に載せ替えられたりしないかしら?」とか考えながら・・・

 

家で電源を入れてみると…

さっそく電源を入れてみました。

しかし今度は

画面すら表示されない。orz....悪化しとる

キーボードの一部が一瞬光るだけで、すぐ電源が落ちてしまいます。

液晶の生死まで不明になりました・・・完全にダメかもしれませんが、
どうせ壊れているから早速分解してみることにしました。

 

とりあえず分解

以下、このシリーズは型が新しすぎて分解情報が得られなかったので人柱としての報告です。

赤矢印の方向に爪がついていて本体に潜り込んでいるので、赤矢印の方向に小さなマイナスドライバーなどでキャップを起こしてやると両面テープ固定の足のキャップが簡単に外れます。

そしてその下に+ネジが1つづつ付いています。

あとは、後ろの少し隙間のあるところにヘラを入れてパキパキと爪を折らないよう慎重に外していきます。

 

ご開帳〜

 

このノートパソコンは裏蓋を外すと簡単に内部にアクセスできる構造でした。

まずは安全のため

バッテリーを外した状態で電源投入してみます。(今更だろうけど)

バッテリーは端子を赤矢印の方向から起こしてやると素直に外れます。

 

そのまま電源投入しましたが、症状は全く変わりません。。。。ですよね

 

CPU付近が異常に熱い

基板を触っていると、CPU付近が妙に熱いことに気付きました。

よく見ると…

CPUクーラーがグラグラの仮止め状態。

ヒートシンクのネジがきちんと締まっておらず、
放熱がまともにできていない状態のようでした。

もしかすると

熱暴走で停止、あるいは熱ですでにCPUが破損している可能性

があります。

 

いやいや、メーカー修理から帰ってきて仮止め状態ってどうなのよ?

というか、どこで液体でショートしているかわからないんだから、バッテリー端子の方を抜いておいてほしかったな・・・とボヤキながら・・・

 

端子の変色を発見

基板を詳しく観察すると

  • SSD端子

  • メインボードの一部

が 一番茶色っぽく変色しています。おそらく腐食でしょう・・・結構ヤバいなこれ

他にもところどころ変色している端子もある様子・・・もしかしたら本体を持ち運んだときにいろんなところにお酒が付着して腐食が進んでしまった可能性もあります。

やはりこれだと基板交換という結論になりそうですね。

そこで

  • SSD

  • ヒートシンク

  • 外せるパーツ

をすべて取り外し、

無水エタノール+歯ブラシ

で端子部分を重点的に清掃しました。あと、強い腐食部はメラミンスポンジで軽く削ってみました。

 

(ごめんなさい。正直、直ると思っていなかったので洗浄中の写真はほとんど撮っていません。
変に「ブログネタになるかも」と思って写真を撮り始めると直らないジンクスがあるので、今回も半分諦めながら作業していました。)

方法は、大きめのお盆に無水エタノールをたっぷり入れて、基板を浸しながら歯ブラシでゴシゴシしました。

お酒で酔い潰れたPCに更にアルコールを追加するという悪の所業👿🍻

 

<分解画像>

ファンは左右に1つづつ。

わざわざ右用左用で分けてあるところにNECのこだわりを感じます。

ヒートパイプもネジ4本で簡単に外せます。

キーボードとボトムカバーは一体になっていて外せませんでしたが、メインボードは割と簡単に外れます(ヒンジのところに一部潜り混んでいるので、ヒンジのネジを少し緩めたほうが外しやすい)。

液晶画面とボトムカバーもヒンジのネジ2本ずつ外すだけで割と簡単に分離できます。

 

古いCPUグリスもしっかり取ります。

 

洗浄後

ピカピカ✨️シールやら組み立て用のマーキングやら色々消えてます。

 

その後

  • ドライヤーである程度乾燥

  • そのまま2日間放置

して完全乾燥させました。

 

2日後、再組み立て

CPUクーラーをしっかり固定し、
CPUグリスも塗り直して再組み立て。

そして恐る恐る電源を入れると…

画面が点いた!

しかも

F2キーが押せる!

なんとキーを認識してBIOS画面に入ることができました。

日付をセットし直して、そのまま起動すると…

Windows起動成功しました!

 

 

さらに

バッテリーもおそるおそる接続したら正常に充電されました。

まさかの復活です。

 

持ち主に報告した結果

上司に

「直ったっぽいです」

と伝えたところ

「じゃあそのまま使っていいよ」

とのこと。

ありがたく譲り受けることになったので、
初期化して使うことにしました。

 

 

ただ、SSD端子などの変色が少し気になるので、

いつまで使えるかは未知数です。

 

 

このノートPC、実はメンテナンス性がかなり良い

今回分解して思ったのですが、このノートはかなり整備しやすい構造でした。

  • 裏蓋含め、部品の取り外しはプラスドライバー1本

  • SSD交換可能

  • ファン交換可能
  • バッテリー交換可能

つまり

メインボードさえ無事なら、ある程度の部品が交換できます。

 

まとめ

酒をこぼして起動しなくなったノートパソコン。

メーカー修理見積もりは
約30万円。

しかし

  • 分解

  • 無水エタノール洗浄

  • 乾燥

  • CPUグリス塗り直し

まさかの復活を果たしました

修理費用は主に無水エタノールで500円ぐらいかな?

電子機器は液体がかかると即終了と思われがちですが、

清掃で復活するケースもあるんですね。

さて、このノートPC
いつまで元気に動いてくれるでしょうか?

 

スペックも十分なので使い倒します。

 

※今回はお酒をこぼして起動しなくなった NEC LAVIE SOL PC-S1376JAB-J を分解し、端子の清掃とCPUクーラーの再固定で復活しました。

同じように「F2キーを押してください」と表示されるのにキーボードが反応しない、電源は入るのに起動しないといった症状で困っている方の参考になれば幸いです。

 

もし無水エタノールを使う際は火気厳禁です。気を付けてください。

また、お決まりですが分解は完全に「自己責任」でお願いします。

 

kampa.me