Ana içeriğe geç

ESP8266 ve Servo Motor ile Akıllı Sunucu Güç Tetikleyicisi (Home Assistant) Akıllı Parmak

📌 Projenin Amacı

Kişisel sunucumuz (eski bir laptop vb.) elektrik kesintilerinde veya işletim sistemi (HAOS) çöktüğünde fiziksel bir müdahaleye ihtiyaç duyar. Bu proje; bir ESP8266 (NodeMCU) ve SG90 Servo Motor kullanarak bilgisayarın güç tuşuna fiziksel olarak basan, ancak bunu yaparken yanlış alarmları ve gereksiz yeniden başlatmaları (reboot loop) önlemek için akıllı ağ kontrolleri yapan bir sistemdir.

🚀 Öne Çıkan Özellikler (Neden Akıllı?)

  • Çift Katmanlı Durum Kontrolü: Sadece Home Assistant arayüzünü (8123) değil, donanım katmanını da (Observer - 4357) denetler. Arayüz çökse bile sunucu fiziksel olarak çalışıyorsa körleme tuşa basmaz.
  • Yedekli Ağ (Fallback) Sistemi: Sunucunun Ethernet (Kablolu) bağlantısı kopsa bile, Wi-Fi IP'si üzerinden ikincil bir ping atar. Ağlardan birinden cevap gelirse tetiklemeyi iptal eder.
  • Soğuk Başlangıç ve Modem Koruması: Elektrik kesintisi sonrası sistem geri geldiğinde, modemin internete bağlanması ve sunucunun boot edilmesi için 5 dakika (300 saniye) sabırla bekler, gereksiz panik yapmaz.
  • 24 Saatlik Bakım Modu: Fiziksel sunucu bakımı yaparken sistemin tuşa basmasını ve Telegram'a mesaj atmasını tek tuşla 24 saatliğine (veya manuel kapatana kadar) donduran entegre anahtar.
  • Akıllı Telegram Bildirimleri: Manuel tetiklemelerde mesaj atarak rahatsızlık vermez. Sadece sistem kritik bir kararla otomatik müdahale yaptığında veya elektrik gidip/geldiğinde haber verir.

🛠️ Kullanılan Donanımlar

  • Mikrodenetleyici: NodeMCU V2 (ESP8266)
  • Motor: SG90 Servo Motor (D2 pinine bağlı)
  • Sunucu: Hometech Alfa 400C Laptop (veya herhangi bir PC)
  • Montaj: 3M VHB Akrilik Çift Taraflı Bant (Motoru kasaya sabitlemek için en güvenli yöntem) (Ama ben japonla yapıştırdım kemik gibi)

⚙️ Fiziksel Kurulum ve Kalibrasyon İpuçları

Servo motoru bilgisayara yapıştırmadan önce sıfır noktasını doğru ayarlamak çok önemlidir: 1. ESPHome kodunu karta yükleyin ve karta elektrik verin. Motor -1.0 (bekleme) konumuna gelip kilitlenecektir. 2. Elektriği kesin ve servo motorun ucundaki beyaz plastik kolu çıkartın. 3. Plastik kolu, güç tuşunun sadece 2-3 milimetre üzerinde havada kalacak şekilde dişliye tekrar takın. 4. Çift taraflı bandı kullanarak servoyu kasaya sabitleyin. 5. (Eğer basma kuvveti çok/az gelirse kod içerisindeki level: -0.7 değerini 0.0 ile -1.0 arasında değiştirerek hassas ayar yapabilirsiniz.)

💻 ESPHome Kodunun Tamamı (YAML)

[!info] Not Kodu kopyalamadan önce 192.168.1.110 (Ethernet) ve 192.168.1.149 (Wi-Fi) IP adreslerini ile Telegram Bot bilgilerinizi kendi sisteminize göre güncellemeyi unutmayın.

esphome:
  name: sunucu-tetikleyici
  friendly_name: sunucu-tetikleyici

  on_boot:
    priority: -100
    then:
      - wait_until:
          wifi.connected
      - servo.write:
          id: guc_servosu
          level: -1.0
      # Elektrik kesintisi sonrası modemin ve HAOS'un toparlanması için 5 dakika bekle
      - delay: 5min
      - script.execute: sunucu_kontrol

esp8266:
  board: nodemcuv2

logger:

