Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
| www-messdatenerfassung-sas:sensor_code [2026/09/21 18:43] – biebl | www-messdatenerfassung-sas:sensor_code [2026/10/19 20:01] (aktuell) – biebl | ||
|---|---|---|---|
| Zeile 16: | Zeile 16: | ||
| | [[Sonstiges|Sonstiges]] | | [[Sonstiges|Sonstiges]] | ||
| - | ====== Programmcode der Sensor-Boxen ====== | + | |
| Diese Seite enthält den vollständigen Arduino-Code aller Sensor-Boxen, | Diese Seite enthält den vollständigen Arduino-Code aller Sensor-Boxen, | ||
| Zeile 1533: | Zeile 1533: | ||
| strip.show(); | strip.show(); | ||
| } | } | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Temperatursensor Innen/ | ||
| + | |||
| + | Zwei DS18B20-Temperatursensoren an einem gemeinsamen Datenpin (1-Wire-Bus): | ||
| + | |||
| + | **Verkabelung: | ||
| + | |||
| + | ^ Anschluss ^ Verbunden mit ^ Hinweis ^ | ||
| + | | DS18B20 VCC (beide) | 3,3V oder 5V | | | ||
| + | | DS18B20 GND (beide) | GND | | | ||
| + | | DS18B20 DATA (beide) | D2 (GPIO4) | Beide Sensoren parallel an denselben Pin | | ||
| + | | 4,7 kΩ Widerstand | zwischen DATA und VCC | Pull-Up, nur **ein** Widerstand für beide Sensoren | | ||
| + | |||
| + | **Sensor-Adressen ermitteln: | ||
| + | |||
| + | **Messintervall: | ||
| + | |||
| + | **Gesendete Felder:** '' | ||
| + | |||
| + | <code cpp> | ||
| + | / | ||
| + | | ||
| + | * | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | * | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | * | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | /*** 1) Identität **************************************************************************/ | ||
| + | #define SENSOR_ID 207 // eindeutige Sensor-Nummer → IP .207 | ||
| + | |||
| + | /*** 2) Netzwerk ***************************************************************************/ | ||
| + | // Aus SENSOR_ID werden Client-Name (" | ||
| + | char CLIENT[8]; | ||
| + | IPAddress ip(192, 168, 1, SENSOR_ID); | ||
| + | |||
| + | IPAddress SERVER_IP(192, | ||
| + | const int httpPort = 80; | ||
| + | |||
| + | // ============================================================ | ||
| + | // NETZWERK-AUSWAHL - genau EINE der drei Zeilen auf " | ||
| + | // ============================================================ | ||
| + | #define NETZ_A_MOBIL | ||
| + | #define NETZ_B_TEST | ||
| + | #define NETZ_C_LIVE | ||
| + | |||
| + | #if (NETZ_A_MOBIL + NETZ_B_TEST + NETZ_C_LIVE) != 1 | ||
| + | #error "Bitte genau EINE der drei NETZ_-Zeilen oben auf true setzen!" | ||
| + | #endif | ||
| + | |||
| + | // ---- BEREICH A: SAS-ARDUINO-mobil (persönliches Testnetz) ------------- | ||
| + | // Vor Weitergabe des Codes an andere Schulen DIESEN BLOCK LÖSCHEN! | ||
| + | #if NETZ_A_MOBIL | ||
| + | const char* ssid = " | ||
| + | const char* password = "< | ||
| + | const char* apikey | ||
| + | #endif | ||
| + | |||
| + | // ---- BEREICH B: SAS-ARDUINO, | ||
| + | #if NETZ_B_TEST | ||
| + | const char* ssid = " | ||
| + | const char* password = "< | ||
| + | const char* apikey | ||
| + | #endif | ||
| + | |||
| + | // ---- BEREICH C: SAS-ARDUINO, | ||
| + | #if NETZ_C_LIVE | ||
| + | const char* ssid = " | ||
| + | const char* password = "< | ||
| + | const char* apikey | ||
| + | #endif | ||
| + | |||
| + | IPAddress gateway(192, | ||
| + | IPAddress subnet (255, 255, 255, 0); | ||
| + | IPAddress dns (0, | ||
| + | static bool g_lastSendFailed = false; | ||
| + | |||
| + | /*** 3) Intervall **************************************************************************/ | ||
| + | // Pause am Ende jedes Zyklus. Messung (ca. 0,8 s) und Senden kommen noch dazu, daher | ||
| + | // 1 s weniger als das gewünschte Gesamtintervall: | ||
| + | const int SCHREIBPAUSE_MS = NETZ_A_MOBIL ? 9000 : 19000; | ||
| + | |||
| + | /*** 4) Sensoren: 2× DS18B20 ***************************************************************/ | ||
| + | #define ONE_WIRE_BUS D2 // DATA-Pin für beide Sensoren | ||
| + | |||
| + | OneWire oneWire(ONE_WIRE_BUS); | ||
| + | DallasTemperature sensors(& | ||
| + | |||
| + | // WICHTIG: Hier die echten Sensor-Adressen eintragen! | ||
| + | // Die Adressen erscheinen beim Start im seriellen Monitor (115200 Baud): | ||
| + | // " | ||
| + | // Format: {0x28, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX} | ||
| + | |||
| + | // Sensor INNEN (Ti) | ||
| + | DeviceAddress SENSOR_ADDR_INNEN | ||
| + | |||
| + | // Sensor AUSSEN (Ta) | ||
| + | DeviceAddress SENSOR_ADDR_AUSSEN = {0x28, 0x6B, 0x31, 0x7A, 0x00, 0x00, 0x00, 0xDF}; | ||
| + | |||
| + | // Flags, ob Sensoren gefunden wurden | ||
| + | bool sensorInnenOK | ||
| + | bool sensorAussenOK = false; | ||
| + | |||
| + | /*** 5) Zustände / Selbstheilung ***********************************************************/ | ||
| + | static bool | ||
| + | static uint32_t g_bootMs | ||
| + | const uint32_t BOOT_DEADLINE | ||
| + | const uint32_t WIFI_DOWN_LIMIT = 120000; // 2 min offline → Neustart | ||
| + | const uint8_t | ||
| + | static uint32_t g_wifiDownSince = 0; | ||
| + | |||
| + | /*** 6) Prototypen *************************************************************************/ | ||
| + | bool ensureWifi(uint32_t timeoutMs = 15000); | ||
| + | bool wifiBootConnect(); | ||
| + | bool netSoftReset(uint32_t connectTimeoutMs = 15000); | ||
| + | bool tcpPreflight(uint16_t port = 80, uint16_t graceMs = 50); | ||
| + | void sendData(float tempInnen, float tempAussen); | ||
| + | void initSensors(); | ||
| + | |||
| + | /*** 7) SETUP ******************************************************************************/ | ||
| + | void setup() { | ||
| + | Serial.begin(115200); | ||
| + | delay(150); | ||
| + | snprintf(CLIENT, | ||
| + | Serial.printf(" | ||
| + | CLIENT, ip.toString().c_str()); | ||
| + | |||
| + | // WLAN-Start: 3 saubere Versuche mit Funk-Reset & Backoff | ||
| + | if (!wifiBootConnect()) { | ||
| + | Serial.println(" | ||
| + | delay(800); | ||
| + | ESP.restart(); | ||
| + | } | ||
| + | |||
| + | // Beide DS18B20 Sensoren initialisieren | ||
| + | initSensors(); | ||
| + | |||
| + | delay(250); | ||
| + | |||
| + | // Startverzögerung (0..Schreibpause) entkoppelt Massenstart bei vielen Geräten | ||
| + | randomSeed(ESP.getChipId()); | ||
| + | delay(random(0, | ||
| + | |||
| + | g_bootMs = millis(); | ||
| + | } | ||
| + | |||
| + | /*** 8) LOOP ********************************************************************************/ | ||
| + | void loop() { | ||
| + | // Beide Sensoren gleichzeitig messen lassen | ||
| + | sensors.requestTemperatures(); | ||
| + | delay(800); | ||
| + | |||
| + | // Temperatur INNEN auslesen | ||
| + | float tempInnen = -127.0; | ||
| + | if (sensorInnenOK) { | ||
| + | tempInnen = sensors.getTempC(SENSOR_ADDR_INNEN); | ||
| + | if (tempInnen == DEVICE_DISCONNECTED_C || isnan(tempInnen) || tempInnen == -127.0) { | ||
| + | tempInnen = -127.0; | ||
| + | Serial.println(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Temperatur AUSSEN auslesen | ||
| + | float tempAussen = -127.0; | ||
| + | if (sensorAussenOK) { | ||
| + | tempAussen = sensors.getTempC(SENSOR_ADDR_AUSSEN); | ||
| + | if (tempAussen == DEVICE_DISCONNECTED_C || isnan(tempAussen) || tempAussen == -127.0) { | ||
| + | tempAussen = -127.0; | ||
| + | Serial.println(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | Serial.printf(" | ||
| + | |||
| + | // WLAN pflegen & ggf. reconnecten | ||
| + | ensureWifi(10000); | ||
| + | |||
| + | // Beide Werte an EmonCMS senden | ||
| + | sendData(tempInnen, | ||
| + | |||
| + | // Selbstheilung / Watchdog | ||
| + | uint32_t now = millis(); | ||
| + | if (!g_uploadedOnce && (now - g_bootMs > BOOT_DEADLINE)) { | ||
| + | Serial.println(" | ||
| + | delay(300); ESP.restart(); | ||
| + | } | ||
| + | if (g_wifiDownSince && (now - g_wifiDownSince > WIFI_DOWN_LIMIT)) { | ||
| + | Serial.println(" | ||
| + | delay(300); ESP.restart(); | ||
| + | } | ||
| + | |||
| + | // Intervall + kleiner Jitter (±0,3 s) für verteilte Last auf dem Server | ||
| + | delay(SCHREIBPAUSE_MS + random(-300, | ||
| + | } | ||
| + | |||
| + | /*** 9) Sensor-Initialisierung *************************************************************/ | ||
| + | void initSensors() { | ||
| + | sensors.begin(); | ||
| + | sensors.setWaitForConversion(true); | ||
| + | |||
| + | int count = sensors.getDeviceCount(); | ||
| + | Serial.printf(" | ||
| + | |||
| + | if (count < 2) { | ||
| + | Serial.println(" | ||
| + | } | ||
| + | |||
| + | // Alle gefundenen Sensoren durchgehen und zuordnen | ||
| + | DeviceAddress tempAddr; | ||
| + | for (int i = 0; i < count; i++) { | ||
| + | if (sensors.getAddress(tempAddr, | ||
| + | |||
| + | // Adresse ausgeben | ||
| + | Serial.printf(" | ||
| + | for (uint8_t j = 0; j < 8; j++) { | ||
| + | if (tempAddr[j] < 16) Serial.print(" | ||
| + | Serial.print(tempAddr[j], | ||
| + | if (j < 7) Serial.print(":" | ||
| + | } | ||
| + | |||
| + | // Prüfen, ob es der Innen- oder Außen-Sensor ist (Vergleich der kompletten Adresse) | ||
| + | if (memcmp(tempAddr, | ||
| + | Serial.println(" | ||
| + | sensorInnenOK = true; | ||
| + | sensors.setResolution(tempAddr, | ||
| + | |||
| + | } else if (memcmp(tempAddr, | ||
| + | Serial.println(" | ||
| + | sensorAussenOK = true; | ||
| + | sensors.setResolution(tempAddr, | ||
| + | |||
| + | } else { | ||
| + | Serial.println(" | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | Serial.println(); | ||
| + | if (sensorInnenOK && sensorAussenOK) { | ||
| + | Serial.println(" | ||
| + | } else { | ||
| + | if (!sensorInnenOK) | ||
| + | if (!sensorAussenOK) Serial.println(" | ||
| + | Serial.println(" | ||
| + | } | ||
| + | Serial.println(); | ||
| + | } | ||
| + | |||
| + | /*** 10) WLAN-Helfer ***********************************************************************/ | ||
| + | |||
| + | // Mehrstufiger WLAN-Boot: Funk hart zurücksetzen, | ||
| + | bool wifiBootConnect() { | ||
| + | WiFi.persistent(false); | ||
| + | WiFi.mode(WIFI_STA); | ||
| + | WiFi.setAutoConnect(true); | ||
| + | WiFi.setAutoReconnect(true); | ||
| + | WiFi.setOutputPower(20.5); | ||
| + | WiFi.setSleepMode(WIFI_NONE_SLEEP); | ||
| + | |||
| + | for (uint8_t attempt = 1; attempt <= WIFI_BOOT_TRIES; | ||
| + | Serial.printf(" | ||
| + | |||
| + | // Funkzustand hart resetten | ||
| + | WiFi.disconnect(true); | ||
| + | delay(200); | ||
| + | WiFi.forceSleepBegin(); | ||
| + | WiFi.forceSleepWake(); | ||
| + | |||
| + | // Statische IP setzen & verbinden | ||
| + | WiFi.config(ip, | ||
| + | WiFi.begin(ssid, | ||
| + | |||
| + | if (ensureWifi(20000)) { | ||
| + | WiFiClient poke; poke.connect(gateway, | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | // Backoff (1.0–3.0 s) vor nächstem Versuch – verhindert Synchronsturm | ||
| + | int backoff = 1000 + random(0, 2000); | ||
| + | Serial.printf(" | ||
| + | delay(backoff); | ||
| + | } | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | // Reconnect im Betrieb; merkt Offline-Startzeit für Watchdog | ||
| + | bool ensureWifi(uint32_t timeoutMs) { | ||
| + | if (WiFi.status() == WL_CONNECTED) return true; | ||
| + | Serial.print(" | ||
| + | uint32_t start = millis(); | ||
| + | while (WiFi.status() != WL_CONNECTED && millis() - start < timeoutMs) { | ||
| + | delay(250); Serial.print(" | ||
| + | } | ||
| + | if (WiFi.status() == WL_CONNECTED) { | ||
| + | Serial.printf(" | ||
| + | WiFi.localIP().toString().c_str(), | ||
| + | g_wifiDownSince = 0; | ||
| + | return true; | ||
| + | } | ||
| + | Serial.println(" | ||
| + | if (!g_wifiDownSince) g_wifiDownSince = millis(); | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | // Sanfte Netzwerk-Kur: | ||
| + | bool netSoftReset(uint32_t connectTimeoutMs) { | ||
| + | Serial.println(" | ||
| + | WiFi.disconnect(true); | ||
| + | delay(200); | ||
| + | WiFi.forceSleepBegin(); | ||
| + | delay(200); | ||
| + | WiFi.forceSleepWake(); | ||
| + | delay(200); | ||
| + | WiFi.config(ip, | ||
| + | WiFi.begin(ssid, | ||
| + | return ensureWifi(connectTimeoutMs); | ||
| + | } | ||
| + | |||
| + | // Kurzer TCP-Preflight: | ||
| + | bool tcpPreflight(uint16_t port, uint16_t graceMs) { | ||
| + | WiFiClient poke; | ||
| + | if (!poke.connect(SERVER_IP, | ||
| + | delay(graceMs); | ||
| + | poke.stop(); | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | /*** 11) Upload an EmonCMS *****************************************************************/ | ||
| + | void sendData(float tempInnen, float tempAussen) { | ||
| + | if (WiFi.status() != WL_CONNECTED) { | ||
| + | Serial.println(" | ||
| + | g_lastSendFailed = true; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // POST zu / | ||
| + | String path = "/ | ||
| + | String query = "? | ||
| + | |||
| + | // JSON mit BEIDEN Temperaturen (eine Nachkommastelle): | ||
| + | // Ti_C = Temperatur Innen, Ta_C = Temperatur Außen | ||
| + | String body = " | ||
| + | ", | ||
| + | |||
| + | // Preflight nach Fehlschlag | ||
| + | if (g_lastSendFailed) { | ||
| + | bool ok = tcpPreflight(httpPort, | ||
| + | Serial.printf(" | ||
| + | } | ||
| + | |||
| + | String hostStr = SERVER_IP.toString(); | ||
| + | |||
| + | auto doPostOnce = [&](int attempt) -> int { | ||
| + | if (WiFi.status() != WL_CONNECTED) { | ||
| + | ensureWifi(8000); | ||
| + | if (WiFi.status() != WL_CONNECTED) { | ||
| + | Serial.printf(" | ||
| + | return -10000; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | WiFiClient client; | ||
| + | client.setNoDelay(true); | ||
| + | client.setTimeout(15000); | ||
| + | |||
| + | HTTPClient http; | ||
| + | http.setTimeout(15000); | ||
| + | http.setReuse(false); | ||
| + | |||
| + | if (!http.begin(client, | ||
| + | Serial.println(" | ||
| + | return -2; | ||
| + | } | ||
| + | |||
| + | http.addHeader(" | ||
| + | http.addHeader(" | ||
| + | |||
| + | int code = http.POST(body); | ||
| + | String err = http.errorToString(code); | ||
| + | String payload = (code > 0) ? http.getString() : String(); | ||
| + | http.end(); | ||
| + | |||
| + | Serial.printf(" | ||
| + | if (code == 200) { | ||
| + | if (payload.length()) { Serial.print(" | ||
| + | Serial.printf(" | ||
| + | g_uploadedOnce | ||
| + | g_lastSendFailed = false; | ||
| + | } | ||
| + | return code; | ||
| + | }; | ||
| + | |||
| + | // 3 normale Versuche | ||
| + | int code = -9999; | ||
| + | for (int attempt = 1; attempt <= 3; ++attempt) { | ||
| + | code = doPostOnce(attempt); | ||
| + | if (code == 200) return; | ||
| + | delay(250 * attempt); | ||
| + | } | ||
| + | |||
| + | // Soft-Reset + 4. Versuch | ||
| + | if (netSoftReset(15000)) { | ||
| + | code = doPostOnce(4); | ||
| + | if (code == 200) return; | ||
| + | } | ||
| + | |||
| + | // Zweiter Soft-Reset + 5. Versuch | ||
| + | Serial.println(" | ||
| + | if (netSoftReset(15000)) { | ||
| + | code = doPostOnce(5); | ||
| + | if (code == 200) return; | ||
| + | } | ||
| + | |||
| + | g_lastSendFailed = true; // alles fehlgeschlagen | ||
| + | Serial.printf(" | ||
| + | CLIENT, tempInnen, tempAussen); | ||
| + | } | ||
| + | |||
| + | / | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | * | ||
| + | | ||
| + | | ||
| + | * | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| </ | </ | ||