コメントを追加
This commit is contained in:
parent
c6ffbacb03
commit
e7f9fb4c1b
|
|
@ -3,7 +3,9 @@
|
||||||
#include <Adafruit_NeoPixel.h>
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
// 今回は1個のNeoPixelを使います。
|
||||||
constexpr uint16_t kCount = 1;
|
constexpr uint16_t kCount = 1;
|
||||||
|
// 音楽再生中に順番に表示する色です。値はRGBの順です。
|
||||||
constexpr uint32_t kColors[] = {
|
constexpr uint32_t kColors[] = {
|
||||||
0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00,
|
0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00,
|
||||||
0x0000FF, 0x4B0082, 0x8F00FF
|
0x0000FF, 0x4B0082, 0x8F00FF
|
||||||
|
|
@ -14,6 +16,7 @@ Adafruit_NeoPixel pixels(kCount, 0, NEO_GRB + NEO_KHZ800);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoPixelController::begin(uint8_t pin) {
|
void NeoPixelController::begin(uint8_t pin) {
|
||||||
|
// LEDの接続先と明るさを設定し、停止中の色を表示します。
|
||||||
pixels.setPin(pin);
|
pixels.setPin(pin);
|
||||||
pixels.begin();
|
pixels.begin();
|
||||||
pixels.setBrightness(255);
|
pixels.setBrightness(255);
|
||||||
|
|
@ -25,17 +28,20 @@ void NeoPixelController::setPlaying(bool playing) {
|
||||||
isPlaying_ = playing;
|
isPlaying_ = playing;
|
||||||
|
|
||||||
if (isPlaying_) {
|
if (isPlaying_) {
|
||||||
|
// 再生開始時刻を記録します。ここから色の変化を始めます。
|
||||||
transitionStart_ = millis();
|
transitionStart_ = millis();
|
||||||
lastUpdate_ = transitionStart_;
|
lastUpdate_ = transitionStart_;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 停止中は、決めておいた色に戻します。
|
||||||
colorIndex_ = kStoppedColorIndex;
|
colorIndex_ = kStoppedColorIndex;
|
||||||
pixels.setPixelColor(0, kColors[colorIndex_]);
|
pixels.setPixelColor(0, kColors[colorIndex_]);
|
||||||
pixels.show();
|
pixels.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoPixelController::update() {
|
void NeoPixelController::update() {
|
||||||
|
// 再生中でない、または更新間隔が短すぎるときは何もしません。
|
||||||
if (!isPlaying_ || millis() - lastUpdate_ < kUpdateIntervalMs) {
|
if (!isPlaying_ || millis() - lastUpdate_ < kUpdateIntervalMs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +50,7 @@ void NeoPixelController::update() {
|
||||||
lastUpdate_ = now;
|
lastUpdate_ = now;
|
||||||
unsigned long transitionElapsed = now - transitionStart_;
|
unsigned long transitionElapsed = now - transitionStart_;
|
||||||
if (transitionElapsed >= kTransitionMs) {
|
if (transitionElapsed >= kTransitionMs) {
|
||||||
|
// 色の切り替えが終わったら、次の色へ進みます。
|
||||||
colorIndex_ = (colorIndex_ + transitionElapsed / kTransitionMs) % kColorCount;
|
colorIndex_ = (colorIndex_ + transitionElapsed / kTransitionMs) % kColorCount;
|
||||||
transitionStart_ = now;
|
transitionStart_ = now;
|
||||||
transitionElapsed = 0;
|
transitionElapsed = 0;
|
||||||
|
|
@ -52,6 +59,7 @@ void NeoPixelController::update() {
|
||||||
uint8_t nextColorIndex = (colorIndex_ + 1) % kColorCount;
|
uint8_t nextColorIndex = (colorIndex_ + 1) % kColorCount;
|
||||||
uint32_t currentColor = kColors[colorIndex_];
|
uint32_t currentColor = kColors[colorIndex_];
|
||||||
uint32_t nextColor = kColors[nextColorIndex];
|
uint32_t nextColor = kColors[nextColorIndex];
|
||||||
|
// 現在の色と次の色を混ぜることで、急に変わらず滑らかに変化させます。
|
||||||
uint8_t red = ((currentColor >> 16) * (kTransitionMs - transitionElapsed)
|
uint8_t red = ((currentColor >> 16) * (kTransitionMs - transitionElapsed)
|
||||||
+ (nextColor >> 16) * transitionElapsed) / kTransitionMs;
|
+ (nextColor >> 16) * transitionElapsed) / kTransitionMs;
|
||||||
uint8_t green = (((currentColor >> 8) & 0xFF) * (kTransitionMs - transitionElapsed)
|
uint8_t green = (((currentColor >> 8) & 0xFF) * (kTransitionMs - transitionElapsed)
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,22 @@
|
||||||
|
|
||||||
class NeoPixelController {
|
class NeoPixelController {
|
||||||
public:
|
public:
|
||||||
|
// NeoPixelを使い始める準備をします。
|
||||||
void begin(uint8_t pin);
|
void begin(uint8_t pin);
|
||||||
|
// 音楽再生中かどうかを設定します。
|
||||||
void setPlaying(bool playing);
|
void setPlaying(bool playing);
|
||||||
|
// 時間の経過に合わせてLEDを更新します。
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// 音楽停止中に表示する色(配列の4番目、緑)です。
|
||||||
static constexpr uint8_t kStoppedColorIndex = 3;
|
static constexpr uint8_t kStoppedColorIndex = 3;
|
||||||
|
// 色を計算する間隔です。短いほどなめらかになります。
|
||||||
static constexpr unsigned long kUpdateIntervalMs = 30;
|
static constexpr unsigned long kUpdateIntervalMs = 30;
|
||||||
|
// 1色から次の色へ変わる時間です。
|
||||||
static constexpr unsigned long kTransitionMs = 3000;
|
static constexpr unsigned long kTransitionMs = 3000;
|
||||||
|
|
||||||
|
// 現在のLEDの状態を保存します。
|
||||||
bool isPlaying_ = false;
|
bool isPlaying_ = false;
|
||||||
uint8_t colorIndex_ = 0;
|
uint8_t colorIndex_ = 0;
|
||||||
unsigned long lastUpdate_ = 0;
|
unsigned long lastUpdate_ = 0;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
// BLEサービスと、その中の2本の通信路を識別する番号です。
|
||||||
|
// UUIDは、Bluetooth上で機能を区別するための長い名前です。
|
||||||
constexpr char kNusServiceUuid[] = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
|
constexpr char kNusServiceUuid[] = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
|
||||||
constexpr char kNusRxUuid[] = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
|
constexpr char kNusRxUuid[] = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
|
||||||
constexpr char kNusTxUuid[] = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
|
constexpr char kNusTxUuid[] = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
|
||||||
|
|
@ -16,21 +18,25 @@ constexpr char kBleDeviceNamePrefix[] = "EnvLog";
|
||||||
constexpr int64_t kInitialYear = 1700;
|
constexpr int64_t kInitialYear = 1700;
|
||||||
constexpr int64_t kSecondsPerDay = 86400;
|
constexpr int64_t kSecondsPerDay = 86400;
|
||||||
|
|
||||||
|
// センサー本体と、センサーが使える状態かどうかを保存します。
|
||||||
Adafruit_AHTX0 aht20;
|
Adafruit_AHTX0 aht20;
|
||||||
BH1750 bh1750;
|
BH1750 bh1750;
|
||||||
bool aht20Ready = false;
|
bool aht20Ready = false;
|
||||||
bool bh1750Ready = false;
|
bool bh1750Ready = false;
|
||||||
|
|
||||||
bool isLeapYear(int year) {
|
bool isLeapYear(int year) {
|
||||||
|
// 4年ごと。ただし100年ごとは除き、400年ごとはうるう年です。
|
||||||
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BLEの受信側の処理です。アプリがデータを書き込むと呼ばれます。
|
||||||
class SensorLoggerCallbacks : public BLECharacteristicCallbacks {
|
class SensorLoggerCallbacks : public BLECharacteristicCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit SensorLoggerCallbacks(SensorLogger* logger) : logger_(logger) {}
|
explicit SensorLoggerCallbacks(SensorLogger* logger) : logger_(logger) {}
|
||||||
|
|
||||||
void onWrite(BLECharacteristic* characteristic) override {
|
void onWrite(BLECharacteristic* characteristic) override {
|
||||||
|
// 受信した文字をSensorLoggerへ渡します。
|
||||||
logger_->receiveBluetoothData(characteristic->getValue().c_str());
|
logger_->receiveBluetoothData(characteristic->getValue().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,15 +44,18 @@ private:
|
||||||
SensorLogger* logger_;
|
SensorLogger* logger_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// BLEの接続と切断を知らせる処理です。
|
||||||
class SensorLoggerServerCallbacks : public BLEServerCallbacks {
|
class SensorLoggerServerCallbacks : public BLEServerCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit SensorLoggerServerCallbacks(SensorLogger* logger) : logger_(logger) {}
|
explicit SensorLoggerServerCallbacks(SensorLogger* logger) : logger_(logger) {}
|
||||||
|
|
||||||
void onConnect(BLEServer*) override {
|
void onConnect(BLEServer*) override {
|
||||||
|
// 接続中は、画面に接続中であることを表示できます。
|
||||||
logger_->bluetoothConnected_ = true;
|
logger_->bluetoothConnected_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDisconnect(BLEServer* server) override {
|
void onDisconnect(BLEServer* server) override {
|
||||||
|
// 切断されたら、次の接続を待つために広告を再開します。
|
||||||
logger_->bluetoothConnected_ = false;
|
logger_->bluetoothConnected_ = false;
|
||||||
logger_->commandBuffer_ = "";
|
logger_->commandBuffer_ = "";
|
||||||
logger_->lastAdvertise_ = millis();
|
logger_->lastAdvertise_ = millis();
|
||||||
|
|
@ -58,33 +67,41 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
void SensorLogger::begin(uint8_t soilPin) {
|
void SensorLogger::begin(uint8_t soilPin) {
|
||||||
|
// センサーとBLE通信を使い始めるための初期設定です。
|
||||||
soilPin_ = soilPin;
|
soilPin_ = soilPin;
|
||||||
pinMode(soilPin_, INPUT);
|
pinMode(soilPin_, INPUT);
|
||||||
analogReadResolution(12);
|
analogReadResolution(12);
|
||||||
|
|
||||||
|
// センサーの初期化に成功したかを保存します。
|
||||||
aht20Ready = aht20.begin();
|
aht20Ready = aht20.begin();
|
||||||
bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
bh1750Ready = bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
||||||
|
|
||||||
|
// ESP32固有の番号を名前に加え、同じ名前の機器と区別します。
|
||||||
uint64_t mac = ESP.getEfuseMac();
|
uint64_t mac = ESP.getEfuseMac();
|
||||||
char name[14];
|
char name[14];
|
||||||
snprintf(name, sizeof(name), "%s-%05llX", kBleDeviceNamePrefix, mac & 0xFFFFF);
|
snprintf(name, sizeof(name), "%s-%05llX", kBleDeviceNamePrefix, mac & 0xFFFFF);
|
||||||
bluetoothName_ = name;
|
bluetoothName_ = name;
|
||||||
BLEDevice::init(bluetoothName_.c_str());
|
BLEDevice::init(bluetoothName_.c_str());
|
||||||
|
|
||||||
|
// BLEサーバーと、センサー通信用のサービスを作ります。
|
||||||
server_ = BLEDevice::createServer();
|
server_ = BLEDevice::createServer();
|
||||||
server_->setCallbacks(new SensorLoggerServerCallbacks(this));
|
server_->setCallbacks(new SensorLoggerServerCallbacks(this));
|
||||||
BLEService* service = server_->createService(kNusServiceUuid);
|
BLEService* service = server_->createService(kNusServiceUuid);
|
||||||
|
// RXはアプリからESP32へ命令を送る通路です。
|
||||||
BLECharacteristic* rxCharacteristic = service->createCharacteristic(
|
BLECharacteristic* rxCharacteristic = service->createCharacteristic(
|
||||||
kNusRxUuid, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
|
kNusRxUuid, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
|
||||||
rxCharacteristic->setCallbacks(new SensorLoggerCallbacks(this));
|
rxCharacteristic->setCallbacks(new SensorLoggerCallbacks(this));
|
||||||
|
// TXはESP32からアプリへログを送る通路です。
|
||||||
txCharacteristic_ = service->createCharacteristic(
|
txCharacteristic_ = service->createCharacteristic(
|
||||||
kNusTxUuid, BLECharacteristic::PROPERTY_NOTIFY);
|
kNusTxUuid, BLECharacteristic::PROPERTY_NOTIFY);
|
||||||
txCharacteristic_->addDescriptor(new BLE2902());
|
txCharacteristic_->addDescriptor(new BLE2902());
|
||||||
service->start();
|
service->start();
|
||||||
|
|
||||||
|
// BLE広告は「この機器が近くにいます」と周囲へ知らせる仕組みです。
|
||||||
BLEAdvertising* advertising = BLEDevice::getAdvertising();
|
BLEAdvertising* advertising = BLEDevice::getAdvertising();
|
||||||
advertising->addServiceUUID(kNusServiceUuid);
|
advertising->addServiceUUID(kNusServiceUuid);
|
||||||
advertising->setScanResponse(true);
|
advertising->setScanResponse(true);
|
||||||
|
// A2DP開始後に共通のBluetooth名が変わっても、BLE名を返せるようにします。
|
||||||
BLEAdvertisementData scanResponseData;
|
BLEAdvertisementData scanResponseData;
|
||||||
scanResponseData.setName(bluetoothName_.c_str());
|
scanResponseData.setName(bluetoothName_.c_str());
|
||||||
advertising->setScanResponseData(scanResponseData);
|
advertising->setScanResponseData(scanResponseData);
|
||||||
|
|
@ -92,21 +109,25 @@ void SensorLogger::begin(uint8_t soilPin) {
|
||||||
advertising->setMinPreferred(0x12);
|
advertising->setMinPreferred(0x12);
|
||||||
BLEDevice::startAdvertising();
|
BLEDevice::startAdvertising();
|
||||||
|
|
||||||
|
// 起動直後にも1回計測して、画面に値を表示できるようにします。
|
||||||
readAndStore();
|
readAndStore();
|
||||||
lastMeasure_ = millis();
|
lastMeasure_ = millis();
|
||||||
lastAdvertise_ = millis();
|
lastAdvertise_ = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SensorLogger::update() {
|
bool SensorLogger::update() {
|
||||||
|
// loopから繰り返し呼ばれ、通信と定期計測を担当します。
|
||||||
processBluetooth();
|
processBluetooth();
|
||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
if (!bluetoothConnected_ && now - lastAdvertise_ >= kAdvertiseRetryIntervalMs) {
|
if (!bluetoothConnected_ && now - lastAdvertise_ >= kAdvertiseRetryIntervalMs) {
|
||||||
|
// 接続されていなければ、定期的にBLE広告を出し直します。
|
||||||
lastAdvertise_ = now;
|
lastAdvertise_ = now;
|
||||||
BLEDevice::startAdvertising();
|
BLEDevice::startAdvertising();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - lastMeasure_ >= kMeasureIntervalMs) {
|
if (now - lastMeasure_ >= kMeasureIntervalMs) {
|
||||||
|
// 計測間隔を過ぎたら、新しいログを保存します。
|
||||||
lastMeasure_ = now;
|
lastMeasure_ = now;
|
||||||
readAndStore();
|
readAndStore();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -115,6 +136,7 @@ bool SensorLogger::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::refreshSensors() {
|
void SensorLogger::refreshSensors() {
|
||||||
|
// センサーを読み直します。ログには保存せず、画面表示だけを更新します。
|
||||||
SensorReading reading = {};
|
SensorReading reading = {};
|
||||||
sensors_event_t humidityEvent;
|
sensors_event_t humidityEvent;
|
||||||
sensors_event_t temperatureEvent;
|
sensors_event_t temperatureEvent;
|
||||||
|
|
@ -153,11 +175,13 @@ size_t SensorLogger::logCount() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SensorLogger::formatTimestamp(int64_t timestamp, char* buffer, size_t bufferSize) const {
|
bool SensorLogger::formatTimestamp(int64_t timestamp, char* buffer, size_t bufferSize) const {
|
||||||
|
// 秒数を、人が読みやすい「年/月/日 時:分:秒」に変換します。
|
||||||
if (bufferSize == 0) {
|
if (bufferSize == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeSynchronized_) {
|
if (timeSynchronized_) {
|
||||||
|
// 時刻合わせ済みなら、ESP32の標準時刻変換を使います。
|
||||||
time_t unixTimestamp = static_cast<time_t>(timestamp);
|
time_t unixTimestamp = static_cast<time_t>(timestamp);
|
||||||
struct tm date;
|
struct tm date;
|
||||||
localtime_r(&unixTimestamp, &date);
|
localtime_r(&unixTimestamp, &date);
|
||||||
|
|
@ -165,6 +189,7 @@ bool SensorLogger::formatTimestamp(int64_t timestamp, char* buffer, size_t buffe
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 時刻合わせ前は、起動してからの経過秒数を仮の日付として表示します。
|
||||||
int year = static_cast<int>(kInitialYear);
|
int year = static_cast<int>(kInitialYear);
|
||||||
int month = 1;
|
int month = 1;
|
||||||
int day = 1;
|
int day = 1;
|
||||||
|
|
@ -193,6 +218,7 @@ bool SensorLogger::getCurrentTime(char* buffer, size_t bufferSize) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::readAndStore() {
|
void SensorLogger::readAndStore() {
|
||||||
|
// センサーを読み取り、最新値とログの両方へ保存します。
|
||||||
sensors_event_t humidityEvent;
|
sensors_event_t humidityEvent;
|
||||||
sensors_event_t temperatureEvent;
|
sensors_event_t temperatureEvent;
|
||||||
SensorReading reading = {};
|
SensorReading reading = {};
|
||||||
|
|
@ -209,6 +235,7 @@ void SensorLogger::readAndStore() {
|
||||||
reading.timestamp = currentTimestamp();
|
reading.timestamp = currentTimestamp();
|
||||||
latest_ = reading;
|
latest_ = reading;
|
||||||
|
|
||||||
|
// 配列が満杯になったら、一番古いデータから上書きします。
|
||||||
size_t writeIndex = (logStart_ + logCount_) % kMaxLogEntries;
|
size_t writeIndex = (logStart_ + logCount_) % kMaxLogEntries;
|
||||||
if (logCount_ == kMaxLogEntries) {
|
if (logCount_ == kMaxLogEntries) {
|
||||||
log_[logStart_] = reading;
|
log_[logStart_] = reading;
|
||||||
|
|
@ -220,6 +247,7 @@ void SensorLogger::readAndStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::processBluetooth() {
|
void SensorLogger::processBluetooth() {
|
||||||
|
// 受信した文字を改行ごとの命令に分けて実行します。
|
||||||
while (commandBuffer_.indexOf('\n') >= 0 || commandBuffer_.indexOf('\r') >= 0) {
|
while (commandBuffer_.indexOf('\n') >= 0 || commandBuffer_.indexOf('\r') >= 0) {
|
||||||
int lineEnd = commandBuffer_.indexOf('\n');
|
int lineEnd = commandBuffer_.indexOf('\n');
|
||||||
int carriageReturn = commandBuffer_.indexOf('\r');
|
int carriageReturn = commandBuffer_.indexOf('\r');
|
||||||
|
|
@ -235,6 +263,7 @@ void SensorLogger::processBluetooth() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::receiveBluetoothData(const String& data) {
|
void SensorLogger::receiveBluetoothData(const String& data) {
|
||||||
|
// BLEで届いた文字を命令バッファへ追加します。
|
||||||
for (size_t index = 0; index < data.length(); ++index) {
|
for (size_t index = 0; index < data.length(); ++index) {
|
||||||
char character = data[index];
|
char character = data[index];
|
||||||
if (character == '\n' || character == '\r') {
|
if (character == '\n' || character == '\r') {
|
||||||
|
|
@ -246,6 +275,7 @@ void SensorLogger::receiveBluetoothData(const String& data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::applyHistoricalTimeAdjustment(int64_t epoch) {
|
void SensorLogger::applyHistoricalTimeAdjustment(int64_t epoch) {
|
||||||
|
// 時刻合わせ前に保存したログへ、正しい時刻との差を加えます。
|
||||||
if (historicalAdjustmentApplied_ || timeSynchronized_) {
|
if (historicalAdjustmentApplied_ || timeSynchronized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -263,13 +293,17 @@ void SensorLogger::applyHistoricalTimeAdjustment(int64_t epoch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::processCommand(const String& command) {
|
void SensorLogger::processCommand(const String& command) {
|
||||||
|
// アプリから届いた命令を判定します。
|
||||||
if (command == "GET_LOG") {
|
if (command == "GET_LOG") {
|
||||||
|
// 保存済みのログを順番に送ります。
|
||||||
sendLog();
|
sendLog();
|
||||||
} else if (command == "CLEAR_LOG") {
|
} else if (command == "CLEAR_LOG") {
|
||||||
|
// ログを空にして、完了通知を送ります。
|
||||||
clearLog();
|
clearLog();
|
||||||
txCharacteristic_->setValue("OK_CLEARED\n");
|
txCharacteristic_->setValue("OK_CLEARED\n");
|
||||||
txCharacteristic_->notify();
|
txCharacteristic_->notify();
|
||||||
} else if (command.startsWith("TIME:")) {
|
} else if (command.startsWith("TIME:")) {
|
||||||
|
// アプリから受け取った時刻で、時計を合わせます。
|
||||||
unsigned long epoch = command.substring(5).toInt();
|
unsigned long epoch = command.substring(5).toInt();
|
||||||
unsigned int year;
|
unsigned int year;
|
||||||
unsigned int month;
|
unsigned int month;
|
||||||
|
|
@ -302,6 +336,7 @@ void SensorLogger::processCommand(const String& command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::sendLog() {
|
void SensorLogger::sendLog() {
|
||||||
|
// 保存したログを1行ずつ作り、BLE通知で送ります。
|
||||||
for (size_t index = 0; index < logCount_; ++index) {
|
for (size_t index = 0; index < logCount_; ++index) {
|
||||||
size_t logIndex = (logStart_ + index) % kMaxLogEntries;
|
size_t logIndex = (logStart_ + index) % kMaxLogEntries;
|
||||||
const SensorReading& reading = log_[logIndex];
|
const SensorReading& reading = log_[logIndex];
|
||||||
|
|
@ -325,6 +360,7 @@ void SensorLogger::sendLog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::sendNotification(const char* data) {
|
void SensorLogger::sendNotification(const char* data) {
|
||||||
|
// BLE通知には一度に送れる長さの制限があるため、短く分割します。
|
||||||
constexpr size_t kNotificationSize = 20;
|
constexpr size_t kNotificationSize = 20;
|
||||||
uint8_t notification[kNotificationSize];
|
uint8_t notification[kNotificationSize];
|
||||||
size_t length = strlen(data);
|
size_t length = strlen(data);
|
||||||
|
|
@ -338,11 +374,13 @@ void SensorLogger::sendNotification(const char* data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorLogger::clearLog() {
|
void SensorLogger::clearLog() {
|
||||||
|
// 配列の中身を消す代わりに、件数を0にして空として扱います。
|
||||||
logStart_ = 0;
|
logStart_ = 0;
|
||||||
logCount_ = 0;
|
logCount_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t SensorLogger::currentTimestamp() const {
|
int64_t SensorLogger::currentTimestamp() const {
|
||||||
|
// 現在の経過秒数に時刻の差を足して、現在時刻を返します。
|
||||||
int64_t elapsedSeconds = millis() / 1000UL;
|
int64_t elapsedSeconds = millis() / 1000UL;
|
||||||
return timeSynchronized_ ? epochOffset_ + elapsedSeconds : elapsedSeconds;
|
return timeSynchronized_ ? epochOffset_ + elapsedSeconds : elapsedSeconds;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,32 +6,45 @@ class BLECharacteristic;
|
||||||
class BLEServer;
|
class BLEServer;
|
||||||
|
|
||||||
struct SensorReading {
|
struct SensorReading {
|
||||||
int64_t timestamp;
|
int64_t timestamp; // 計測した時刻(秒)
|
||||||
float temperature;
|
float temperature; // 温度(℃)
|
||||||
float humidity;
|
float humidity; // 湿度(%)
|
||||||
float illuminance;
|
float illuminance; // 明るさ(lux)
|
||||||
uint16_t soilMoisture;
|
uint16_t soilMoisture; // 土壌センサーの値
|
||||||
};
|
};
|
||||||
|
|
||||||
class SensorLogger {
|
class SensorLogger {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t kMaxLogEntries = 2000;
|
static constexpr size_t kMaxLogEntries = 2000;
|
||||||
|
|
||||||
|
// センサー、BLE、保存データを使い始める準備をします。
|
||||||
void begin(uint8_t soilPin);
|
void begin(uint8_t soilPin);
|
||||||
|
// BLEの受信と、一定時間ごとの計測を行います。
|
||||||
bool update();
|
bool update();
|
||||||
|
// センサーを読み取り、画面表示用の最新値を更新します。
|
||||||
void refreshSensors();
|
void refreshSensors();
|
||||||
|
// 最後に読み取ったセンサー値を返します。
|
||||||
const SensorReading& latest() const;
|
const SensorReading& latest() const;
|
||||||
|
// 1件以上のログが保存されているかを返します。
|
||||||
bool hasReading() const;
|
bool hasReading() const;
|
||||||
|
// 保存されているログの件数を返します。
|
||||||
size_t logCount() const;
|
size_t logCount() const;
|
||||||
|
// 現在時刻を文字列にしてbufferへ書き込みます。
|
||||||
bool getCurrentTime(char* buffer, size_t bufferSize) const;
|
bool getCurrentTime(char* buffer, size_t bufferSize) const;
|
||||||
|
// 秒数を年月日と時刻の文字列に変換します。
|
||||||
bool formatTimestamp(int64_t timestamp, char* buffer, size_t bufferSize) const;
|
bool formatTimestamp(int64_t timestamp, char* buffer, size_t bufferSize) const;
|
||||||
|
// BLEで表示する名前を返します。
|
||||||
const char* bluetoothName() const;
|
const char* bluetoothName() const;
|
||||||
|
// BLE機器が接続中かを返します。
|
||||||
bool isBluetoothConnected() const;
|
bool isBluetoothConnected() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// センサーを読み取る間隔(現在は1時間)です。
|
||||||
static constexpr unsigned long kMeasureIntervalMs = 3600000UL;
|
static constexpr unsigned long kMeasureIntervalMs = 3600000UL;
|
||||||
|
// BLE接続がないとき、広告を再開する間隔です。
|
||||||
static constexpr unsigned long kAdvertiseRetryIntervalMs = 500UL;
|
static constexpr unsigned long kAdvertiseRetryIntervalMs = 500UL;
|
||||||
|
|
||||||
|
// 内部処理用の関数です。
|
||||||
void readAndStore();
|
void readAndStore();
|
||||||
void processBluetooth();
|
void processBluetooth();
|
||||||
void processCommand(const String& command);
|
void processCommand(const String& command);
|
||||||
|
|
@ -42,22 +55,22 @@ private:
|
||||||
void applyHistoricalTimeAdjustment(int64_t epoch);
|
void applyHistoricalTimeAdjustment(int64_t epoch);
|
||||||
int64_t currentTimestamp() const;
|
int64_t currentTimestamp() const;
|
||||||
|
|
||||||
BLEServer* server_ = nullptr;
|
BLEServer* server_ = nullptr; // BLEサーバー本体
|
||||||
BLECharacteristic* txCharacteristic_ = nullptr;
|
BLECharacteristic* txCharacteristic_ = nullptr; // アプリへ通知する通路
|
||||||
SensorReading log_[kMaxLogEntries] = {};
|
SensorReading log_[kMaxLogEntries] = {}; // 最大2000件の計測ログ
|
||||||
SensorReading latest_ = {};
|
SensorReading latest_ = {}; // 最新の計測値
|
||||||
size_t logStart_ = 0;
|
size_t logStart_ = 0; // 一番古いログの位置
|
||||||
size_t logCount_ = 0;
|
size_t logCount_ = 0; // 現在のログ件数
|
||||||
uint8_t soilPin_ = 0;
|
uint8_t soilPin_ = 0; // 土壌センサーを接続したGPIO番号
|
||||||
unsigned long lastMeasure_ = 0;
|
unsigned long lastMeasure_ = 0; // 最後に計測した時刻
|
||||||
unsigned long lastAdvertise_ = 0;
|
unsigned long lastAdvertise_ = 0; // 最後にBLE広告を開始した時刻
|
||||||
int64_t epochOffset_ = 0;
|
int64_t epochOffset_ = 0; // 現在時刻を合わせるための差
|
||||||
int64_t historicalEpochOffset_ = 0;
|
int64_t historicalEpochOffset_ = 0; // 過去ログに加える時刻の差
|
||||||
bool timeSynchronized_ = false;
|
bool timeSynchronized_ = false; // 時刻合わせが済んだか
|
||||||
bool historicalAdjustmentApplied_ = false;
|
bool historicalAdjustmentApplied_ = false; // 過去ログの補正が済んだか
|
||||||
bool bluetoothConnected_ = false;
|
bool bluetoothConnected_ = false; // BLE接続中か
|
||||||
String bluetoothName_;
|
String bluetoothName_; // BLEに表示する名前
|
||||||
String commandBuffer_;
|
String commandBuffer_; // BLEから受け取った未処理の文字
|
||||||
|
|
||||||
friend class SensorLoggerCallbacks;
|
friend class SensorLoggerCallbacks;
|
||||||
friend class SensorLoggerServerCallbacks;
|
friend class SensorLoggerServerCallbacks;
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,34 @@
|
||||||
|
// A2DPは、スマートフォンから音楽を受信するBluetoothの仕組みです。
|
||||||
#include "BluetoothA2DPSink.h"
|
#include "BluetoothA2DPSink.h"
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
#include "NeoPixelController.h"
|
#include "NeoPixelController.h"
|
||||||
#include "SensorLogger.h"
|
#include "SensorLogger.h"
|
||||||
|
|
||||||
|
// スマートフォンのBluetooth一覧に表示されるスピーカー名です。
|
||||||
const char kA2dpDeviceName[] = "BT_Speaker5.5";
|
const char kA2dpDeviceName[] = "BT_Speaker5.5";
|
||||||
|
|
||||||
// I2C接続のOLED設定 (SSD1306 128x64)
|
// I2Cで接続した128x64ドットのOLED画面です。
|
||||||
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
|
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
|
||||||
|
|
||||||
|
// このプログラムで使う機能の担当者を作ります。
|
||||||
BluetoothA2DPSink a2dp_sink;
|
BluetoothA2DPSink a2dp_sink;
|
||||||
NeoPixelController neoPixelController;
|
NeoPixelController neoPixelController;
|
||||||
SensorLogger sensorLogger;
|
SensorLogger sensorLogger;
|
||||||
|
|
||||||
// 表示用変数
|
// 音楽情報と画面更新に使う変数です。
|
||||||
String currentTitle = "未接続";
|
String currentTitle = "未接続";
|
||||||
String currentArtist = "";
|
String currentArtist = "";
|
||||||
int currentVolumePercent = 0; // 音量 (0 ~ 100%)
|
int currentVolumePercent = 0; // 音量(0〜100%)
|
||||||
bool bluetoothConnected = false;
|
bool bluetoothConnected = false;
|
||||||
bool musicPlaying = false;
|
bool musicPlaying = false;
|
||||||
unsigned long lastDisplayUpdate = 0;
|
unsigned long lastDisplayUpdate = 0;
|
||||||
|
|
||||||
// 画面全体を再描画する関数
|
// OLEDの内容を現在の状態に合わせて描き直します。
|
||||||
void updateDisplay() {
|
void updateDisplay() {
|
||||||
u8g2.clearBuffer();
|
u8g2.clearBuffer();
|
||||||
|
|
||||||
|
// 音楽を再生していないときは、センサーの情報を表示します。
|
||||||
if (!bluetoothConnected || !musicPlaying) {
|
if (!bluetoothConnected || !musicPlaying) {
|
||||||
u8g2.setFont(u8g2_font_6x10_tf);
|
u8g2.setFont(u8g2_font_6x10_tf);
|
||||||
const SensorReading& reading = sensorLogger.latest();
|
const SensorReading& reading = sensorLogger.latest();
|
||||||
|
|
@ -61,42 +65,38 @@ void updateDisplay() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// 音楽を再生中は、音量と曲名を表示します。
|
||||||
// 1. 音量表示エリア(最上部:英数フォント)
|
// 上の部分は小さい英数字フォントで音量を表示します。
|
||||||
// --------------------------------------------------
|
|
||||||
u8g2.setFont(u8g2_font_6x10_tf);
|
u8g2.setFont(u8g2_font_6x10_tf);
|
||||||
|
|
||||||
// "Vol: 80%" のテキスト描画
|
// 例: 「Vol: 80%」という文字を作って表示します。
|
||||||
char volStr[16];
|
char volStr[16];
|
||||||
snprintf(volStr, sizeof(volStr), "Vol: %3d%%", currentVolumePercent);
|
snprintf(volStr, sizeof(volStr), "Vol: %3d%%", currentVolumePercent);
|
||||||
u8g2.drawStr(0, 10, volStr);
|
u8g2.drawStr(0, 10, volStr);
|
||||||
|
|
||||||
// 音量バーの描画
|
// 音量の割合を、0〜50ドットの長さに変換して棒グラフにします。
|
||||||
int barWidth = map(currentVolumePercent, 0, 100, 0, 50); // 50ピクセル幅に変換
|
int barWidth = map(currentVolumePercent, 0, 100, 0, 50);
|
||||||
u8g2.drawFrame(75, 2, 52, 9); // 外枠
|
u8g2.drawFrame(75, 2, 52, 9); // 棒グラフの外枠
|
||||||
u8g2.drawBox(76, 3, barWidth, 7); // バーの中身
|
u8g2.drawBox(76, 3, barWidth, 7); // 音量に応じた中身
|
||||||
|
|
||||||
u8g2.drawLine(0, 13, 128, 13); // 区切り線
|
u8g2.drawLine(0, 13, 128, 13); // 区切り線
|
||||||
|
|
||||||
// --------------------------------------------------
|
// 下の部分は、日本語を含む曲名とアーティスト名を表示します。
|
||||||
// 2. 曲名・アーティスト表示エリア(日本語フォント)
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 日本語フォントに切り替え (美咲フォント UTF-8)
|
|
||||||
u8g2.setFont(u8g2_font_unifont_t_japanese1);
|
u8g2.setFont(u8g2_font_unifont_t_japanese1);
|
||||||
|
|
||||||
// 曲名の表示 (y=32)
|
// 曲名を表示します。drawUTF8は日本語も扱える関数です。
|
||||||
u8g2.drawUTF8(0, 32, currentTitle.c_str());
|
u8g2.drawUTF8(0, 32, currentTitle.c_str());
|
||||||
|
|
||||||
// アーティスト名の表示 (y=54)
|
// アーティスト名が空でないときだけ表示します。
|
||||||
if (currentArtist.length() > 0) {
|
if (currentArtist.length() > 0) {
|
||||||
// アーティスト名も日本語フォントのまま描画します
|
|
||||||
u8g2.drawUTF8(0, 54, currentArtist.c_str());
|
u8g2.drawUTF8(0, 54, currentArtist.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
u8g2.sendBuffer(); // 画面転送
|
// ここまでの描画内容を、実際のOLED画面へ送ります。
|
||||||
|
u8g2.sendBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// メタデータ(曲名・アーティスト名)の受信コールバック
|
// スマートフォンから曲名やアーティスト名を受け取ったときに呼ばれます。
|
||||||
void avrc_metadata_callback(uint8_t id, const uint8_t *text) {
|
void avrc_metadata_callback(uint8_t id, const uint8_t *text) {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ void avrc_metadata_callback(uint8_t id, const uint8_t *text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 音量変更時のコールバック関数
|
// スマートフォン側で音量が変わったときに呼ばれます。
|
||||||
void volume_changed_callback(int volume) {
|
void volume_changed_callback(int volume) {
|
||||||
currentVolumePercent = map(volume, 0, 127, 0, 100);
|
currentVolumePercent = map(volume, 0, 127, 0, 100);
|
||||||
if (bluetoothConnected && musicPlaying) {
|
if (bluetoothConnected && musicPlaying) {
|
||||||
|
|
@ -122,12 +122,14 @@ void volume_changed_callback(int volume) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_state_changed_callback(esp_a2d_audio_state_t state, void *) {
|
void audio_state_changed_callback(esp_a2d_audio_state_t state, void *) {
|
||||||
|
// 音声が流れ始めたかどうかを保存し、LEDと画面を更新します。
|
||||||
musicPlaying = state == ESP_A2D_AUDIO_STATE_STARTED;
|
musicPlaying = state == ESP_A2D_AUDIO_STATE_STARTED;
|
||||||
neoPixelController.setPlaying(musicPlaying);
|
neoPixelController.setPlaying(musicPlaying);
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void connection_state_changed_callback(esp_a2d_connection_state_t state, void *) {
|
void connection_state_changed_callback(esp_a2d_connection_state_t state, void *) {
|
||||||
|
// スマートフォンとの接続状態を保存します。
|
||||||
bluetoothConnected = state == ESP_A2D_CONNECTION_STATE_CONNECTED;
|
bluetoothConnected = state == ESP_A2D_CONNECTION_STATE_CONNECTED;
|
||||||
if (!bluetoothConnected) {
|
if (!bluetoothConnected) {
|
||||||
musicPlaying = false;
|
musicPlaying = false;
|
||||||
|
|
@ -136,15 +138,15 @@ void connection_state_changed_callback(esp_a2d_connection_state_t state, void *)
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// setupは、電源を入れた直後に1回だけ実行されます。
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
neoPixelController.begin(18);
|
neoPixelController.begin(18);
|
||||||
|
|
||||||
// SDAをGPIO 19、SCLをGPIO 32 に設定
|
// I2Cの配線を設定します。Wire.begin(SDA, SCL)の順です。
|
||||||
// (配線に合わせて Wire.begin(SDA_PIN, SCL_PIN) の順で指定します)
|
|
||||||
Wire.begin(21, 22);
|
Wire.begin(21, 22);
|
||||||
|
|
||||||
// OLED初期化
|
// OLEDを使える状態にします。
|
||||||
u8g2.begin();
|
u8g2.begin();
|
||||||
u8g2.enableUTF8Print(); // UTF-8(日本語)描画を有効化
|
u8g2.enableUTF8Print(); // UTF-8(日本語)描画を有効化
|
||||||
|
|
||||||
|
|
@ -152,7 +154,8 @@ void setup() {
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
lastDisplayUpdate = millis();
|
lastDisplayUpdate = millis();
|
||||||
|
|
||||||
// I2S ピン設定 (BCK: 27, WS: 25, DOUT: 26)
|
// I2Sは、Bluetooth音声をアンプへ送るための通信方式です。
|
||||||
|
// BCK=27、WS=25、音声データ出力=DOUT=26に配線しています。
|
||||||
i2s_pin_config_t my_pin_config = {
|
i2s_pin_config_t my_pin_config = {
|
||||||
.bck_io_num = 27,
|
.bck_io_num = 27,
|
||||||
.ws_io_num = 25,
|
.ws_io_num = 25,
|
||||||
|
|
@ -162,17 +165,18 @@ void setup() {
|
||||||
|
|
||||||
a2dp_sink.set_pin_config(my_pin_config);
|
a2dp_sink.set_pin_config(my_pin_config);
|
||||||
|
|
||||||
// コールバック登録
|
// Bluetoothでイベントが起きたときの通知先を登録します。
|
||||||
a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);
|
a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);
|
||||||
a2dp_sink.set_on_volumechange(volume_changed_callback);
|
a2dp_sink.set_on_volumechange(volume_changed_callback);
|
||||||
a2dp_sink.set_on_audio_state_changed(audio_state_changed_callback);
|
a2dp_sink.set_on_audio_state_changed(audio_state_changed_callback);
|
||||||
a2dp_sink.set_on_connection_state_changed(connection_state_changed_callback);
|
a2dp_sink.set_on_connection_state_changed(connection_state_changed_callback);
|
||||||
|
|
||||||
// Bluetooth起動
|
// Bluetoothスピーカーとして起動します。
|
||||||
a2dp_sink.start(kA2dpDeviceName);
|
a2dp_sink.start(kA2dpDeviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
// loopは何度も繰り返し実行されます。
|
||||||
neoPixelController.update();
|
neoPixelController.update();
|
||||||
bool newReading = sensorLogger.update();
|
bool newReading = sensorLogger.update();
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue