main.cpp
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
 
#include <Adafruit_BME680.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoJson.h>
 
// ====== USER CONFIG ======
const char* WIFI_SSID     = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
 
const char* FIREBASE_API_KEY = "YOUR_FIREBASE_API_KEY"; // from firebaseConfig.apiKey
const char* FIREBASE_DB_URL  = "https://YOUR_PROJECT_ID-default-rtdb.YOUR_REGION.firebasedatabase.app";
 
const char* DEVICE_EMAIL    = "device@sparrow.local";
const char* DEVICE_PASSWORD = "DEVICE_PASSWORD";
const char* DEVICE_ID       = "sparrow-01";
 
// Google GTS Root R1 (cross-signed by GlobalSign Root CA), seen in TLS chain for
// identitytoolkit.googleapis.com and *.firebaseio.com (captured via openssl s_client)
static const char GOOGLE_ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx
OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT
GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63
ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS
iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k
KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ
DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk
j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5
cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW
CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499
iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei
Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap
sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b
9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf
BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw
JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH
MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al
oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy
MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF
AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9
NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9
WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw
9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy
+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----
)EOF";
 
// Hardware (adapt to your Sparrow board)
#define I2C_SDA       21   
#define I2C_SCL       22   
#define NEOPIXEL_PIN  3    
#define MIN_VALID_EPOCH 1577836800UL // Jan 1 2020 used to detect if NTP time is set
 
// How often to send data / poll LED
const unsigned long TELEMETRY_INTERVAL_MS = 10UL * 1000UL; // 10 seconds
const unsigned long LED_POLL_INTERVAL_MS  = 2UL * 1000UL;  // 2 seconds
 
// Re-auth roughly every 50 minutes
const unsigned long TOKEN_REFRESH_MS = 50UL * 60UL * 1000UL;
 
// ====== Globals ======
WiFiClientSecure secureClient;
Adafruit_BME680 bme;
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
 
String idToken;
unsigned long lastSignIn      = 0;
unsigned long lastTelemetry   = 0;
unsigned long lastLedPoll     = 0;
bool timeSynced               = false;
 
// ====== Utility: Time/NTP ======
bool hasValidTime() {
  return time(nullptr) > MIN_VALID_EPOCH;
}
 
void syncTimeIfNeeded() {
  if (timeSynced || WiFi.status() != WL_CONNECTED) {
    return;
  }
 
  // Adjust offsets for your timezone / daylight saving as needed
  const long gmtOffset_sec = 0;
  const int daylightOffset_sec = 0;
  configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org", "time.nist.gov");
 
  struct tm timeinfo;
  if (getLocalTime(&timeinfo, 2000)) { // wait up to 2 seconds
    timeSynced = true;
    Serial.println("NTP time synced");
  }
}
 
// ====== Utility: WiFi ======
void connectWiFi() {
  Serial.print("Connecting to WiFi ");
  Serial.println(WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < 40) {
    delay(500);
    Serial.print(".");
    retries++;
  }
  Serial.println();
 
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("WiFi connected, IP = ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("WiFi connection failed");
  }
}
 
// ====== Utility: Firebase Sign-in (email/password) ======
bool firebaseSignIn() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    if (WiFi.status() != WL_CONNECTED) {
      return false;
    }
  }
 
  String url = String("https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=") + FIREBASE_API_KEY;
 
  HTTPClient https;
  https.begin(secureClient, url);
  https.addHeader("Content-Type", "application/json");
 
  StaticJsonDocument<256> payloadDoc;
  payloadDoc["email"] = DEVICE_EMAIL;
  payloadDoc["password"] = DEVICE_PASSWORD;
  payloadDoc["returnSecureToken"] = true;
 
  String payload;
  serializeJson(payloadDoc, payload);
 
  Serial.println("Signing in to Firebase...");
  int httpCode = https.POST(payload);
  if (httpCode != 200) {
    Serial.print("Sign-in failed, HTTP code ");
    Serial.println(httpCode);
    Serial.println(https.getString());
    https.end();
    return false;
  }
 
  DynamicJsonDocument respDoc(1024);
  DeserializationError err = deserializeJson(respDoc, https.getString());
  https.end();
 
  if (err) {
    Serial.print("Failed to parse sign-in response: ");
    Serial.println(err.c_str());
    return false;
  }
 
  idToken = respDoc["idToken"].as<String>();
  String expiresInStr = respDoc["expiresIn"].as<String>();
  Serial.print("Sign-in OK, idToken length = ");
  Serial.println(idToken.length());
  Serial.print("Token expires in (sec): ");
  Serial.println(expiresInStr);
 
  lastSignIn = millis();
  return true;
}
 
// Ensure we have a valid token
bool ensureSignedIn() {
  if (idToken.length() == 0 || millis() - lastSignIn > TOKEN_REFRESH_MS) {
    return firebaseSignIn();
  }
  return true;
}
 