api:
  encryption:
    key: "KENDI_API_SIFRENIZ"

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "sunucu-tetikleyici"
    password: "AP_SIFRENIZ"

web_server:
  port: 80

http_request:
  id: telegram_istegi
  verify_ssl: false
  timeout: 5s

globals:
  - id: sunucu_core_erisilebilir
    type: bool
    restore_value: no
    initial_value: "true"

  - id: sunucu_donanim_erisilebilir
    type: bool
    restore_value: no
    initial_value: "true"

  - id: otomatik_kilit
    type: bool
    restore_value: no
    initial_value: "false"

  - id: bakim_modu
    type: bool
    restore_value: no
    initial_value: "false"

  # Durum Hafızası: 0=İlk Açılış, 1=Sorunsuz, 2=Sadece Donanım Var, 3=Tamamen Kapalı
  - id: sistem_durumu
    type: int
    restore_value: no
    initial_value: "0" 

output:
  - platform: esp8266_pwm
    id: pwm_output
    pin: D2
    frequency: 50 Hz

servo:
  - id: guc_servosu
    output: pwm_output

switch:
  - platform: template
    name: "Bakim Modu (24 Saat)"
    id: bakim_modu_anahtari
    icon: mdi:tools
    optimistic: true
    lambda: |-
      return id(bakim_modu);
    turn_on_action:
      - lambda: 'id(bakim_modu) = true;'
      - script.execute:
          id: telegram_gonder
          mesaj: "🛠️ Bakım Modu AKTİF. 24 saat boyunca otomatik takipler ve tetiklemeler tamamen durduruldu."
      - script.execute: bakim_modu_zamanlayici
    turn_off_action:
      - lambda: 'id(bakim_modu) = false;'
      - script.stop: bakim_modu_zamanlayici
      - script.execute:
          id: telegram_gonder
          mesaj: "✅ Bakım Modu KAPATILDI. Sistem takibi tekrar devrede."
      - script.execute: sunucu_kontrol

button:
  - platform: template
    name: "Sistemi Baslat (1 sn)"
    icon: mdi:power
    on_press:
      - servo.write:
          id: guc_servosu
          level: -0.7
      - delay: 1s
      - servo.write:
          id: guc_servosu
          level: -1.0

  - platform: template
    name: "Uzun Bas (3 sn)"
    icon: mdi:power-sleep
    on_press:
      - servo.write:
          id: guc_servosu
          level: -0.7
      - delay: 3s
      - servo.write:
          id: guc_servosu
          level: -1.0

  - platform: template
    name: "Zorla Kapat (10 sn)"
    icon: mdi:power-plug-off
    on_press:
      - servo.write:
          id: guc_servosu
          level: -0.7
      - delay: 10s
      - servo.write:
          id: guc_servosu
          level: -1.0

