Ana içeriğe geç

Faz 4 — Real-time Event Propagation + Celery Jobs

PR #266 ile production'a alındı. Backend OCPP handler'larından frontend'e canlı event push + 4 zamanlanmış bakım task'ı.

7 WebSocket Event Tipi

Tüm event'ler /ws/telemetry kanalı üzerinden tenant-scoped olarak yayınlanır. Frontend useOcppLiveStream(chargerId, eventTypes) hook'u ile filtreli abone olur.

Event TypeTetikleyici HandlerPayload Özet
ocpp.bootBootNotification Acceptedcharger_id, vendor, model, firmware_version, last_boot_at
ocpp.statusStatusNotificationconnector_id, status (Available/Charging/Faulted/...), error_code, info
ocpp.meterMeterValues (downsample whitelist sonrası)connector_id, transaction_id, samples[]: measurand, value, unit, phase
ocpp.session_startedStartTransaction Acceptedsession_id, charger_id, connector_id, id_tag_masked, meter_start, started_at
ocpp.session_stoppedStopTransactionsession_id, energy_wh, duration_s, stop_reason, stopped_at
ocpp.command_resultDispatcher response (REST komut callback)command_id, action, status (accepted/rejected/timeout), latency_ms
ocpp.firmware_statusFirmwareStatusNotificationcharger_id, status (Downloading/Installing/Installed/Failed), progress

Event Envelope

Tüm event'ler ortak zarf yapısına uyar:

{
"type": "ocpp.<event_name>",
"ts": "2026-04-25T17:00:00.000Z",
"tenant_id": "<uuid>",
"charger_id": "<uuid>",
"data": {
/* event-specific */
}
}
  • ts: UTC ISO-8601, milisaniye hassasiyetinde.
  • tenant_id: Frontend kendi tenant'ı dışındaki event'leri receive etmez (backend filtrede).
  • charger_id: Frontend chargerId-bazlı filter için kullanır.
  • data: Event'e göre tipli payload (OcppEvent discriminated union, frontend/src/lib/ws/ocpp-events.ts).

MeterValues Downsample Whitelist

ocpp.meter event'leri yüksek hacimlidir (her connector × 30 sn × N measurand). UI'a kritik olmayan örnekler DB'ye yazılır ama broadcast edilmez.

Whitelist'e Dahil Measurand'lar

MeasurandSebep
Power.Active.ImportAnlık şarj gücü (kW) — Live Meter Chart
VoltageFaz gerilimi — diagnostic
Current.ImportFaz akımı — diagnostic
Energy.Active.Import.RegisterKümülatif enerji — session toplam
SoCEV battery state-of-charge (varsa)

Whitelist'e Dahil Context'ler

  • Sample.Periodic (transaction sırasında periyodik)
  • Transaction.Begin / Transaction.End (oturum açılış/kapanış)

Diğer tüm context'ler (Sample.Clock, Trigger, Other) DB'ye yazılır ama WS'e push edilmez (UI ihtiyacı yok).

samples[].phase alanı normalize semantiği (PR-B, 2026-05-11)