// ====== Utility: HTTP POST to Realtime DB ======
bool firebasePost(const String& path, const String& jsonBody) {
  if (!ensureSignedIn()) {
    Serial.println("Cannot POST: not signed in");
    return false;
  }
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    if (WiFi.status() != WL_CONNECTED) return false;
  }
 
  String url = String(FIREBASE_DB_URL) + path + ".json?auth=" + idToken;
 
  HTTPClient https;
  https.begin(secureClient, url);
  https.addHeader("Content-Type", "application/json");
 
  int httpCode = https.POST(jsonBody);
  Serial.print("POST ");
  Serial.print(path);
  Serial.print(" -> HTTP ");
  Serial.println(httpCode);
 
  if (httpCode < 200 || httpCode >= 300) {
    Serial.println(https.getString());
    https.end();
    return false;
  }
  https.end();
  return true;
}
 
// ====== Utility: HTTP GET from Realtime DB ======
bool firebaseGet(const String& path, String& responseOut) {
  if (!ensureSignedIn()) {
    Serial.println("Cannot GET: not signed in");
    return false;
  }
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    if (WiFi.status() != WL_CONNECTED) return false;
  }
 
  String url = String(FIREBASE_DB_URL) + path + ".json?auth=" + idToken;
 
  HTTPClient https;
  https.begin(secureClient, url);
 
  int httpCode = https.GET();
  Serial.print("GET ");
  Serial.print(path);
  Serial.print(" -> HTTP ");
  Serial.println(httpCode);
 
  if (httpCode != 200) {
    Serial.println(https.getString());
    https.end();
    return false;
  }
 
  responseOut = https.getString();
  https.end();
  return true;
}
 
// ====== BME680 ======
bool initBME() {
  Wire.begin(I2C_SDA, I2C_SCL);
 
  if (!bme.begin(0x76)) {  // change to 0x77 if needed
    Serial.println("Could not find BME680 sensor!");
    return false;
  }
 
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(0, 0); // turn off gas heater to save power
 
  Serial.println("BME680 initialized.");
  return true;
}
 
bool readBME(float& temperature, float& humidity, float& pressure) {
  if (!bme.performReading()) {
    Serial.println("Failed to read BME680");
    return false;
  }
  temperature = bme.temperature;              // °C
  humidity    = bme.humidity;                 // %
  pressure    = bme.pressure / 100.0f;        // hPa
  return true;
}
 
// ====== NeoPixel helpers ======
uint32_t parseColor(const String& hexColor) {
  String hex = hexColor;
  if (hex.startsWith("#")) {
    hex.remove(0, 1);
  }
  if (hex.length() != 6) {
    return pixel.Color(255, 255, 255); // default to white
  }
  long rgb = strtol(hex.c_str(), nullptr, 16);
  uint8_t r = (rgb >> 16) & 0xFF;
  uint8_t g = (rgb >> 8) & 0xFF;
  uint8_t b = rgb & 0xFF;
  return pixel.Color(r, g, b);
}
 
void applyLedState(const String& state, const String& colorHex) {
  bool on = state == "on";
  uint32_t color = parseColor(colorHex);
  if (!on) {
    pixel.setPixelColor(0, 0, 0, 0);
  } else {
    pixel.setPixelColor(0, color);
  }
  pixel.show();
 
  Serial.print("LED -> ");
  Serial.print(on ? "ON " : "OFF ");
  Serial.println(colorHex);
}
 
// ====== Periodic tasks ======
void sendTelemetryIfDue() {
  if (millis() - lastTelemetry < TELEMETRY_INTERVAL_MS) return;
  lastTelemetry = millis();
 
  float t, h, p;
  if (!readBME(t, h, p)) return;
 
  StaticJsonDocument<256> doc;
  long long nowMs = hasValidTime() ? (long long) (time(nullptr) * 1000LL)
                                   : (long long) millis();
  doc["timestamp"]   = nowMs;
  doc["temperature"] = t;
  doc["humidity"]    = h;
  doc["pressure"]    = p;
 
  String json;
  serializeJson(doc, json);
 
  String path = "/devices/" + String(DEVICE_ID) + "/telemetry";
  firebasePost(path, json);
}
 
void pollLedIfDue() {
  if (millis() - lastLedPoll < LED_POLL_INTERVAL_MS) return;
  lastLedPoll = millis();
 
  String response;
  String path = "/devices/" + String(DEVICE_ID) + "/led";
  if (!firebaseGet(path, response)) return;
 
  if (response == "null") {
    // no LED data yet
    return;
  }
 
  DynamicJsonDocument doc(256);
  DeserializationError err = deserializeJson(doc, response);
  if (err) {
    Serial.print("LED JSON parse error: ");
    Serial.println(err.c_str());
    return;
  }
 
  String state = doc["state"] | "off";
  String color = doc["color"] | "#ffffff";
  applyLedState(state, color);
}
 
// ====== SETUP / LOOP ======
void setup() {
  Serial.begin(115200);
  delay(1000);
 
  connectWiFi();
  syncTimeIfNeeded();
 
  secureClient.setTimeout(15000);
  secureClient.setCACert(GOOGLE_ROOT_CA); // validate HTTPS using Google root
 
  initBME();
 
  pixel.begin();
  pixel.clear();
  pixel.show();
 
  // Initial sign in
  firebaseSignIn();
}
 
void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }
 
  syncTimeIfNeeded();
  sendTelemetryIfDue();
  pollLedIfDue();
 
  delay(10); // tiny delay to keep loop friendly
}
iothings/laboratoare/2025_code/lab9_1.txt · Last modified: 2025/11/23 13:42 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0