From 2e3cd4220a9d58b1e2190471c6d775cdc5048d68 Mon Sep 17 00:00:00 2001 From: Koichi Ito Date: Fri, 9 Oct 2026 16:07:24 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=8F=E3=83=BC=E3=83=89=E6=9C=AA=E6=8E=A5?= =?UTF-8?q?=E7=B6=9A=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/SensorLogger.cpp | 120 +++++++++++++++++++++++--------------- firmware/SensorLogger.h | 3 +- firmware/firmware.ino | 71 +++++++++++++--------- 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/firmware/SensorLogger.cpp b/firmware/SensorLogger.cpp index 8695591..5a3aaac 100644 --- a/firmware/SensorLogger.cpp +++ b/firmware/SensorLogger.cpp @@ -74,65 +74,70 @@ private: SensorLogger* logger_; }; -void SensorLogger::begin(uint8_t soilPin) { +void SensorLogger::begin(uint8_t soilPin, bool i2cConnected, bool bleDataConnected) { // センサーとBLE通信を使い始めるための初期設定です。 soilPin_ = soilPin; + bleDataConnected_ = bleDataConnected; pinMode(soilPin_, INPUT); analogReadResolution(12); // センサーが接続されているときだけ初期化します。 // 未接続時にライブラリへアクセスすると、不要なI2Cエラーや待ち時間が発生します。 - if (isI2cDevicePresent(0x38)) { - aht20Ready = aht20.begin(); - if (!aht20Ready) { - Serial.println("[ERROR] AHT20 initialization failed"); + if (i2cConnected) { + if (isI2cDevicePresent(0x38)) { + aht20Ready = aht20.begin(); + if (!aht20Ready) { + Serial.println("[ERROR] AHT20 initialization failed"); + } + } else { + Serial.println("[WARNING] AHT20 not detected at 0x38"); } - } else { - Serial.println("[WARNING] AHT20 not detected at 0x38"); - } - if (isI2cDevicePresent(0x23)) { - bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); - if (!bh1750Ready) { - Serial.println("[ERROR] BH1750 initialization failed"); + if (isI2cDevicePresent(0x23)) { + bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); + if (!bh1750Ready) { + Serial.println("[ERROR] BH1750 initialization failed"); + } + } else { + Serial.println("[WARNING] BH1750 not detected at 0x23"); } - } else { - Serial.println("[WARNING] BH1750 not detected at 0x23"); } - // ESP32固有の番号を名前に加え、同じ名前の機器と区別します。 - uint64_t mac = ESP.getEfuseMac(); - char name[14]; - snprintf(name, sizeof(name), "%s-%05llX", kBleDeviceNamePrefix, mac & 0xFFFFF); - bluetoothName_ = name; - BLEDevice::init(bluetoothName_.c_str()); - Serial.printf("[INFO] BLE initialized as '%s'\n", bluetoothName_.c_str()); + if (bleDataConnected_) { + // ESP32固有の番号を名前に加え、同じ名前の機器と区別します。 + uint64_t mac = ESP.getEfuseMac(); + char name[14]; + snprintf(name, sizeof(name), "%s-%05llX", kBleDeviceNamePrefix, mac & 0xFFFFF); + bluetoothName_ = name; + BLEDevice::init(bluetoothName_.c_str()); + Serial.printf("[INFO] BLE initialized as '%s'\n", bluetoothName_.c_str()); - // BLEサーバーと、センサー通信用のサービスを作ります。 - server_ = BLEDevice::createServer(); - server_->setCallbacks(new SensorLoggerServerCallbacks(this)); - BLEService* service = server_->createService(kNusServiceUuid); - // RXはアプリからESP32へ命令を送る通路です。 - BLECharacteristic* rxCharacteristic = service->createCharacteristic( - kNusRxUuid, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); - rxCharacteristic->setCallbacks(new SensorLoggerCallbacks(this)); - // TXはESP32からアプリへログを送る通路です。 - txCharacteristic_ = service->createCharacteristic( - kNusTxUuid, BLECharacteristic::PROPERTY_NOTIFY); - txCharacteristic_->addDescriptor(new BLE2902()); - service->start(); + // BLEサーバーと、センサー通信用のサービスを作ります。 + server_ = BLEDevice::createServer(); + server_->setCallbacks(new SensorLoggerServerCallbacks(this)); + BLEService* service = server_->createService(kNusServiceUuid); + // RXはアプリからESP32へ命令を送る通路です。 + BLECharacteristic* rxCharacteristic = service->createCharacteristic( + kNusRxUuid, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + rxCharacteristic->setCallbacks(new SensorLoggerCallbacks(this)); + // TXはESP32からアプリへログを送る通路です。 + txCharacteristic_ = service->createCharacteristic( + kNusTxUuid, BLECharacteristic::PROPERTY_NOTIFY); + txCharacteristic_->addDescriptor(new BLE2902()); + service->start(); - // BLE広告は「この機器が近くにいます」と周囲へ知らせる仕組みです。 - BLEAdvertising* advertising = BLEDevice::getAdvertising(); - advertising->addServiceUUID(kNusServiceUuid); - advertising->setScanResponse(true); - // A2DP開始後に共通のBluetooth名が変わっても、BLE名を返せるようにします。 - BLEAdvertisementData scanResponseData; - scanResponseData.setName(bluetoothName_.c_str()); - advertising->setScanResponseData(scanResponseData); - advertising->setMinPreferred(0x06); - advertising->setMinPreferred(0x12); - BLEDevice::startAdvertising(); - Serial.println("[INFO] BLE advertising started"); + // BLE広告は「この機器が近くにいます」と周囲へ知らせる仕組みです。 + BLEAdvertising* advertising = BLEDevice::getAdvertising(); + advertising->addServiceUUID(kNusServiceUuid); + advertising->setScanResponse(true); + // A2DP開始後に共通のBluetooth名が変わっても、BLE名を返せるようにします。 + BLEAdvertisementData scanResponseData; + scanResponseData.setName(bluetoothName_.c_str()); + advertising->setScanResponseData(scanResponseData); + advertising->setMinPreferred(0x06); + advertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + Serial.println("[INFO] BLE advertising started"); + } // 起動直後にも1回計測して、画面に値を表示できるようにします。 readAndStore(); @@ -142,10 +147,13 @@ void SensorLogger::begin(uint8_t soilPin) { bool SensorLogger::update() { // loopから繰り返し呼ばれ、通信と定期計測を担当します。 - processBluetooth(); + if (bleDataConnected_) { + processBluetooth(); + } unsigned long now = millis(); - if (!bluetoothConnected_ && now - lastAdvertise_ >= kAdvertiseRetryIntervalMs) { + if (bleDataConnected_ && !bluetoothConnected_ + && now - lastAdvertise_ >= kAdvertiseRetryIntervalMs) { // 接続されていなければ、定期的にBLE広告を出し直します。 lastAdvertise_ = now; BLEDevice::startAdvertising(); @@ -288,6 +296,10 @@ void SensorLogger::processBluetooth() { } void SensorLogger::receiveBluetoothData(const String& data) { + if (!bleDataConnected_) { + return; + } + // BLEで届いた文字を命令バッファへ追加します。 for (size_t index = 0; index < data.length(); ++index) { char character = data[index]; @@ -320,6 +332,10 @@ void SensorLogger::applyHistoricalTimeAdjustment(int64_t epoch) { } void SensorLogger::processCommand(const String& command) { + if (!bleDataConnected_ || txCharacteristic_ == nullptr) { + return; + } + // アプリから届いた命令を判定します。 if (command == "GET_LOG") { // 保存済みのログを順番に送ります。 @@ -366,6 +382,10 @@ void SensorLogger::processCommand(const String& command) { } void SensorLogger::sendLog() { + if (!bleDataConnected_ || txCharacteristic_ == nullptr) { + return; + } + // 保存したログを1行ずつ作り、BLE通知で送ります。 for (size_t index = 0; index < logCount_; ++index) { size_t logIndex = (logStart_ + index) % kMaxLogEntries; @@ -390,6 +410,10 @@ void SensorLogger::sendLog() { } void SensorLogger::sendNotification(const char* data) { + if (!bleDataConnected_ || txCharacteristic_ == nullptr) { + return; + } + // BLE通知には一度に送れる長さの制限があるため、短く分割します。 constexpr size_t kNotificationSize = 20; uint8_t notification[kNotificationSize]; diff --git a/firmware/SensorLogger.h b/firmware/SensorLogger.h index b37274b..0166a98 100644 --- a/firmware/SensorLogger.h +++ b/firmware/SensorLogger.h @@ -18,7 +18,7 @@ public: static constexpr size_t kMaxLogEntries = 2000; // センサー、BLE、保存データを使い始める準備をします。 - void begin(uint8_t soilPin); + void begin(uint8_t soilPin, bool i2cConnected, bool bleDataConnected); // BLEの受信と、一定時間ごとの計測を行います。 bool update(); // センサーを読み取り、画面表示用の最新値を更新します。 @@ -69,6 +69,7 @@ private: bool timeSynchronized_ = false; // 時刻合わせが済んだか bool historicalAdjustmentApplied_ = false; // 過去ログの補正が済んだか bool bluetoothConnected_ = false; // BLE接続中か + bool bleDataConnected_ = false; // BLEデータ送受信を使うか String bluetoothName_; // BLEに表示する名前 String commandBuffer_; // BLEから受け取った未処理の文字 diff --git a/firmware/firmware.ino b/firmware/firmware.ino index 5e4dac1..a367690 100644 --- a/firmware/firmware.ino +++ b/firmware/firmware.ino @@ -1,3 +1,10 @@ +// I2C機器(OLED・センサー)を接続していない場合は false にします。 +const bool kI2cConnected = true; // I2C機器が接続されていない場合は false にします。 +// BLEでのセンサーデータ送受信を使わない場合は false にします。 +const bool kBleDataConnected = true; // BLEでのセンサーデータ送受信を使わない場合は false にします。 +// NeoPixelの接続ピンです。未接続の場合は -1 にすると一切使用しません。 +const int kNeoPixelPin = 18; // 使用しない場合は -1 に、通常は 18番ピンに接続します。 + // A2DPは、スマートフォンから音楽を受信するBluetoothの仕組みです。 #include "BluetoothA2DPSink.h" #include @@ -6,7 +13,7 @@ #include "SensorLogger.h" // スマートフォンのBluetooth一覧に表示されるスピーカー名です。 -const char kA2dpDeviceName[] = "BT_Speaker5.6"; +const char kA2dpDeviceName[] = "BT_Speaker5.7"; // I2C接続のSDA/SCLピンです。配線に合わせてここだけ変更します。 // 通常基板:SDA=21, SCL=22 // 開発用基板:SDA=19, SCL=5 @@ -210,7 +217,9 @@ void audio_state_changed_callback(esp_a2d_audio_state_t state, void *) { musicPlaying = state == ESP_A2D_AUDIO_STATE_STARTED; Serial.printf("[INFO] Audio state changed: %d (playing=%s)\n", state, musicPlaying ? "yes" : "no"); - neoPixelController.setPlaying(musicPlaying); + if (kNeoPixelPin >= 0) { + neoPixelController.setPlaying(musicPlaying); + } updateDisplay(); } @@ -230,33 +239,37 @@ void setup() { Serial.begin(115200); Serial.println("[INFO] Firmware starting"); - neoPixelController.begin(18); - - // I2Cの配線を設定します。Wire.begin(SDA, SCL)の順です。 - // OLED未接続時もSDA/SCLが浮かないようにします。 - pinMode(kI2cSdaPin, INPUT_PULLUP); - pinMode(kI2cSclPin, INPUT_PULLUP); - Wire.begin(kI2cSdaPin, kI2cSclPin); - Wire.setTimeOut(50); - - // OLEDが接続されているときだけ初期化と描画を行います。 - uint8_t oledAddress = 0; - if (isI2cDevicePresent(0x3C)) { - oledAddress = 0x3C; - } else if (isI2cDevicePresent(0x3D)) { - oledAddress = 0x3D; - } - if (oledAddress != 0) { - u8g2.setI2CAddress(oledAddress << 1); - u8g2.begin(); - u8g2.enableUTF8Print(); // UTF-8(日本語)描画を有効化 - oledReady = true; - Serial.printf("[INFO] OLED detected at 0x%02X\n", oledAddress); - } else { - Serial.println("[WARNING] OLED not detected at 0x3C or 0x3D"); + if (kNeoPixelPin >= 0) { + neoPixelController.begin(static_cast(kNeoPixelPin)); } - sensorLogger.begin(32); + if (kI2cConnected) { + // I2Cの配線を設定します。Wire.begin(SDA, SCL)の順です。 + // OLED未接続時もSDA/SCLが浮かないようにします。 + pinMode(kI2cSdaPin, INPUT_PULLUP); + pinMode(kI2cSclPin, INPUT_PULLUP); + Wire.begin(kI2cSdaPin, kI2cSclPin); + Wire.setTimeOut(50); + + // OLEDが接続されているときだけ初期化と描画を行います。 + uint8_t oledAddress = 0; + if (isI2cDevicePresent(0x3C)) { + oledAddress = 0x3C; + } else if (isI2cDevicePresent(0x3D)) { + oledAddress = 0x3D; + } + if (oledAddress != 0) { + u8g2.setI2CAddress(oledAddress << 1); + u8g2.begin(); + u8g2.enableUTF8Print(); // UTF-8(日本語)描画を有効化 + oledReady = true; + Serial.printf("[INFO] OLED detected at 0x%02X\n", oledAddress); + } else { + Serial.println("[WARNING] OLED not detected at 0x3C or 0x3D"); + } + } + + sensorLogger.begin(32, kI2cConnected, kBleDataConnected); updateDisplay(); lastDisplayUpdate = millis(); @@ -284,7 +297,9 @@ void setup() { void loop() { // loopは何度も繰り返し実行されます。 - neoPixelController.update(); + if (kNeoPixelPin >= 0) { + neoPixelController.update(); + } bool newReading = sensorLogger.update(); unsigned long now = millis(); if ((!bluetoothConnected || !musicPlaying)