#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "DG_Home_WLAN.h"
// ---------------------------------------------------------------------------
// Raumvariable
// ---------------------------------------------------------------------------
// Alle Fensterkontakte in diesem Raum
const int fenster_ids[] = { 42 };
// Alle Heizkreise in diesem Raum
const int heizung_ids[] = { 147 };
// ---------------------------------------------------------------------------
// Geräte Hardware
// ---------------------------------------------------------------------------
// Taster / Bedienung
const byte TA_1 = 0; // runter
const byte TA_2 = 14; // rauf
// Entprellzeit
const int debounce = 100;
unsigned long last_debounce = 0;
// ---------------------------------------------------------------------------
// Display / UI
// ---------------------------------------------------------------------------
#define font1 u8g2_font_helvR08_te
#define font2 u8g2_font_helvB12_te
#define font3 u8g2_font_helvB14_te
const byte contrast_hell = 255;
const byte contrast_dunkel = 0;
const int display_dunkel = 5000; // 5 s bis Dunkelmodus
volatile unsigned long last_display = 0;
byte rssi_graph = 0; // 0..3: RSSI-Balkenstufe
bool refresh = false;
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(
U8G2_R0,
/* reset = */ U8X8_PIN_NONE);
// ---------------------------------------------------------------------------
// DS18B20
// ---------------------------------------------------------------------------
const byte ONE_WIRE_BUS = 2;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// ---------------------------------------------------------------------------
// Klima-Variablen
// ---------------------------------------------------------------------------
// klima_values[0] = Temperatur, klima_values[1] = RSSI
float klima_values[] = { 0.0f, 0.0f };
// Defaulttemperatur für Thermostat
float thermostat = 22.0f; // °C
unsigned long last_data_send = 0;
const unsigned long data_send = 10000UL; // alle 10 s Klima holen / senden
bool data_read = false; // Sensor im Lesemodus angestoßen
const byte loop_send = 6; // nach 6 * data_send MultiCall
volatile byte pm_change = 0; // 0=keine, 1=runter, 2=rauf
byte m_thermostat = 0;
bool hk = false; // Heizkörper an/aus
volatile bool fenster = false;
byte loop_count = 0;
// ---------------------------------------------------------------------------
// Globale Konstanten / Multicall-Layout
// ---------------------------------------------------------------------------
constexpr size_t FENSTER_COUNT = sizeof(fenster_ids) / sizeof(fenster_ids[0]);
constexpr size_t HEIZUNG_COUNT = sizeof(heizung_ids) / sizeof(heizung_ids[0]);
constexpr size_t KLIMA_COUNT = sizeof(klima_values) / sizeof(*klima_values);
// Index-Bereiche im Multicall-Array
constexpr size_t IDX_OFFSET_FENSTER = 0;
constexpr size_t IDX_OFFSET_HEIZUNG = IDX_OFFSET_FENSTER + FENSTER_COUNT;
constexpr size_t IDX_HZG_GET = IDX_OFFSET_HEIZUNG + HEIZUNG_COUNT;
constexpr size_t IDX_KLIMA_SET = IDX_HZG_GET + 1;
constexpr size_t MC_COUNT = IDX_KLIMA_SET + 1;
String mc_methods[MC_COUNT];
uint8_t mc_param_types[MC_COUNT];
String mc_params[MC_COUNT][2];
float mc_results[MC_COUNT];
void handleRoot();
void handleThermostatSet();
void refresh_display();
// ---------------------------------------------------------------------------
// Multicall-Konfiguration
// ---------------------------------------------------------------------------
void init_multicall_config() {
size_t idx = 0;
// 1) Alle Fenster → sensor_read(fenster_id, 0)
for (size_t i = 0; i < FENSTER_COUNT; ++i, ++idx) {
mc_methods[idx] = F("sensor_read");
mc_param_types[idx] = 1; // int
mc_params[idx][0] = String(fenster_ids[i]);
mc_params[idx][1] = F("0");
}
// 2) Alle Heizungen → aktor_read(heizung_id)
for (size_t i = 0; i < HEIZUNG_COUNT; ++i, ++idx) {
mc_methods[idx] = F("aktor_read");
mc_param_types[idx] = 1; // int
mc_params[idx][0] = String(heizung_ids[i]);
mc_params[idx][1] = String(); // leer
}
// 3) hzg_get("<DEVICE_NAME>-00")
mc_methods[idx] = F("hzg_get");
mc_param_types[idx] = 3; // string
mc_params[idx][0] = String(DEVICE_NAME) + "-00";
mc_params[idx][1] = String();
++idx;
// 4) klima_daten_set(DEVICE_ID, struct)
mc_methods[idx] = F("klima_daten_set");
mc_param_types[idx] = 1; // int
mc_params[idx][0] = String(DEVICE_ID);
mc_params[idx][1] = String();
++idx;
if (idx != MC_COUNT) {
// optional: Debug-Ausgabe
}
}
// ---------------------------------------------------------------------------
// Helfer: Einzel-XML-RPC (über DG_Home_WLAN)
// ---------------------------------------------------------------------------
// num_type: 0=boolean, 1=int, 2=double(*10 → int)
int send_message(const String& methodBody, uint8_t num_type) {
return DGWLAN::send_message(methodBody, num_type);
}
// Aktor/Sensor lesen/setzen
int aktor_sensor(int id_as, int set, bool aktor) {
String msg = F("<methodName>");
if (set == -1) {
msg += aktor ? F("aktor_read") : F("sensor_read");
} else {
msg += aktor ? F("aktor_set") : F("sensor_set");
}
msg += F("</methodName>\n<params>\n");
// Param 1: ID
msg += F("<param><value><int>");
msg += id_as;
msg += F("</int></value></param>\n");
// Param 2: optional set-Wert (double)
if (set != -1) {
msg += F("<param><value><double>");
msg += set;
msg += F("</double></value></param>\n");
}
msg += F("</params>\n");
if (set == -1) {
// lesen: Double *10 als int
int v = send_message(msg, 2);
return v;
} else {
// schreiben: boolean
int ok = send_message(msg, 0);
return ok;
}
}
// Solltemperatur an Server schicken
void thermostat_soll(float temp) {
String name = String(DEVICE_NAME) + "-00";
String msg = F("<methodName>hzg_change</methodName>\n<params>\n");
// Param 1: Name (string)
msg += F("<param><value><string>");
msg += name;
msg += F("</string></value></param>\n");
// Param 2: Temperatur (double)
msg += F("<param><value><double>");
msg += String(temp, 1);
msg += F("</double></value></param>\n</params>\n");
int ok = send_message(msg, 0);
(void)ok;
}
// ---------------------------------------------------------------------------
// WEBSEITEN
// ---------------------------------------------------------------------------
void handleRoot() {
// Verbindung & Qualität
const bool connected = DGWLAN::isConnected();
const String ip = DGWLAN::ip();
const uint8_t err = DGWLAN::getLastError();
const char* err_txt = DGWLAN::getLastErrorText();
String quality;
if (connected) {
const char* bars =
(rssi_graph > 2) ? "▮▮▮▮" : (rssi_graph > 1) ? "▮▮▮▯"
: (rssi_graph > 0) ? "▮▮▯▯"
: "▮▯▯▯";
quality = String(bars) + " " + String((int)klima_values[1]) + " dBm";
} else {
quality = F("nicht verbunden");
}
// Fenstertext
const char* fenster_txt = fenster ? "mind. eins offen" : "alle zu";
// Heizungstext
const char* hk_txt = hk ? "An" : "Aus";
// Ist-Temperatur (mit Sensorfehler-Abfang)
String temp_ist_str;
if (klima_values[0] < -100.0f) { // z. B. -127 bei Fehler
temp_ist_str = F("Sensorfehler");
} else {
temp_ist_str = String(klima_values[0], 1) + " °C";
}
// Soll-Temperatur
String temp_soll_str = String(thermostat, 1) + " °C";
String s = DGWLAN::DGHOME_html_header("Raumklima");
s += F("<table style='width:100%;max-width:480px;margin:0 auto;border-collapse:collapse;'>");
// --- Titelzeile ---
s += F("<tr><th colspan='2'>");
s += DEVICE_NAME;
s += F("</th></tr>");
// --- System / Verbindung ---
s += F("<tr><th colspan='2'>System & Verbindung</th></tr>");
s += F("<tr><td>Hostname</td><td align='right'><code>");
s += DGWLAN::hostname();
s += F("</code></td></tr>");
s += F("<tr><td>Version</td><td align='right'>");
s += DEVICE_VERSION;
s += F("</td></tr>");
s += F("<tr><td>Datum</td><td align='right'>");
s += DEVICE_DATE;
s += F("</td></tr>");
s += F("<tr><td>CPU</td><td align='right'>");
s += DEVICE_CPU;
s += F("</td></tr>");
s += F("<tr><td>Autor</td><td align='right'>");
s += DEVICE_AUTHOR;
s += F("</td></tr>");
s += F("<tr><td>IP-Adresse</td><td align='right'><code>");
s += ip;
s += F("</code></td></tr>");
s += F("<tr><td>Verbindung</td><td align='right'>");
s += connected ? F("<b>verbunden</b>") : F("<b>nicht verbunden</b>");
s += F("</td></tr>");
s += F("<tr><td>Netzqualität</td><td align='right'>");
s += quality;
s += F("</td></tr>");
s += F("<tr><td>Fehler</td><td align='right'><code>");
s += String(err);
s += F(" – ");
s += err_txt;
s += F("</code></td></tr>");
// --- Klima & Heizung ---
s += F("<tr><th colspan='2'>Raumklima</th></tr>");
s += F("<tr><td>Temperatur (Ist)</td><td align='right'>");
s += temp_ist_str;
s += F("</td></tr>");
s += F("<tr><td>Temperatur (Soll)</td><td align='right'>");
s += temp_soll_str;
s += F("</td></tr>");
s += F("<tr><td>Fenster</td><td align='right'>");
s += fenster_txt;
s += F("</td></tr>");
s += F("<tr><td>Heizung</td><td align='right'>");
s += hk_txt;
s += F("</td></tr>");
// --- Thermostat-Einstellung ---
s += F("<tr><th colspan='2'>Thermostat einstellen</th></tr>");
s += F("<tr><td colspan='2'>");
s += F("<form method='POST' action='/thermostat' "
"style='display:block;width:100%;max-width:480px;margin:0 auto;'>");
s += F("<label for='soll'>Soll-Temperatur [°C]</label>");
s += F("<input type='number' id='soll' name='soll' "
"step='0.5' min='15.0' max='27.0' "
"style='width:100%;box-sizing:border-box;' value='");
s += String(thermostat, 1);
s += F("'>");
s += F("<button type='submit' class='B00 Blue' "
"style='width:100%;margin-top:6px;'>Speichern</button>");
s += F("</form>");
s += F("</td></tr>");
// --- Buttons unten ---
s += F("<tr><td colspan='2' style='padding-top:10px;'>");
s += F("<a href='/' style='display:block;margin-top:4px;'>"
"<button type='button' class='B00 Yellow' style='width:100%'>Aktualisieren</button>"
"</a>");
s += F("<a href='/wlan' style='display:block;margin-top:4px;'>"
"<button type='button' class='B00 Blue' style='width:100%'>WLAN-Konfiguration</button>"
"</a>");
s += F("<a href='/diag' style='display:block;margin-top:4px;'>"
"<button type='button' class='B00 Grey2' style='width:100%'>Diagnose</button>"
"</a>");
s += F("</td></tr>");
// Footer
s += F("<tr><td colspan='2' align='center' class='muted' style='padding-top:10px'>© by ");
s += DEVICE_AUTHOR;
s += F(" ");
s += DEVICE_DATE;
s += F("</td></tr>");
s += F("</table>");
s += DGWLAN::DGHOME_html_footer();
DGHOME_web().send(200, "text/html; charset=utf-8", s);
}
void handleThermostatSet() {
if (DGHOME_web().hasArg("soll")) {
String v = DGHOME_web().arg("soll");
v.trim();
if (v.length()) {
float t = v.toFloat();
// Clamp auf 15–27 °C
if (t < 15.0f) t = 15.0f;
if (t > 27.0f) t = 27.0f;
// auf 0.5 °C runden OHNE math.h
t = ((int)(t * 2.0f + 0.5f)) / 2.0f;
thermostat = t;
// Direkt an den Server schicken
thermostat_soll(thermostat);
// Display wieder hell machen
u8g2.setContrast(contrast_hell);
last_display = millis();
// Anzeige im nächsten loop-Durchgang aktualisieren
refresh = true;
}
}
// Danach wieder zurück zur Hauptseite
handleRoot();
}
// ---------------------------------------------------------------------------
// MultiCall über DG_Home_WLAN
// ---------------------------------------------------------------------------
bool send_multicall() {
bool ok = DGWLAN::send_multicall(
mc_methods,
mc_param_types,
mc_params,
mc_results,
MC_COUNT,
"klima_daten_set",
DEVICE_NAME,
klima_values,
KLIMA_COUNT);
if (!ok) {
return false;
}
// 1) Fenster
bool irgendeinFensterOffen = false;
for (size_t i = 0; i < FENSTER_COUNT; ++i) {
if (mc_results[IDX_OFFSET_FENSTER + i] != 0.0f) {
irgendeinFensterOffen = true;
break;
}
}
if (fenster != irgendeinFensterOffen) {
fenster = irgendeinFensterOffen;
refresh = true;
}
// 2) Heizung (Hauptstatus)
bool h = false;
if (HEIZUNG_COUNT > 0) {
h = (mc_results[IDX_OFFSET_HEIZUNG] != 0.0f);
}
if (hk != h) {
hk = h;
refresh = true;
}
// 3) Thermostat-Sollwert vom Server (nur übernehmen,
// wenn lokal nichts in Bearbeitung)
if (m_thermostat == 0) {
float t = mc_results[IDX_HZG_GET];
if (thermostat != t) {
thermostat = t;
refresh = true;
}
}
// 4) klima_daten_set Ergebnis: IDX_KLIMA_SET (optional auswertbar)
return true;
}
// ---------------------------------------------------------------------------
// Anzeige
// ---------------------------------------------------------------------------
void refresh_display() {
u8g2.clearBuffer();
u8g2.setFont(font1);
u8g2.drawUTF8(0, 8, "Raumklima");
// Ist-Temperatur
u8g2.setFont(font2);
u8g2.setCursor(0, 26);
if (klima_values[0] < -100.0f) { // Sensorfehler, z.B. -127
u8g2.drawUTF8(0, 26, "Sensorfehler");
} else {
u8g2.print(klima_values[0], 1);
u8g2.drawUTF8(32, 26, "°C");
}
u8g2.setFont(font2);
u8g2.setCursor(0, 64);
uint8_t err = DGWLAN::getLastError();
if (err == 0) {
// kein Kommunikationsfehler
if (!fenster) {
// Solltemperatur
u8g2.print(thermostat, 1);
u8g2.drawUTF8(32, 64, "°C");
// Status Heizung
u8g2.setFont(font1);
u8g2.setCursor(0, 46);
u8g2.print(F("Thermostat"));
u8g2.setCursor(64, 46);
u8g2.print(F("Heizung"));
u8g2.setFont(font1);
u8g2.setCursor(64, 64);
if (hk) {
u8g2.print(F("An"));
} else {
u8g2.print(F("Aus"));
}
} else {
// Fenster offen
u8g2.print(F("Fenster offen!"));
}
} else {
// Fehlermeldung aus DG_Home_WLAN
u8g2.print(DGWLAN::getLastErrorText());
}
// WLAN-Signalbalken (nur wenn nicht "Kein WLAN")
if (err != 1) {
// immer ein Balken
u8g2.drawBox(108, 3, 2, 2);
if (rssi_graph > 0) {
u8g2.drawBox(112, 2, 4, 3);
}
if (rssi_graph > 1) {
u8g2.drawBox(118, 1, 4, 4);
}
if (rssi_graph > 2) {
u8g2.drawBox(124, 0, 4, 5);
}
}
u8g2.sendBuffer();
}
// ---------------------------------------------------------------------------
// Interrupts (2 Taster)
// ---------------------------------------------------------------------------
IRAM_ATTR void taster_01() {
if (!pm_change) {
pm_change = 1; // runter
}
}
IRAM_ATTR void taster_02() {
if (!pm_change) {
pm_change = 2; // rauf
}
}
// ---------------------------------------------------------------------------
// SETUP
// ---------------------------------------------------------------------------
void setup() {
// Display Begrüßung
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(font1);
u8g2.setContrast(contrast_hell);
u8g2.setCursor(0, 12);
u8g2.print(F("Hallo Birgit & Dirk"));
u8g2.setCursor(0, 24);
u8g2.print(F("Sensorname: "));
u8g2.print(DEVICE_NAME);
u8g2.setCursor(0, 36);
u8g2.print(F("Version: "));
u8g2.print(DEVICE_VERSION);
u8g2.sendBuffer();
// Web-Routen
DGHOME_web().on("/", handleRoot);
DGHOME_web().on("/thermostat", HTTP_POST, handleThermostatSet);
// DG_Home_WLAN starten
DGWLAN::begin();
init_multicall_config();
// Taster
pinMode(TA_1, INPUT_PULLUP);
pinMode(TA_2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(TA_1), taster_01, FALLING);
attachInterrupt(digitalPinToInterrupt(TA_2), taster_02, FALLING);
// DS18B20 initialisieren inkl. erster Messung
sensors.begin();
sensors.setResolution(12);
sensors.setWaitForConversion(false);
sensors.requestTemperatures();
delay(800); // erste Konvertierung abwarten
klima_values[0] = sensors.getTempCByIndex(0);
// RSSI initial + Balken
float rssi = DGWLAN::rssi();
klima_values[1] = rssi;
int rssi_level =
(rssi >= -55) ? 3 : (rssi >= -65) ? 2
: (rssi >= -75) ? 1
: 0;
rssi_graph = rssi_level;
last_display = millis();
loop_count = loop_send; // direkt beim ersten Durchlauf MultiCall
}
// ---------------------------------------------------------------------------
// LOOP
// ---------------------------------------------------------------------------
void loop() {
// Einmal Zeit holen
unsigned long now = millis();
// WLAN/OTA/HTTP-Service der Lib
DGWLAN::loop();
// Display dimmen
if (last_display && (now - last_display >= (unsigned long)display_dunkel)) {
last_display = 0;
u8g2.setContrast(contrast_dunkel);
}
// Taster-Entprellung & Thermostat-Verstellung
if (!last_debounce) {
if (pm_change) {
last_debounce = now;
u8g2.setContrast(contrast_hell);
last_display = now;
// nur verstellen, wenn keine Fenster-Sperre
if (!fenster) {
m_thermostat = 1;
if (pm_change == 1 && thermostat >= 15.5f) {
if (thermostat > 27.0f) {
thermostat = 27.0f;
} else {
thermostat -= 0.5f;
}
} else if (pm_change == 2 && thermostat <= 26.5f) {
if (thermostat < 15.0f) {
thermostat = 15.0f;
} else {
thermostat += 0.5f;
}
}
refresh = true;
}
}
} else if (now - last_debounce >= (unsigned long)debounce) {
pm_change = 0;
last_debounce = 0;
}
// 1 Sekunde vor dem Auslesen, Sensor anstoßen
if (!data_read && (now - last_data_send >= (data_send - 1000UL))) {
sensors.requestTemperatures();
data_read = true;
}
// Zyklische Klima-Abfrage / MultiCall
if (now - last_data_send >= data_send) {
last_data_send = now;
data_read = false;
// Temperatur lesen & runden (1 Nachkommastelle, ohne math.h)
float t = sensors.getTempCByIndex(0);
t = ((int)(t * 10.0f + 0.5f)) / 10.0f;
if (klima_values[0] != t) {
klima_values[0] = t;
refresh = true;
}
// RSSI: für Server immer aktualisieren,
// Display nur bei Stufenänderung anfassen
float rssi = DGWLAN::rssi();
if (klima_values[1] != rssi) {
klima_values[1] = rssi;
int rssi_level =
(rssi >= -55) ? 3 : (rssi >= -65) ? 2
: (rssi >= -75) ? 1
: 0;
if (rssi_graph != rssi_level) {
rssi_graph = rssi_level;
refresh = true;
}
}
// Wenn Benutzer kurz zuvor verstellt hat,
// dann beim nächsten Sendezyklus ans Server-Thermostat schicken
if (m_thermostat) {
if (m_thermostat == 1) {
m_thermostat = 2;
} else if (m_thermostat == 2) {
m_thermostat = 0;
thermostat_soll(thermostat);
}
}
// Multicall
if (loop_count >= loop_send) {
loop_count = 0;
send_multicall();
} else {
loop_count++;
}
}
// Display nur bei Änderungen neu aufbauen
if (refresh) {
refresh_display();
refresh = false;
}
}
Sorry: Bedienerfehler im Forum. Erst auf "Code" einstellen dann Code hineinkopieren...