BTサポート

This commit is contained in:
Koichi Ito 2026-09-20 16:13:03 +09:00
parent 05afe2f439
commit 3279c296a0
4 changed files with 209 additions and 53 deletions

View File

@ -41,6 +41,11 @@ jobs:
- name: Install U8g2 library
run: arduino-cli lib install "U8g2"
- name: Install sensor libraries
run: |
arduino-cli lib install "Adafruit AHTX0"
arduino-cli lib install "BH1750"
# コンパイル実行 (output-dir を指定して構成ファイルを確実に生成)
- name: Compile Sketch
run: |

149
firmware/SensorLogger.cpp Normal file
View File

@ -0,0 +1,149 @@
#include "SensorLogger.h"
#include <Adafruit_AHTX0.h>
#include <BH1750.h>
#include <time.h>
namespace {
Adafruit_AHTX0 aht20;
BH1750 bh1750;
bool aht20Ready = false;
bool bh1750Ready = false;
}
void SensorLogger::begin(uint8_t soilPin, const char* bluetoothName) {
soilPin_ = soilPin;
bluetoothName_ = bluetoothName;
pinMode(soilPin_, INPUT);
analogReadResolution(12);
aht20Ready = aht20.begin();
bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
bluetooth_.begin(bluetoothName);
readAndStore();
lastMeasure_ = millis();
}
bool SensorLogger::update() {
processBluetooth();
unsigned long now = millis();
if (now - lastMeasure_ >= kMeasureIntervalMs) {
lastMeasure_ = now;
readAndStore();
return true;
}
return false;
}
const SensorReading& SensorLogger::latest() const {
return latest_;
}
bool SensorLogger::hasReading() const {
return logCount_ != 0;
}
void SensorLogger::readAndStore() {
sensors_event_t humidityEvent;
sensors_event_t temperatureEvent;
SensorReading reading = {};
if (aht20Ready) {
aht20.getEvent(&humidityEvent, &temperatureEvent);
reading.temperature = temperatureEvent.temperature;
reading.humidity = humidityEvent.relative_humidity;
}
if (bh1750Ready) {
reading.illuminance = bh1750.readLightLevel();
}
reading.soilMoisture = analogRead(soilPin_);
reading.timestamp = currentTimestamp();
latest_ = reading;
size_t writeIndex = (logStart_ + logCount_) % kMaxLogEntries;
if (logCount_ == kMaxLogEntries) {
log_[logStart_] = reading;
logStart_ = (logStart_ + 1) % kMaxLogEntries;
} else {
log_[writeIndex] = reading;
++logCount_;
}
}
void SensorLogger::processBluetooth() {
while (bluetooth_.available()) {
char character = static_cast<char>(bluetooth_.read());
if (character == '\n' || character == '\r') {
if (commandBuffer_.length() != 0) {
processCommand(commandBuffer_);
commandBuffer_ = "";
}
} else if (commandBuffer_.length() < 64) {
commandBuffer_ += character;
}
}
}
void SensorLogger::processCommand(const String& command) {
if (command == "GET_LOG") {
sendLog();
} else if (command == "CLEAR_LOG") {
clearLog();
bluetooth_.println("OK_CLEARED");
} else if (command.startsWith("TIME:")) {
unsigned long epoch = command.substring(5).toInt();
unsigned int year;
unsigned int month;
unsigned int day;
unsigned int hour;
unsigned int minute;
unsigned int second;
if (sscanf(command.c_str(), "TIME:[%u,%u,%u,%u,%u,%u]",
&year, &month, &day, &hour, &minute, &second) == 6) {
struct tm date = {};
date.tm_year = year - 1900;
date.tm_mon = month - 1;
date.tm_mday = day;
date.tm_hour = hour;
date.tm_min = minute;
date.tm_sec = second;
epoch = static_cast<unsigned long>(mktime(&date));
}
if (epoch != 0) {
epochOffset_ = epoch - millis() / 1000UL;
timeSynchronized_ = true;
bluetooth_.println("OK_TIME");
} else {
bluetooth_.println("ERROR_TIME");
}
}
}
void SensorLogger::sendLog() {
bluetooth_.println("timestamp,temp,humid,light,soil,device");
for (size_t index = 0; index < logCount_; ++index) {
size_t logIndex = (logStart_ + index) % kMaxLogEntries;
const SensorReading& reading = log_[logIndex];
bluetooth_.printf("%lu,%.2f,%.2f,%.2f,%u,%s\n",
reading.timestamp,
reading.temperature,
reading.humidity,
reading.illuminance,
reading.soilMoisture,
bluetoothName_);
delay(20);
}
bluetooth_.println("END_LOG");
}
void SensorLogger::clearLog() {
logStart_ = 0;
logCount_ = 0;
}
unsigned long SensorLogger::currentTimestamp() const {
unsigned long elapsedSeconds = millis() / 1000UL;
return timeSynchronized_ ? epochOffset_ + elapsedSeconds : elapsedSeconds;
}

