Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:laboratoare:2025_code:lab9_1 [2025/11/23 12:45]
dan.tudose created
iothings:laboratoare:2025_code:lab9_1 [2025/11/24 16:58] (current)
dan.tudose
Line 1: Line 1:
 <code C main.cpp>​ <code C 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";​
 +
 +// 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"​);​
 +  }
 +}
 +
 +bool waitForTime(uint32_t timeoutMs = 8000) {
 +  unsigned long start = millis();
 +  while (!hasValidTime() && millis() - start < timeoutMs) {
 +    syncTimeIfNeeded();​
 +    delay(200);
 +  }
 +  return hasValidTime();​
 +}
 +
 +// ====== 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();​
 +  waitForTime();​
 +
 +  secureClient.setTimeout(15000);​
 +  secureClient.setInsecure();​ // NOTE: uses HTTPS but skips cert validation
 +
 +  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
 +}
  
 </​code>​ </​code>​
iothings/laboratoare/2025_code/lab9_1.1763894759.txt.gz · Last modified: 2025/11/23 12:45 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