ESP8266 ve OpenTherm Adaptör ile Wİ-Fİ Akıllı Kombi / Kazan Kontrolü

👤 Yazar: admin 📅 Tarih: 28.10.2026 22:15 👁️ Görüntüleme: 2

ESP8266 NodeMCU ve OpenTherm Adaptör ile Akıllı Wi-Fi Termostat Projesi

Bu rehberde, ESP8266 NodeMCU Wi-Fi modülü ve OpenTherm Adaptör kartı kullanılarak geliştirilen akıllı kombi kontrol (Wi-Fi Termostat) sisteminin donanım bağlantıları ve yazılım altyapısı incelenmektedir. ESP8266'nın dahili Wi-Fi yeteneği sayesinde kombi uzaktan akıllı telefon veya web arayüzü üzerinden kontrol edilebilir ve anlık veriler takip edilebilir. OpenTherm Adaptör kartı detayı tıklayarak detaylara ulaşabilirsiniz.

ESP8266 NodeMCU ve OpenTherm Adaptör Bağlantı Şeması


1. Donanım Pin Eşleşme Tablosu

Görseldeki devre şemasına göre ESP8266 NodeMCU, OpenTherm Adaptör ve Kombi arasındaki bağlantı haritası şu şekildedir:

Bileşen / Hat Adaptör / Kombi Terminali ESP8266 NodeMCU Karşılığı Açıklama / Kablo Rengi
Güç ve Toprak 3V / VCC 3V3 3.3V Güç Beslemesi (Kırmızı Kablo)
GND G (GND) Toprak Hattı (Siyah Kablo)
IN (Interrupt) D1 (GPIO 5) Veri Giriş / Kesme Hattı (Mavi Kablo)
Sinyal / Kontrol OUT D2 (GPIO 4) Veri Çıkış Hattı (Yeşil Kablo)
Kombi Bağlantısı (OT) OT1 ve OT2 Kombi Termostat Çıkışı Çift Damarlı Kablo (Mavi Kablolar)
ESP8266 Mantık Seviyesi Notu:
ESP8266 NodeMCU kartı 3.3V mantık seviyelerinde çalışır. OpenTherm adaptör kartı 3.3V uyumlu olduğundan doğrudan ESP8266 besleme ve GPIO pinlerine güvenle bağlanabilir.

2. C++ ESP8266 Arduino Kodu (Wi-Fi Entegrasyonlu OpenTherm Kontrolü)

Arduino oduna OTA wifi port üzerinden program yüklemede eklidir. İlk yüklemede seri porttan yükleyip sonrasında wifi üzerinden yazılım atılabilir. Programlama portu resimdeki gibi seçilmelidir.

program portu 

Aşağıdaki C++ kodu, ESP8266 kullanarak kombi durumunu okuyan ve OpenTherm protokolü üzerinden hedef sıcaklığı ayarlayan temel bir uygulama örneğidir:

C++ / ESP8266 OpenTherm Smart Thermostat Example

opentherm_wifi.ino

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <EEPROM.h>
#include "web.h"
#include <OpenTherm.h>

// OpenTherm input and output pins
const int inPin = 4;
const int outPin = 5;

// --- Wi-Fi AYARLARI ---
const char* ssid = "YENIDIR_WIFI";
const char* password = "WIFI_SIFRENIZ";

ESP8266WebServer server(80);
OpenTherm ot(inPin, outPin);

// Kontrol and İzleme Değişkenleri
float boilerTemp = 40.0;
float dhwTemp = 45.0;
float currentWaterTemp = 0.0;
float activeBoilerSet = 0.0;
float activeDhwSet = 0.0;
float modulation = 0.0;
bool flameStatus = false;
bool dhwActiveStatus = false;
bool centralHeatingEnabled = true;
bool dhwEnabled = true;
bool settingMode = true;

unsigned long ts = 0, new_ts = 0;
String lastErrorStr = "Yok (Normal)";
String boilerStatusStr = "Bilinmiyor";

void ICACHE_RAM_ATTR handleInterrupt() {
  ot.handleInterrupt();
}