44
firmware/SensorLogger.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <Arduino.h>
#include <BluetoothSerial.h>
struct SensorReading {
unsigned long timestamp;
float temperature;
float humidity;
float illuminance;
uint16_t soilMoisture;
};
class SensorLogger {
public:
static constexpr size_t kMaxLogEntries = 2000;
void begin(uint8_t soilPin, const char* bluetoothName);
bool update();
const SensorReading& latest() const;
bool hasReading() const;
private:
static constexpr unsigned long kMeasureIntervalMs = 3600000UL;
void readAndStore();
void processBluetooth();
void processCommand(const String& command);
void sendLog();
void clearLog();
unsigned long currentTimestamp() const;
BluetoothSerial bluetooth_;
SensorReading log_[kMaxLogEntries] = {};
SensorReading latest_ = {};
size_t logStart_ = 0;
size_t logCount_ = 0;
uint8_t soilPin_ = 0;
unsigned long lastMeasure_ = 0;
unsigned long epochOffset_ = 0;
bool timeSynchronized_ = false;
const char* bluetoothName_ = "";
String commandBuffer_;
};

View File

@ -1,34 +1,23 @@
#include "BluetoothA2DPSink.h"
#include <Wire.h>
#include <U8g2lib.h>
#include <Adafruit_AHTX0.h>
#include <BH1750.h>
#include "NeoPixelController.h"
#include "SensorLogger.h"
const char kBluetoothName[] = "BT_Speaker5.32";
constexpr uint8_t kSoilMoisturePin = 32;
constexpr unsigned long kSensorUpdateIntervalMs = 1000;
// I2C接続のOLED設定 (SSD1306 128x64)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
BluetoothA2DPSink a2dp_sink;
NeoPixelController neoPixelController;
Adafruit_AHTX0 aht20;
BH1750 bh1750;
SensorLogger sensorLogger;
// 表示用変数
String currentTitle = "未接続";
String currentArtist = "";
int currentVolumePercent = 0; // 音量 (0 ~ 100%)
bool bluetoothConnected = false;
bool aht20Ready = false;
bool bh1750Ready = false;
unsigned long lastSensorUpdate = 0;
float temperature = 0.0f;
float humidity = 0.0f;
float illuminance = 0.0f;
int soilMoisture = 0;
// 画面全体を再描画する関数
void updateDisplay() {
@ -36,23 +25,17 @@ void updateDisplay() {
if (!bluetoothConnected) {
u8g2.setFont(u8g2_font_6x10_tf);
const SensorReading& reading = sensorLogger.latest();
char sensorLine[32];
if (aht20Ready) {
snprintf(sensorLine, sizeof(sensorLine), "T:%5.1fC H:%5.1f%%", temperature, humidity);
} else {
snprintf(sensorLine, sizeof(sensorLine), "AHT20: error");
}
snprintf(sensorLine, sizeof(sensorLine), "T:%5.1fC H:%5.1f%%",
reading.temperature, reading.humidity);
u8g2.drawStr(0, 12, sensorLine);
if (bh1750Ready) {
snprintf(sensorLine, sizeof(sensorLine), "Light: %6.1f lx", illuminance);
} else {
snprintf(sensorLine, sizeof(sensorLine), "BH1750: error");
}
snprintf(sensorLine, sizeof(sensorLine), "Light: %6.1f lx", reading.illuminance);
u8g2.drawStr(0, 30, sensorLine);
snprintf(sensorLine, sizeof(sensorLine), "Soil: %4d", soilMoisture);
snprintf(sensorLine, sizeof(sensorLine), "Soil: %4u", reading.soilMoisture);
u8g2.drawStr(0, 48, sensorLine);
u8g2.sendBuffer();
return;
@ -127,24 +110,6 @@ void connection_state_changed_callback(esp_a2d_connection_state_t state, void *)
updateDisplay();
}
void updateSensorDisplay() {
sensors_event_t humidityEvent;
sensors_event_t temperatureEvent;
if (aht20Ready) {
aht20.getEvent(&humidityEvent, &temperatureEvent);
temperature = temperatureEvent.temperature;
humidity = humidityEvent.relative_humidity;
}
if (bh1750Ready) {
illuminance = bh1750.readLightLevel();
}
soilMoisture = analogRead(kSoilMoisturePin);
updateDisplay();
}
void setup() {
Serial.begin(115200);
@ -158,12 +123,8 @@ void setup() {
u8g2.begin();
u8g2.enableUTF8Print(); // UTF-8(日本語)描画を有効化
aht20Ready = aht20.begin();
bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
analogReadResolution(12);
updateSensorDisplay();
lastSensorUpdate = millis();
sensorLogger.begin(32, kBluetoothName);
updateDisplay();
// I2S ピン設定 (BCK: 27, WS: 25, DOUT: 26)
i2s_pin_config_t my_pin_config = {
@ -187,10 +148,7 @@ void setup() {
void loop() {
neoPixelController.update();
unsigned long now = millis();
if (!bluetoothConnected && now - lastSensorUpdate >= kSensorUpdateIntervalMs) {
lastSensorUpdate = now;
updateSensorDisplay();
if (sensorLogger.update() && !bluetoothConnected) {
updateDisplay();
}
}