ocpp.meter event payload'ında her samples[] item'ın phase alanı:

  • Tip: string | null (frontend TypeScript: phase: 'L1' | 'L2' | 'L3' | 'L1-N' | 'L2-N' | 'L3-N' | 'N' | null).
  • null literal anlamı: Phase belirtilmedi (OCPP spec'inde opsiyonel; tek-faz cihaz veya N/A).
  • 'L1'/'L2'/'L3' değerleri: 3-fazlı charger sample'ları — her phase ayrı row + ayrı WS event içinde gelir (Migration 0042 sonrası PK 4-tuple desteği).

Backend normalize: DB seviyesinde phase NOT NULL DEFAULT '' (Migration 0042). Vendor phase atlarsa '' literal'i yazılır. WS event publish ve REST response site'lerinde ''null olarak çevrilir. Frontend asla '' sentinel'i görmez.

Handler refactor (commit 6b3a022): on_meter_values artık persist + broadcast'i ayrı try/except bloklarında yürütür. Persist hatası (örn. eski 3-tuple PK ile 3-fazlı conflict) broadcast'i bloke etmez → LiveMeterChart UI canlı kalır. Detay: PR-B Changelog Entry + Runbook.

4 Celery Beat Task

backend/app/tasks/ocpp_tasks.py — Celery beat schedule'a kayıtlı zamanlanmış bakım task'ları.

TaskScheduleAmaçSide Effect
heartbeat_watchdog60 snPR-H/4-fix 2026-05-21: DEPRECATED — heartbeat/inactivity kapsamı PR-A1/A2 domain alarm task'larına taşındı. Task name registry'de no-op stub korunur (geri uyumluluk).(yok — no-op)
reconcile_charging_profiles5 dkGetCompositeSchedule ile DB beklenen profil drift kontrolüDrift varsa re-push (Faz 4-C ileri)
purge_command_logGünlük 02:00 UTC90 gün retention politikasıocpp_command_log tablosundan eski kayıtlar DELETE
sync_firmware_statusSaatlikAktif firmware update'i olan charger'lara TriggerMessage(FirmwareStatusNotification)dispatcher.send (charger response'u event olarak gelir)
ocpp_domain.check_charger_offline_extended (PR-A2)5 dklast_heartbeat_at > 1h ise critical alarm açar (ocpp_charger_offline_extended)record_ocpp_domain_incident + notification dispatch

Task Idempotency Garantileri

  • heartbeat_watchdog: DEPRECATED PR-H/4-fix. Heartbeat/inactivity izleme artık PR-A1 (ocpp_charger_offline, WebSocket disconnect anlık high) + PR-A2 (ocpp_charger_offline_extended, 1h+ critical) domain evaluator'ları ile yapılır. Detay: changelog.
  • purge_command_log: DELETE WHERE created_at < NOW() - INTERVAL '90 days' — multiple çalıştırma güvenli.

Defensive Patterns (Faz 4 Hardening)

1. Best-Effort Publish

try:
await publish_ocpp_event(...)
except Exception:
logger.warning("ocpp event publish failed", exc_info=True)
# Handler akışı kesilmez — OCPP yanıtı charger'a verilir

2. Lazy Import (Circular Koruma)

events.py modülü service.py'den lazy import edilir; uygulama startup zamanı circular reference yaratmaz.

3. DetachedInstanceError Snapshot

StartTransaction / StopTransaction handler'larında SQLAlchemy session kapanmadan önce gerekli alanlar plain dict snapshot olarak alınır; event publish sonrası lazy-load denemesi DetachedInstanceError fırlatmaz.

4. id_tag Son-4 Mask (KVKK)

def mask_id_tag(id_tag: str) -> str:
if len(id_tag) <= 4:
return "****"
return "*" * (len(id_tag) - 4) + id_tag[-4:]

RFID ID'leri WS event'lerinde, audit log'larda ve UI'da maskelenmiş gönderilir. Plaintext id_tag yalnızca DB'de (encrypted at rest yakında).

5. Tenant İzolasyonu Her Publish'te

WS broker event.tenant_id field'ını subscriber tenant ile karşılaştırır; cross-tenant fan-out mümkün değil.

Prometheus Metric'leri

MetricFazEtiketlerAmaç
ocpp_ws_events_published_totalFaz 4-A (yeni)event_type, tenant_idEvent volume + drop detection
ocpp_messages_totalFaz 2action, direction, statusProtokol mesaj sayacı
ocpp_command_latency_secondsFaz 2actionREST → dispatcher → charger response süresi
ocpp_command_timeouts_totalFaz 2 remediationaction, timeout_kind30sn dispatcher timeout sayacı
ocpp_active_chargersFaz 2tenant_idOnline charger count gauge

Grafana Panel Önerileri

  • Event throughput: rate(ocpp_ws_events_published_total[1m]) — event tipine göre breakdown.
  • Command tail latency: histogram_quantile(0.99, ocpp_command_latency_seconds_bucket) — p99 < 5sn target.
  • Timeout oranı: rate(ocpp_command_timeouts_total[5m]) / rate(ocpp_messages_total{direction="outbound"}[5m]) — < %1 target.

Faz 4 Çıktı Listesi (tamamlandı — PR #266)

#İşDosya
17 publish helper fonksiyonubackend/app/core/ocpp/events.py
210 publish çağrısı (handler entegrasyonu)backend/app/core/ocpp/zeus_charge_point.py
34 Celery beat taskbackend/app/tasks/ocpp_tasks.py
4Beat schedule kaydıbackend/app/core/celery/beat_schedule.py
5Prometheus metricbackend/app/core/observability/__init__.py
6Defensive pattern test'leribackend/tests/core/ocpp/test_events.py

Sonraki: Faz 5 — Frontend MVP