void handleRoot() {
  digitalWrite(BUILTIN_LED, 1);

  if (server.method() == HTTP_POST) {
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "bt") {
        boilerTemp = server.arg(i).toFloat();
        boilerTemp = constrain(boilerTemp, 30.0, 80.0);
      } else if (server.argName(i) == "dt") {
        dhwTemp = server.arg(i).toFloat();
        dhwTemp = constrain(dhwTemp, 30.0, 65.0);
      } else if (server.argName(i) == "mode") {
        String m = server.arg(i);
        if (m == "winter") centralHeatingEnabled = true;
        else if (m == "summer") centralHeatingEnabled = false;
      } else if (server.argName(i) == "opmode") {
        String om = server.arg(i);
        if (om == "monitor") settingMode = false;
        else if (om == "setting") settingMode = true;

        EEPROM.write(0, settingMode ? 1 : 0);
        EEPROM.commit();
      }
    }
  }

  String page = FPSTR(HTTP_HTML);
  page.replace("{0}", String(boilerTemp, 1));
  page.replace("{1}", String(dhwTemp, 1));

  server.send(200, "text/html", page);
  digitalWrite(BUILTIN_LED, 0);
}

void handleData() {
  String seasonMode = centralHeatingEnabled ? "Kış Modu (Petekler Açık)" : "Yaz Modu (Petekler Kapalı)";
  String chStatusStr = centralHeatingEnabled ? "Aktif (Açık)" : "Kapalı";
  String flameStr = flameStatus ? "Alev Var (Yanıyor)" : "Alev Yok (Beklemede)";
  String dhwStr = dhwActiveStatus ? "Musluk Açık (Isıtılıyor)" : "Kapalı (Beklemede)";
  String opModeStr = settingMode ? "Ayar Modu (Tam Kontrol)" : "Monitör Modu (Salt Okunur)";

  String json = "{";
  json += "\"t\":" + String(currentWaterTemp, 1) + ",";
  json += "\"f\":\"" + flameStr + "\",";
  json += "\"dhwa\":\"" + dhwStr + "\",";
  json += "\"m\":" + String(modulation, 1) + ",";
  json += "\"bs\":" + String(activeBoilerSet, 1) + ",";
  json += "\"ds\":" + String(activeDhwSet, 1) + ",";
  json += "\"sm\":\"" + seasonMode + "\",";
  json += "\"chs\":\"" + chStatusStr + "\",";
  json += "\"om\":\"" + opModeStr + "\",";
  json += "\"om_val\":" + String(settingMode ? 1 : 0) + ",";
  json += "\"st\":\"" + boilerStatusStr + "\",";
  json += "\"err\":\"" + lastErrorStr + "\"";
  json += "}";

  server.send(200, "application/json", json);
}

