Energy Saver
Bir otelin su ısıtıcısını, elektrikli araç şarjını ve çamaşırhanesini gün öncesi elektrik piyasasının en ucuz saatlerinde çalıştırır ve bunun ne kadar tasarruf sağladığını Telegram'dan bildirir.
- Rol
- Kişisel proje — tasarım, kod, testler
- Yıl
- 2026
- Kategori
- Altyapı · Botlar
- Teknolojiler
- Python 3.12, asyncio, FastAPI, Jinja2

Sorun
Direktif (AB) 2019/944'ün 11. maddesine göre AB ülkeleri, akıllı sayacı olan her tüketicinin dinamik fiyatlı elektrik sözleşmesi talep edebilmesini sağlamak zorundadır. Böyle bir sözleşmede faturanın enerji kısmı gün öncesi piyasasını izler ve bu piyasada 1 Ekim 2025'ten beri her 15 dakikanın ayrı bir fiyatı var. Ekim 2026'da sıradan bir günde Almanya'daki fiyat kWh başına 7 ile 29 sent arasında değişti.
Bir otelin ya da kafenin ne zaman çalıştığı önemli olmayan yükleri vardır: büyük tanklı bir su ısıtıcısı, çamaşır kurutma makinesi, gece boyunca park hâlinde duran bir elektrikli araç, havuz pompası. Bunları ucuz saatlere kaydırmak yeni ekipman almadan para kazandırır, ama resepsiyonda kimse her öğleden sonra bir fiyat eğrisi okumaz.
Çözüm
- Gerçek fiyatlar Fraunhofer ISE'nin Energy-Charts API'sinden gelir (token gerekmez) ve SQLite'ta önbelleğe alınır; ENTSO-E Transparency Platform isteğe bağlı ikinci kaynaktır. Hem saatlik hem 15 dakikalık fiyatlar desteklenir.
- Her yük
devices.yamldosyasında tanımlanır: güç, günlük çalışma süresi, izin verilen zaman aralığı (22:00-07:00), en geç bitiş saati, tek blok ya da en kısa blok uzunluğuyla parçalı çalışma. - Planlayıcı en ucuz dilimleri seçer: tek blok için kayan pencere, parçalı çalışma için en ucuz k dilim ve her bloğun en kısa bir uzunluğu olduğunda küçük bir dinamik programlama. Tesis güç sınırı yüklerin fazla üst üste binmesini önler.
- Her plan, her cihazın zaman aralığı açılır açılmaz çalıştığı basit bir programla karşılaştırılır. Para
Decimalile hesaplanır, bu yüzden avro cinsinden tasarruf kesindir. - Her öğleden sonra bir Telegram mesajı, çalışma saatleri ve beklenen tasarrufla birlikte yarının planını verir. Bir web sayfası fiyat grafiğini ve cihaz başına planı gösterir.
- Shelly akıllı prizler yerel HTTP API'leri üzerinden açılıp kapatılabilir. Varsayılan deneme modudur: istek gönderilmez, sadece günlüğe yazılır.
Ekran görüntüleri


Teknolojiler
- Python 3.12
- asyncio
- FastAPI
- Jinja2
- SVG
- aiogram 3
- SQLite
- httpx
- pytest
Tüm fiyatlar tek bir 15 dakikalık ızgaraya yerleştirilir: saatlik bir fiyat dört eşit dilime dönüşür, böylece planlayıcının hangi tür veri geldiğini bilmesi gerekmez. Grafik sunucuda çizilen satır içi SVG'dir ve x ekseninde gerçek zaman vardır, bu yüzden 25 saatlik bir gün daha geniştir. Demo, bir kez indirip kaynağı ve lisansıyla (CC BY 4.0, Bundesnetzagentur | SMARD.de) projeye eklediğim 36 günlük gerçek fiyatlarla çevrimdışı çalışır.
Öne çıkan kod
Zaman aralıkları yerel saatle, fiyatlar UTC ile tutulur. Gece yarısını geçen bir aralık bittiği
güne aittir, böylece bu geceki su ısıtıcısı çalışması yarının fiyatlarıyla birlikte planlanır.
Her saat değeri kendi gününde UTC'ye çevrilir; bu sayede aynı 22:00-07:00 aralığı saatler
ileri alındığında 8 saat, geri alındığında 10 saat sürer:
def _local(day: date, minute: int, tz: ZoneInfo) -> datetime:
day = day + timedelta(days=minute // 1440)
minute %= 1440
local = datetime.combine(day, time(minute // 60, minute % 60), tzinfo=tz)
return local.astimezone(UTC)
def window_bounds(device: Device, day: date, tz: ZoneInfo) -> tuple[datetime, datetime]:
spec = device.spec # start_day_offset is -1 when the window crosses midnight
start_day = day + timedelta(days=spec.start_day_offset)
return _local(start_day, spec.start_minute, tz), _local(start_day, spec.end_minute, tz)Bulduğum ve düzelttiğim hata
Negatif fiyatlı bir günde "-0.00 €"
Ne bozuldu
Demonun ilk çalıştırması yarının planını su ısıtıcısı için -0.00 € instead of 0.29 € ve tüm
gün için Expected cost -0.00 € olarak yazdı. Sayfa en düşük fiyatı -0.0 gösterdi.
Asıl neden
25 Ekim 2026 rüzgârlı bir pazardı: Almanya'daki fiyat 02:00'den 16:00'ya kadar 0,00 ya da
−0,01 EUR/MWh'de kaldı. Planlayıcı doğru şekilde −0,01'lik dilimleri seçti, yani su ısıtıcısına
çalıştığı için 0,00003 € ödenecekti. Decimal.quantize sıfıra yuvarlanan bir sayının işaretini
korur; float biçimlendirme de aynısını yapar.
Çözüm
Para için bir, kWh başına sent için bir yuvarlama fonksiyonu: sıfıra yuvarlanan değer işaretini kaybeder, gerçek negatif bir tutar ise eksi işaretiyle yazılmaya devam eder. Test, bu dilimlerden birinin gerçek maliyetini kullanır.
def money(value: Decimal | None) -> str:
if value is None:
return "—"
return f"{value.quantize(CENT, ROUND_HALF_UP):.2f} €"def cents(value: Decimal) -> Decimal:
rounded = value.quantize(CENT, ROUND_HALF_UP)
return abs(rounded) if rounded == 0 else rounded
def money(value: Decimal | None) -> str:
return "—" if value is None else f"{cents(value):.2f} €"Sonuç
- 108 otomatik test; saat değişikliği olan iki günün gerçek fiyatları (saatlik ve 15 dakikalık), saatlik fiyatlardan 15 dakikalık fiyatlara geçiş ve kaba kuvvetle doğrulanan dinamik programlama dahil.
- Beş örnek cihazla 30 günlük gerçek Alman fiyatları üzerinde geriye dönük test (26 Eylül – 25 Ekim 2026): günde 4,88 € tasarruf, spot enerji maliyetinin %29,6'sı, ay boyunca 146,47 €.
- Demo internet ve anahtar olmadan çalışır; geriye dönük test birkaç saniye sürer.