まずはコード全体
// --- 必要なライブラリ ---
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <ArduinoJson.h>
#include <SPIFFS.h>
#include <vector>
// --- Wi-Fi設定 ---
const char* ssidList[] = {"abc-g-defghi"}; //IDの真ん中に「-g]が入った方を入力
const char* passwordList[] = {"abcdefghijkl"};
//const char* ssidList[] = {"abc-g-defghi", "abc-g-123456"}; //←Wi-Fiが複数ある時に使用
//const char* passwordList[] = {"abcdefghijkl", "123456789012"}; // ←Wi-Fiが複数ある時に使用
const int ssidCount = sizeof(ssidList) / sizeof(ssidList[0]);
// --- Firestore情報 ---
#define USER_EMAIL "sample@lunchtown.jp" //アプリに登録時のアドレス
#define USER_PASSWORD "abc123" //アプリ登録時のパスワード
#define DEVICE_ID "XYZ123" // 6ケタのデバイスID
#define API_KEY "AIzaSyDZbbWJ44XJRtqOJjWo7lCfNUs2I_thcys"
#define DATABASE_ID "(default)"
#define PROJECT_ID "controlingwatersupply"
#define LED_PIN 2
// --- グローバル変数群 ---
std::vector<String> valveIdList;
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
String uid, apiKey, deviceId;
unsigned long lastCheck = 0;
unsigned long lastValveCheck = 0;
unsigned long lastUpdate = 0;
unsigned long lastRebootCheck = 0;
const unsigned long interval = 10000;
const unsigned long valveCheckInterval = 10000;
const unsigned long updateInterval = 20000;
const unsigned long rebootCheckInterval = 30000;
int pinNum = 14; // デフォルト値
// --- 関数プロトタイプ宣言 ---
void connectToWiFi();
void getDeviceConfigFromFirestore();
void saveConfigToSPIFFS(const String& uid, const String& apiKey, const String& deviceId);
bool loadConfigFromSPIFFS();
void readValvesFromFirestore();
void loadPinNumFromFirestore(const String& vid);
void updateValveFromFirestore();
void checkValveState();
void checkShouldReboot();
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("🚀 Firestore最小構成テスト");
initializePins();
if (!SPIFFS.begin(true)) {
Serial.println("❌ SPIFFSマウントに失敗");
return;
}
Serial.println("✅ SPIFFS マウント成功");
if (deviceId != DEVICE_ID) {
Serial.println("⚡️ デバイスIDが異なるためSPIFFSを更新します");
deviceId = DEVICE_ID;
saveConfigToSPIFFS(uid, apiKey, deviceId);
}
connectToWiFi();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("❌ Wi-Fi 接続失敗");
return;
}
Serial.println("✅ Wi-Fi接続完了");
// ピンのモードを設定
pinMode(LED_PIN, OUTPUT);
if (!loadConfigFromSPIFFS()) {
Serial.println("⚠️ /config.json なし または JSONエラー → 手動でセット");
apiKey = API_KEY;
deviceId = DEVICE_ID;
}
config.api_key = API_KEY;
config.database_url = "https://controlingwatersupply.firebaseio.com";
auth.user.email = USER_EMAIL;
auth.user.password = USER_PASSWORD;
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
Serial.println("✅ Firebase初期化完了");
// 4. UIDが取得できるまで待つ
while (auth.token.uid.c_str() == "") {
delay(100);
}
uid = String(auth.token.uid.c_str());
Serial.println("✅ 現在のUID: " + uid);
// ✅ 空チェックを明示(安全ガード)
if (deviceId == "") {
Serial.println("❌ deviceIdが未設定 → 処理停止");
while (true); // フリーズ
}
// Firestoreからデータ取得
getDeviceConfigFromFirestore();
readValvesFromFirestore();
for (String vid : valveIdList) {
//loadPinNumFromFirestore(vid);
if (vid.length() > 0) { // 🔵 vidが空でないかを確認
loadPinNumFromFirestore(vid);
}
}
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, LOW);
Serial.println("✅ 使用ピン:" + String(pinNum));
}
// --- loop() ---
void loop() {
if (WiFi.status() != WL_CONNECTED) {
connectToWiFi();
digitalWrite(LED_PIN, LOW); // Wi-Fiが切断された場合、LEDを消灯
} else {
digitalWrite(LED_PIN, HIGH); // Wi-Fiが接続された場合、LEDを点灯
}
if (millis() - lastCheck > interval) {
lastCheck = millis();
checkShouldReboot();
}
// バルブ状態チェック
if (millis() - lastValveCheck > valveCheckInterval) {
lastValveCheck = millis();
checkValveState();
}
if (millis() - lastUpdate > updateInterval) {
lastUpdate = millis();
updateValveFromFirestore(); // ← この呼び出しに10秒制限を追加
}
}
void connectToWiFi() {
WiFi.mode(WIFI_STA);
WiFi.disconnect(true); // WiFiリセット
delay(1000);
for (int i = 0; i < ssidCount; i++) {
Serial.print("Connecting to ");
Serial.println(ssidList[i]);
WiFi.begin(ssidList[i], passwordList[i]);
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
delay(500);
Serial.print(".");
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // LEDを点滅
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected to WiFi!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
digitalWrite(LED_PIN, HIGH); // 接続が確立したらLEDを点灯
return;
} else {
Serial.println("\nFailed to connect to WiFi.");
WiFi.disconnect();
delay(1000);
}
}
Serial.println("Could not connect to any Wi-Fi network.");
digitalWrite(LED_PIN, LOW); // 接続に失敗したらLEDを消灯
}
void getDeviceConfigFromFirestore() {
String path = "users/" + uid + "/devices/" + String(DEVICE_ID);
bool success = Firebase.Firestore.getDocument(&fbdo, PROJECT_ID, "", path.c_str());
if (success) {
Serial.println("✅ Firestore ドキュメント取得成功");
Serial.println(fbdo.payload());
FirebaseJson json;
json.setJsonData(fbdo.payload());
FirebaseJsonData resultUid, resultApiKey, resultValveId;
//FirebaseJsonData result;
if (json.get(resultUid, "fields/uid/stringValue")) {
uid = resultUid.stringValue;
Serial.println("✅ uid: " + uid);
} else {
Serial.println("❌ uid の取得に失敗しました");
}
if (json.get(resultApiKey, "fields/apikey/stringValue")) {
apiKey = resultApiKey.stringValue;
Serial.println("✅ apiKey取得成功");
} else {
Serial.println("❌ apiKey の取得に失敗しました");
}
String deviceId = DEVICE_ID;
saveConfigToSPIFFS(uid, apiKey, deviceId);
} else {
Serial.println("❌ Firestore ドキュメント取得失敗:");
Serial.println(fbdo.errorReason());
}
}
void saveConfigToSPIFFS(const String& uid, const String& apiKey, const String& deviceId) {
StaticJsonDocument<512> doc;
doc["uid"] = uid;
doc["apiKey"] = apiKey;
doc["deviceId"] = deviceId;
File file = SPIFFS.open("/config.json", FILE_WRITE);
if (!file) {
Serial.println("❌ SPIFFS 書き込みに失敗しました");
return;
}
serializeJson(doc, file);
file.close();
Serial.println("✅ 構成情報を SPIFFS に保存しました:");
serializeJsonPretty(doc, Serial);
Serial.println();
}
bool loadConfigFromSPIFFS() {
if (!SPIFFS.begin(true)) {
Serial.println("❌ SPIFFS開始失敗");
return false;
}
File file = SPIFFS.open("/config.json", FILE_READ);
if (!file) {
Serial.println("❌ SPIFFS 構成ファイルが見つかりません");
file.close();
return false;
}
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, file);
if (error) {
Serial.print("❌ JSON パースエラー: ");
Serial.println(error.c_str());
return false;
}
uid = doc["uid"].as<String>();
apiKey = doc["apiKey"].as<String>();
deviceId = doc["deviceId"].as<String>();
Serial.println("✅ SPIFFS 構成読み込み成功:");
serializeJsonPretty(doc, Serial);
Serial.println();
file.close();
return true;
}
void connectToFirebase(const String &apiKey, const String &uid) {
config.api_key = apiKey;
config.database_url = "https://controlingwatersupply.firebaseio.com"; // ← Firestore使う場合でも一応必要
auth.user.email.clear();
auth.user.password.clear();
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
Serial.println("✅ Firebase 初期化完了");
}
void readValvesFromFirestore() {
valveIdList.clear(); // 毎回クリアして再取得
String path = "users/" + uid + "/devices/" + deviceId + "/valves";
int pageSize = 100;
if (Firebase.Firestore.listDocuments(&fbdo, PROJECT_ID, DATABASE_ID, path.c_str(), pageSize, "", "", "", false)) {
Serial.println("✅ Firestoreからvalvesを取得しました:");
Serial.println(fbdo.payload());
FirebaseJson json;
json.setJsonData(fbdo.payload());
FirebaseJsonData docData;
if (json.get(docData, "documents")) {
FirebaseJsonArray docArray;
docArray.setJsonArrayData(docData.stringValue);
for (size_t i = 0; i < docArray.size(); ++i) {
FirebaseJsonData item;
if (docArray.get(item, i)) {
FirebaseJson docJson(item.stringValue); //
FirebaseJsonData nameField;
if (docJson.get(nameField, "name")) {
String fullPath = nameField.stringValue;
int lastSlash = fullPath.lastIndexOf('/');
if (lastSlash != -1) {
String valveId = fullPath.substring(lastSlash + 1);
valveIdList.push_back(valveId);
Serial.println("✅ valveId 抽出: " + valveId);
}
}
}
}
}
} else {
Serial.println("❌ Firestoreからvalvesの取得に失敗: " + fbdo.errorReason());
}
}
void updateValveFromFirestore() {
String path = "users/" + uid + "/devices/" + deviceId + "/valves";
Serial.println("📡 FirestoreからvalveStateを取得します(最終修正版)...");
if (Firebase.Firestore.listDocuments(&fbdo,
PROJECT_ID,
DATABASE_ID,
path.c_str(),
10,
"", "", "", false)) {
Serial.println("✅ 取得成功:");
Serial.println(fbdo.payload());
FirebaseJson root(fbdo.payload());
FirebaseJsonData result;
// ✅ documents 配列全体を取り出す
if (root.get(result, "documents")) {
FirebaseJsonArray docArray;
docArray.setJsonArrayData(result.stringValue); // ← ここで documents を配列として再構築
FirebaseJsonData docItem;
if (docArray.get(docItem, 0)) {
FirebaseJson docJson(docItem.stringValue); // ← ドキュメント1件分をJSONに
FirebaseJsonData isOnValue;
if (docJson.get(isOnValue, "fields/isOn/booleanValue")) {
bool valveState = isOnValue.boolValue;
Serial.printf("🆕 isOn: %s\n", valveState ? "ON" : "OFF");
//digitalWrite(pinNum, valveState ? HIGH : LOW);
} else {
Serial.println("⚠️ fields/isOn/booleanValue が取得できません");
}
} else {
Serial.println("❌ documents 配列の 0番要素が取得できませんでした");
}
} else {
Serial.println("❌ documents フィールドが取得できませんでした");
}
} else {
Serial.println("❌ Firestore 取得失敗:");
Serial.println(fbdo.errorReason());
}
}
void onFirestoreUpdate(FirebaseStream data) {
Serial.println("📥 Firestore ドキュメント変更検知");
FirebaseJson payload;
FirebaseJsonData result;
payload.setJsonData(data.payload());
if (payload.get(result, "fields/isOn/booleanValue")) {
bool valveState = result.boolValue;
Serial.printf("🆕 受信 valveState: %s\n", valveState ? "ON" : "OFF");
digitalWrite(pinNum, valveState ? HIGH : LOW);
} else {
Serial.println("⚠️ isOn が見つかりません");
}
}
void loadPinNumFromFirestore(const String& vid) {
//String valveId = VALVE_ID;
if (vid.length() == 0) {
Serial.println("❌ vidが空です。loadPinNum中断。");
return;
}
String path = "users/" + uid + "/devices/" + deviceId + "/valves/" + vid;
Serial.println("🔍 pinNum 取得用 Firestoreパス:" + path);
if (Firebase.Firestore.getDocument(&fbdo, PROJECT_ID, "", path.c_str())) {
FirebaseJson json;
json.setJsonData(fbdo.payload());
FirebaseJsonData pinData;
if (json.get(pinData, "fields/pinNum/integerValue")) {
int dynamicPin = pinData.intValue;
pinNum = dynamicPin;
Serial.println("✅ pinNum 読み込み成功:" + String(dynamicPin));
pinMode(pinNum, OUTPUT);
digitalWrite(pinNum, LOW);
Serial.println("ピン番号" + pinNum);
} else {
Serial.println("pinNum フィールドが見つかりません");
pinNum = 14;
}
} else {
Serial.println("❌ FirestoreからpinNum取得失敗: " + fbdo.errorReason());
pinNum = 14;
}
}
void checkValveState() {
for (String vid : valveIdList) {
String path = "users/" + uid + "/devices/" + deviceId + "/valves/" + vid;
Serial.println("📥 isOn 取得パス: " + path);
if (Firebase.Firestore.getDocument(&fbdo, PROJECT_ID, DATABASE_ID, path.c_str())) {
FirebaseJson json;
json.setJsonData(fbdo.payload());
FirebaseJsonData onData;
FirebaseJsonData pinData;
if (json.get(pinData, "fields/pinNum/integerValue")) {
int pin = pinData.intValue;
if (json.get(onData, "fields/isOn/booleanValue")) {
bool isOn = onData.boolValue;
pinMode(pin, OUTPUT); // 念のため毎回指定(冗長でも安全)
digitalWrite(pin, isOn ? HIGH : LOW);
Serial.printf("🔁 pin %d → %s\n", pin, isOn ? "ON" : "OFF");
} else {
Serial.println("⚠️ isOn フィールドが存在しません");
}
} else {
Serial.println("⚠️ pinNum フィールドが存在しません");
}
} else {
Serial.println("❌ Firestore取得失敗: " + fbdo.errorReason());
}
}
}
void checkShouldReboot() {
String path = "users/" + uid + "/devices/" + deviceId;
if (Firebase.Firestore.getDocument(&fbdo, PROJECT_ID, "", path.c_str())) {
FirebaseJson json;
json.setJsonData(fbdo.payload());
FirebaseJsonData shouldRebootData;
if (json.get(shouldRebootData, "fields/shouldReboot/booleanValue")) {
bool shouldReboot = shouldRebootData.boolValue;
Serial.println("📝 shouldReboot = "+ String(shouldReboot ? "true" : "false"));
//Serial.println(shouldReboot ? "true" : "false");
if (shouldReboot) {
// shouldReboot を false に戻す
FirebaseJson updateJson;
updateJson.set("fields/shouldReboot/booleanValue", false);
bool result = Firebase.Firestore.patchDocument(
&fbdo,
PROJECT_ID,
"",
path.c_str(),
updateJson.raw(),
"shouldReboot"
);
if (result) {
Serial.println("📦 shouldReboot: false 書き込み完了");
delay(1000);
ESP.restart();
} else {
Serial.println("❌ shouldReboot書き戻し失敗: " + fbdo.errorReason());
}
}
} else {
Serial.println("⚠️ shouldRebootフィールドが取得できません");
}
} else {
Serial.println("❌ CheckReboot Firestore取得失敗: " + fbdo.errorReason());
}
}
// --- 使用するすべてのピンを明示的に初期化する ---
void initializePins() {
const int pinsToInitialize[] = {25, 32}; // 必要なピンをここに列挙
for (int i = 0; i < sizeof(pinsToInitialize) / sizeof(pinsToInitialize[0]); i++) {
int pin = pinsToInitialize[i];
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW); // 起動直後は必ずLOWにしておく
Serial.println("✅ 初期化ピン: " + String(pin) + " → LOW");
}
}