void setup(void) {
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, 0);
  Serial.begin(115200);

  EEPROM.begin(512);
  byte savedMode = EEPROM.read(0);
  if (savedMode == 0 || savedMode == 1) {
    settingMode = (savedMode == 1);
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("boiler-control")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
  Serial.println("HTTP server started");

  ArduinoOTA.setHostname("esp8266-boiler-control");
  ArduinoOTA.setPassword("admin123");

  ArduinoOTA.onStart([]() {
    String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem";
    Serial.println("OTA Güncellemesi başlıyor: " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nOTA Güncellemesi tamamlandı!");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("İlerleme: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Hata[%u]: ", error);
    if (error == OTA_AUTH_ERROR) lastErrorStr = "OTA: Yetkilendirme Hatası";
    else if (error == OTA_BEGIN_ERROR) lastErrorStr = "OTA: Başlangıç Hatası";
    else if (error == OTA_CONNECT_ERROR) lastErrorStr = "OTA: Bağlantı Hatası";
    else if (error == OTA_RECEIVE_ERROR) lastErrorStr = "OTA: Veri Alım Hatası";
    else if (error == OTA_END_ERROR) lastErrorStr = "OTA: Bitiş Hatası";
  });

  ArduinoOTA.begin();
  Serial.println("OTA Hazır");

  ts = millis();
  ot.begin(handleInterrupt);
}

void loop(void) {
  ArduinoOTA.handle();
  server.handleClient();

  new_ts = millis();
  if (new_ts - ts > 2500) {
    bool enableCooling = false;

    unsigned long responseStatusMsg = ot.setBoilerStatus(centralHeatingEnabled, dhwEnabled, enableCooling);
    OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();

    if (responseStatus == OpenThermResponseStatus::SUCCESS) {
      boilerStatusStr = "Bağlantı Başarılı (OK)";

      // Gelen ham yanıt mesajını Seri Monitör'e yazdıralım (Musluk açıkken/kapalıyken takip için)
      Serial.print("OpenTherm Yanıt Mesajı (HEX): 0x");
      Serial.println(responseStatusMsg, HEX);

      if (settingMode) {
        ot.setBoilerTemperature(boilerTemp);
        ot.setDHWSetpoint(dhwTemp);
        activeBoilerSet = boilerTemp;
        activeDhwSet = dhwTemp;
      } else {
        activeBoilerSet = boilerTemp;
        activeDhwSet = dhwTemp;
      }

      float tempRead = ot.getBoilerTemperature();
      if (tempRead > 0 && tempRead < 100) {
        currentWaterTemp = tempRead;
      }

      float modRead = ot.getModulation();
      if (modRead >= 0 && modRead <= 100) {
        modulation = modRead;
      }

      flameStatus = ot.isFlameOn(responseStatusMsg);

      // Standart OpenTherm Slave Status'te Bit 2 DHW (Musluk Suyu) aktifliğini gösterir
      dhwActiveStatus = (responseStatusMsg & (1 << 2)) != 0;

      lastErrorStr = "Yok (Normal)";
    } else {
      boilerStatusStr = "İletişim Hatası!";
      if (responseStatus == OpenThermResponseStatus::NONE) lastErrorStr = "OpenTherm: Yanıt Alınamadı (NONE)";
      else if (responseStatus == OpenThermResponseStatus::INVALID) lastErrorStr = "OpenTherm: Geçersiz Veri (0x" + String(responseStatusMsg, HEX) + ")";
      else if (responseStatus == OpenThermResponseStatus::TIMEOUT) lastErrorStr = "OpenTherm: Zaman Aşımı (TIMEOUT)";
    }

    ts = new_ts;
  }
}
web.h

#ifndef WEB_H
#define WEB_H

#include <Arduino.h>

const char HTTP_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Kombi Uzaktan Kontrol</title>
  <style>
    body { font-family: Arial, sans-serif; text-align: center; background-color: #f4f4f9; margin: 0; padding: 20px; }
    .card { background: white; max-width: 420px; margin: 0 auto; padding: 20px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
    h2 { color: #333; }
    .box { background: #eef2f7; padding: 12px; border-radius: 8px; margin: 10px 0; text-align: left; font-size: 15px; }
    .info { font-size: 15px; color: #555; margin: 8px 0; }
    .error-box { background: #ffe6e6; border: 1px solid #ff4d4d; color: #cc0000; padding: 10px; border-radius: 6px; margin-top: 15px; font-size: 14px; text-align: left; }
    input[type=number] { width: 70px; padding: 6px; font-size: 16px; text-align: center; }
    input[type=submit] { padding: 8px 14px; font-size: 14px; background-color: #007BFF; color: white; border: none; border-radius: 5px; cursor: pointer; }
    input[type=submit]:hover { background-color: #0056b3; }
    .btn-mode { padding: 8px 10px; font-size: 13px; border: none; border-radius: 5px; cursor: pointer; color: white; text-decoration: none; display: inline-block; }
    .btn-winter { background-color: #d9534f; }
    .btn-summer { background-color: #f0ad4e; }
    .btn-monitor { background-color: #6c757d; }
    .btn-setting { background-color: #28a745; }
    .form-row { display: flex; justify-content: space-between; align-items: center; margin: 8px 0; }
    hr { border: 0; border-top: 1px solid #eee; margin: 15px 0; }
  </style>
  <script>
    function updateData() {
      fetch('/data')
        .then(response => response.json())
        .then(data => {
          document.getElementById('op-mode').innerText = data.om;
          document.getElementById('season-mode').innerText = data.sm;
          document.getElementById('ch-status').innerText = data.chs;
          document.getElementById('dhw-status').innerText = data.dhwa;
          document.getElementById('active-bs').innerText = data.bs;
          document.getElementById('active-ds').innerText = data.ds;
          document.getElementById('curr-temp').innerText = data.t;
          document.getElementById('flame-status').innerText = data.f;
          document.getElementById('mod-rate').innerText = data.m;
          document.getElementById('boiler-status').innerText = data.st;
          document.getElementById('last-err').innerText = data.err;

          var settingControls = document.getElementById('setting-controls');
          if (data.om_val == 0) {
            settingControls.style.display = 'none';
          } else {
            settingControls.style.display = 'block';
          }
        })
        .catch(error => console.log('Veri alınamadı:', error));
    }
    setInterval(updateData, 500);
  </script>
</head>
<body>
  <div class="card">
    <h2>Kombi Kontrol Paneli</h2>

    <!-- Canlı Veriler ve Mod Kutusu -->
    <div class="box">
      <div><b>Kontrol Modu:</b> <span id="op-mode" style="color: #007BFF; font-weight: bold;">Yükleniyor...</span></div>
      <div style="margin-top: 6px;"><b>Sezon:</b> <span id="season-mode" style="color: #333;">--</span></div>
      <div style="margin-top: 6px;"><b>Isıtma Suyu (Petekler):</b> <span id="ch-status" style="font-weight: bold; color: #28a745;">--</span></div>
      <div style="margin-top: 6px;"><b>Musluk Suyu (DHW):</b> <span id="dhw-status" style="font-weight: bold; color: #0275d8;">--</span></div>
      <div style="margin-top: 6px;"><b>Aktif Petek Seti:</b> <span id="active-bs">--</span> &deg;C</div>
      <div style="margin-top: 6px;"><b>Aktif Musluk Seti:</b> <span id="active-ds">--</span> &deg;C</div>
      <div style="margin-top: 6px;"><b>Anlık Petek Suyu:</b> <span id="curr-temp">--</span> &deg;C</div>
      <div style="margin-top: 6px;"><b>Alev Durumu:</b> <span id="flame-status" style="color: #d9534f;">--</span></div>
      <div style="margin-top: 6px;"><b>Modülasyon Oranı:</b> %<span id="mod-rate">--</span></div>
    </div>

    <hr>

    <!-- Kontrol Modu Seçimi -->
    <div class="form-row">
      <span><b>Çalışma Modu:</b></span>
      <span>
        <form action="/" method="POST" style="display:inline;">
          <input type="hidden" name="opmode" value="monitor">
          <input type="submit" value="Monitör" class="btn-mode btn-monitor">
        </form>
        <form action="/" method="POST" style="display:inline; margin-left: 3px;">
          <input type="hidden" name="opmode" value="setting">
          <input type="submit" value="Ayar" class="btn-mode btn-setting">
        </form>
      </span>
    </div>

    <hr>

    <!-- Yaz / Kış Modu Değiştirme Butonları -->
    <div class="form-row">
      <span><b>Sezon Seçimi:</b></span>
      <span>
        <form action="/" method="POST" style="display:inline;">
          <input type="hidden" name="mode" value="winter">
          <input type="submit" value="Kış" class="btn-mode btn-winter">
        </form>
        <form action="/" method="POST" style="display:inline; margin-left: 3px;">
          <input type="hidden" name="mode" value="summer">
          <input type="submit" value="Yaz" class="btn-mode btn-summer">
        </form>
      </span>
    </div>

    <!-- Sadece Ayar Modunda Görünen Kontroller -->
    <div id="setting-controls">
      <hr>
      <form action="/" method="POST">
        <div class="form-row">
          <span><b>Petek Hedef:</b></span>
          <span>
            <input type="number" name="bt" value="{0}" step="1" min="30" max="80"> &deg;C
            <input type="submit" value="Gönder">
          </span>
        </div>
      </form>

      <hr>

      <form action="/" method="POST">
        <div class="form-row">
          <span><b>Musluk Hedef:</b></span>
          <span>
            <input type="number" name="dt" value="{1}" step="1" min="30" max="65"> &deg;C
            <input type="submit" value="Gönder">
          </span>
        </div>
      </form>
    </div>

    <hr>
    
    <div class="info" style="text-align: left;"><b>Kombi Durumu:</b> <span id="boiler-status">Bağlanıyor...</span></div>

    <div class="error-box">
      <b>Son Hata / Uyarı:</b><br>
      <span id="last-err">Yok</span>
    </div>
  </div>
</body>
</html>
)rawliteral";

#endif

ip numarasından web kontrol ve görüntülemeye girince aşağıdaki gibi bir sayfa ile karşlaşacağız.

web sayfası ekran görüntüsü

💬 Yorumlar

Henüz yorum yapılmamış. İlk yorumu siz yapın!

Bir Yorum Bırakın

Güvenlik Kontrolü:

Yenidir.com
↑ Sayfa Başı
📞 İletişim ✉ E-Posta Gönder 🟥 YouTube
© 2026 Yenidir.com
Yenidir CMS V0.1