script:
  - id: telegram_gonder
    parameters:
      mesaj: string
    then:
      - http_request.post:
          url: "[https://api.telegram.org/botTOKEN/sendMessage](https://api.telegram.org/botTOKEN/sendMessage)"
          request_headers:
            Content-Type: "application/json"
          body: !lambda |-
            return "{\"chat_id\": \"KENDI_CHAT_ID_NIZ\", \"text\": \"" + mesaj + "\"}";

  - id: bakim_modu_zamanlayici
    mode: restart
    then:
      - delay: 24h
      - switch.turn_off: bakim_modu_anahtari

  - id: kilit_ac
    then:
      - delay: 15min
      - lambda: 'id(otomatik_kilit) = false;'

  - id: servo_bas
    mode: single
    then:
      - lambda: 'id(otomatik_kilit) = true;'
      - script.execute:
          id: telegram_gonder
          mesaj: "🟠 Sunucu iki ağda da erişilemiyor ve süre doldu. Açma işlemi başlatılıyor."

      - servo.write:
          id: guc_servosu
          level: -0.7
      - delay: 1s
      - servo.write:
          id: guc_servosu
          level: -1.0

      - script.execute: kilit_ac

  - id: sunucu_kontrol
    mode: single
    then:
      - if:
          condition:
            lambda: 'return !id(bakim_modu);'
          then:
            # 1. Donanım Katmanı (HA Observer) Kontrolü (Ethernet)
            - http_request.get:
                url: "[http://192.168.1.110:4357](http://192.168.1.110:4357)"
                on_response:
                  then:
                    - lambda: 'id(sunucu_donanim_erisilebilir) = true;'
                on_error:
                  then:
                    - lambda: 'id(sunucu_donanim_erisilebilir) = false;'

            - delay: 2s

            # 1.1 Eğer Ethernet (110) cevap vermezse Wi-Fi (149) dene
            - if:
                condition:
                  lambda: 'return !id(sunucu_donanim_erisilebilir);'
                then:
                  - http_request.get:
                      url: "[http://192.168.1.149:4357](http://192.168.1.149:4357)"
                      on_response:
                        then:
                          - lambda: 'id(sunucu_donanim_erisilebilir) = true;'
                      on_error:
                        then:
                          - lambda: 'id(sunucu_donanim_erisilebilir) = false;'
                  - delay: 2s

            # 2. Yazılım Katmanı (HA Core) Kontrolü (Ethernet)
            - http_request.get:
                url: "[http://192.168.1.110:8123](http://192.168.1.110:8123)"
                on_response:
                  then:
                    - lambda: 'id(sunucu_core_erisilebilir) = true;'
                on_error:
                  then:
                    - lambda: 'id(sunucu_core_erisilebilir) = false;'

            - delay: 2s

            # 2.1 Eğer Ethernet (110) cevap vermezse Wi-Fi (149) dene
            - if:
                condition:
                  lambda: 'return !id(sunucu_core_erisilebilir);'
                then:
                  - http_request.get:
                      url: "[http://192.168.1.149:8123](http://192.168.1.149:8123)"
                      on_response:
                        then:
                          - lambda: 'id(sunucu_core_erisilebilir) = true;'
                      on_error:
                        then:
                          - lambda: 'id(sunucu_core_erisilebilir) = false;'
                  - delay: 2s

            # ANALİZ VE KARAR
            - if:
                condition:
                  lambda: 'return id(sunucu_core_erisilebilir);'
                then:
                  # Her Şey Yolunda
                  - if:
                      condition:
                        lambda: 'return id(sistem_durumu) != 1;'
                      then:
                        - if:
                            condition:
                              lambda: 'return id(sistem_durumu) == 0;'
                            then:
                              - script.execute:
                                  id: telegram_gonder
                                  mesaj: "⚡ Elektrik/Sistem devreye girdi. 5 dakikalık test süresi bitti, sunucu aktif olarak izleniyor."
                            else:
                              - script.execute:
                                  id: telegram_gonder
                                  mesaj: "🔵 Sunucu tekrar erişilebilir oldu. Sistem normale döndü."
                        - lambda: 'id(sistem_durumu) = 1;'
                else:
                  - if:
                      condition:
                        lambda: 'return id(sunucu_donanim_erisilebilir);'
                      then:
                        # Makine Açık, Arayüz Çökmüş
                        - if:
                            condition:
                              lambda: 'return id(sistem_durumu) != 2;'
                            then:
                              - script.execute:
                                  id: telegram_gonder
                                  mesaj: "🟣 Kritik Uyarı: HAOS arayüzüne ulaşılamıyor ancak sunucu donanımı çalışıyor. Otomatik müdahale yapılmıyor."
                              - lambda: 'id(sistem_durumu) = 2;'
                      else:
                        # Tamamen Kapalı veya Yeniden Başlıyor veya Modem Gitti
                        - if:
                            condition:
                              lambda: 'return id(sistem_durumu) != 3;'
                            then:
                              - script.execute:
                                  id: telegram_gonder
                                  mesaj: "🟡 Sunucuya ulaşılamıyor. Sunucu kapalı veya modem yeniden başlıyor olabilir. 5 dakika (300 sn) beklenecek."
                              - lambda: 'id(sistem_durumu) = 3;'

                        - if:
                            condition:
                              lambda: 'return !id(otomatik_kilit);'
                            then:
                              - delay: 300s

                              # 300 Saniye Sonrası Son Ethernet (110) Kontrolü
                              - http_request.get:
                                  url: "[http://192.168.1.110:4357](http://192.168.1.110:4357)"
                                  on_response:
                                    then:
                                      - lambda: 'id(sunucu_donanim_erisilebilir) = true;'
                                  on_error:
                                    then:
                                      - lambda: 'id(sunucu_donanim_erisilebilir) = false;'

                              - delay: 2s

                              # 300 Saniye Sonrası Son Wi-Fi (149) Kontrolü
                              - if:
                                  condition:
                                    lambda: 'return !id(sunucu_donanim_erisilebilir);