Changelog
Bu changelog Zeus 2.0 OCPP entegrasyonunun tüm faz teslimat'larını ve breaking-change politikası kararlarını listeler. Format Keep a Changelog prensibine yakındır; semantik versiyonlama kararları Faz 3 §14'te tanımlıdır.
Hot-fix Entries (Production Incident Response)
[Issue #502] 2026-05-21 — RemoteStartTransaction parent_id_tag desteği + 1:1 invariant
OCPP RemoteStartTransaction çağrısında operatör artık manuel 20 karakter id_tag yerine tenant-wide "Üst Etiket" (parent_id_tag) dropdown'undan kişi/grup tanıtıcısı seçebilir. Backend dropdown seçimini 1:1 invariant ile tek bir aktif RFID kartına çözer ve OCPP dispatcher'a idTag=<resolved> gönderir. Eski id_tag payload formu geri uyumluluk için korunur (Pydantic mutex validator XOR).
Bu değişiklik non-breaking ek özelliktir; mevcut clientlar (eski frontend, mobile, integrators) id_tag ile çağırmaya devam edebilir.
Eklendi (backend)
backend/app/features/ocpp/schemas.py:1483-1565—RemoteStartRequest:id_tag: CiString20 | None(opsiyonel, varsayılanNone) — geri uyumluluk.parent_id_tag: CiString20 | None(opsiyonel, varsayılanNone) — Issue #502.@model_validator(mode="after") _mutex_id_or_parent— XOR: tam olarak biri verilmelidir, aksi halde 422VALIDATION_ERROR.model_config = ConfigDict(json_schema_extra={"examples": [...]})— OpenAPI Swagger UI'da iki geçerli payload formu (parent_id_tagveid_tag) örnek olarak görünür.
backend/app/features/ocpp/commands.py:606-764—_resolve_parent_to_id_tag(db, tenant_id, parent_id_tag, *, actor_user_id)helper:- Filtre:
tenant_id = :tenant_id AND charger_id IS NULL AND parent_id_tag = :parent AND status = 'Accepted' AND (expiry_date IS NULL OR expiry_date > NOW()) LIMIT 2. - 0 sonuç →
HTTPException(404, error_code="OCPP_PARENT_TAG_NO_ACTIVE_CARD")(audit log yazılmaz). - 1 sonuç →
entry.id_tag(plaintext) + audit logocpp.remote_start.parent_resolution(PII mask). - 2+ sonuç →
HTTPException(500, error_code="OCPP_PARENT_TAG_DATA_INTEGRITY")+ audit logocpp.remote_start.parent_resolution.invariant_violation+ structured log error. - PII koruma: log/audit payload'larda
parent_id_tagveresolved_id_tagmask_id_tag()(son 4 karakter + yıldız) ile maskelenir.
- Filtre:
backend/app/features/ocpp/commands.py:772-829—remote_start():payload.parent_id_tag is not Noneise_resolve_parent_to_id_tagçağrılır.- Aksi halde
payload.id_tagdoğrudan kullanılır (mutex sayesinde garanti). - OCPP dispatcher payload
{"connectorId": x, "idTag": resolved_id_tag}.
Eklendi (OpenAPI dokümantasyonu)
backend/app/features/ocpp/router.py:526-680—remote_start_endpoint:responsesdict 404/422/500 içincontent.application/json.examplesiçerir:- 404:
charger_not_found(NOT_FOUND) +parent_no_active_card(OCPP_PARENT_TAG_NO_ACTIVE_CARD). - 422:
mutex_violation_both_missing+mutex_violation_both_present(her ikisi de XOR ihlali, aynı VALIDATION_ERROR kodu). - 500:
parent_data_integrity(OCPP_PARENT_TAG_DATA_INTEGRITY).
- 404:
- Docstring'de hata kodları listesi ve runbook referansı.
Eklendi (frontend — bağlı bileşen)
frontend/src/components/chargers/RemoteStartDialog.tsx— manuel<Input>yerine<Select>(useTenantAuthList()filtreli liste). Boş durum CTA:RFID Kartlarısayfasına yönlendirme. Submit payload{ connector_id, parent_id_tag }.frontend/src/types/ocpp.ts—RemoteStartRequesttipi:id_tagveparent_id_tagher ikisi opsiyonel (TypeScript tarafında XOR runtime'da backend mutex enforce eder; frontend defaultparent_id_tagkullanır).- 4 yeni i18n key (TR + EN,
chargers.remoteStart.*):parentTag,parentTagPlaceholder,noParentTag,manageRfidCta,noActiveCard.
Değişti (DB schema — Migration 0056, expand-only)
ocpp_auth_list_entriestablosuna yeni partial UNIQUE INDEX:uq_auth_list_parent_per_tenant ON (tenant_id, parent_id_tag) WHERE parent_id_tag IS NOT NULL AND charger_id IS NULL.- Migration fail-loud pre-check:
SELECT COUNT(*) FROM ... GROUP BY tenant_id, parent_id_tag HAVING COUNT(*) > 1> 0 iseRuntimeErrorraise eder, runbook ile manuel cleanup beklenir. - Runbook:
docs/runbook/0056_parent_id_tag_cleanup.md(8 bölüm: duplicate tespit SQL, cleanup senaryoları, re-run, rollback, INVALID index recovery). - Migration sıralama notu (2026-05-21): İlk yazımda numara
0055idi; PR #5040055_cleanup_double_prefix_ocpp_heartbeataynı parent0054'ten dallandığı için single-head ihlali doğurdu; bu PR0056'ya alındı,down_revision = "0055_cleanup_double_prefix_ocpp_heartbeat"zincire takıldı.
Yeni hata kodları
| Kod | HTTP | Tetiklenme | Eylem |
|---|---|---|---|
OCPP_PARENT_TAG_NO_ACTIVE_CARD | 404 | parent_id_tag için tenant altında aktif kart yok | Operator → "RFID Kartları" sayfasından kart ekle veya status='Accepted'/expiry_date güncelle |
OCPP_PARENT_TAG_DATA_INTEGRITY | 500 | 1:1 invariant ihlali (2+ aktif kart aynı parent altında) | Operasyon → runbook 0056_parent_id_tag_cleanup.md ile temizle |
VALIDATION_ERROR (mutex) | 422 | İstek body'sinde id_tag ve parent_id_tag ikisi de None veya ikisi de dolu | Client → tam olarak biri dolu olacak şekilde gönder |
Geriye dönük uyumluluk
- Non-breaking, additive. Mevcut clientlar (frontend eski sürüm, mobile, external integrators)
{"connector_id": 1, "id_tag": "RFID-..."}ile çağırmaya devam edebilir → mutex validator yalnızca(id_tag is None) == (parent_id_tag is None)durumunda 422 verir. Eski payload bu eşitliği sağlamaz (id_tag dolu, parent_id_tag None → mutex geçer). - OCPP wire format değişmedi. Dispatcher'a giden CALL
{"connectorId": x, "idTag": <resolved_or_provided>}; charger için davranış aynı. - DB-level UNIQUE INDEX (Migration 0056) Partial INDEX'tir —
WHERE parent_id_tag IS NOT NULL AND charger_id IS NULLkapsamı dışında veri etkilenmez. Per-charger override (charger_id IS NOT NULL) ileri sürümde ayrı PR ile değerlendirilir. - API versioning gerekmez —
/api/ocpp/...prefix korunur.
Cross-client uyum
- Frontend: Yeni dropdown UI (RemoteStartDialog)
useTenantAuthList()hook'unu kullanır; staleTime 30s + dialog açılışta refetch. Filtre:status === 'Accepted' && (!expiry_date || new Date(expiry_date) > new Date()) && !!parent_id_tag. Defensive dedupe + alfabetik (Intl.Collator('tr-TR')). - Mobile / external integrators: Eski
id_tagpayload formu çalışmaya devam eder. Yeniparent_id_tagformu opsiyonel; integrator kendi UX'ine entegre edebilir. - Embedded / OCPP charger firmware: Charger tarafından tüketilmez; OCPP wire format değişmedi → etkisiz.
Müşteri-yüzü dokümantasyon (https://docs.enerji.kepmark.com)
Bu repo Zeus 2.0 müşteri-yüzü docs source'u (docs-site/, Docusaurus). Issue #502 için bu sürümde teknik docs güncellendi (faz3-rest-api.md error code listesi + bu changelog entry'si). Operatöre yönelik "Uzaktan Şarj Başlat" ve "RFID Kartları" sayfa içerikleri (ekran görüntülü kullanıcı kılavuzu) docs-site/docs/ altında şu an mevcut değil — operatör dokümantasyonu boşluğu için ayrı bir görev açılır (aşağıdaki "Sonraki adımlar" bölümü).
Sonraki adımlar
- Operatör dokümantasyonu (yeni docs-site sayfaları): "Uzaktan Şarj Başlat" ve "RFID Kartları" sayfaları
docs-site/docs/operator-guide/(veyadocs-site/docs/charging-stations/benzeri) altında oluşturulur. İçerik:- "Uzaktan Şarj Başlat" — dropdown akışı, "Üst Etiket nedir?" mini bölüm, boş durum CTA ekran görüntüsü, mutex hata akışları.
- "RFID Kartları" — "1 kişi = 1 kart" iş kuralı, "Üst Etiket nasıl tanımlanır?", duplicate temizleme rehberi (runbook 0056 referansı).
- Sidebar entry:
docs-site/sidebars.jsoperatör kılavuzu kategorisi eklenir. - EN-mode browser smoke (Mayıs 2026 i18n bundle olayı sonrası zorunlu): Deploy sonrası
?lang=enile Remote Start dialog açılıp "Parent Tag" başlığı ve boş durum metni doğrulanır.
Doküman
- Faz 3 REST API → Error Code Taxonomy —
OCPP_PARENT_TAG_NO_ACTIVE_CARDveOCPP_PARENT_TAG_DATA_INTEGRITYeklendi. - Repo içi:
docs/runbook/0056_parent_id_tag_cleanup.md— duplicate temizleme runbook'u (8 bölüm). - Repo içi:
.claude/plans/502-yi-planla-ve-sonra-eager-umbrella.md— Issue #502 final plan (4 onaylı tasarım kararı). - Issue: zeus2.0#502.
[PR-H/4-fix] 2026-05-21 — Legacy heartbeat_watchdog deprecate + saha bug temizlik
Saha kanıtı 2026-05-21: 1 OCPP alarm 24.5 saat boyunca status='open' kaldı. İki kök neden:
- Bug 1 — Double prefix
alarm_type:engine.record_ocpp_incidentcode.startswith("ocpp_")orijinal string'i (UPPER) kontrol ediyordu,elsebranchf"ocpp_{code.lower()}"ile prefix eklerken"OCPP_HEARTBEAT_TIMEOUT"→"ocpp_ocpp_heartbeat_timeout"üretiyordu. TümOCPP_*errorCode'ları için aynı bug (GroundFailure, OverCurrentFailure, vb. eğer tetiklenmiş olsaydı). - Bug 2 — Close path eksik: Legacy
heartbeat_watchdogCelery task ile açılan alarmlar için hiçbir reconnect senaryosunda close çağrısı yoktu (resolve_charger_offline_if_onlinesadeceocpp_charger_offlinetipini kapatır).
Mimari karar — Yol A: Watchdog deprecate
PR-A1 (WebSocket disconnect → ocpp_charger_offline) + PR-A2 (1h+ heartbeat → ocpp_charger_offline_extended) domain alarm sistemi zaten heartbeat/inactivity kapsamını sağlıyor; mimari overlap kaldırıldı.
Değişiklikler
backend/app/features/alarms/engine.py—code_lower = code.lower()lokal var üzerindenstartswithkontrolü. TümOCPP_*(UPPER/lowercase/mixed/prefix'siz) input'lar tek prefix üretir.backend/app/tasks/ocpp_tasks.py::heartbeat_watchdog— no-op stub (Celery registry geri uyumluluğu için signature korundu;_heartbeat_watchdog_asyncsilindi).backend/app/core/celery/celery_config.py—ocpp_heartbeat_watchdogbeat schedule entry kaldırıldı.- Migration 0055 (
cleanup_double_prefix_ocpp_heartbeat) — mevcut açıkocpp_ocpp_heartbeat_timeoutalarmlarıstatus='closed',close_reason='deprecated_watchdog'SET + AlarmEvent('closed') yazıldı; policy soft-delete (active=false+ name[DEPRECATED PR-H/4-fix] ...). Cascade=delete-orphan nedeniyle hard DELETE audit kaybeder; soft-delete IZLENEBILIRLIK KIRMIZI ÇIZGI'sini korur.
Saha smoke (deploy sonrası)
- Mevcut açık
e22f12ab-...alarmstatus='closed', AlarmEvent('closed') yazıldı. - Celery beat schedule artık
heartbeat_watchdogtetiklemez; PR-A1/A2 domain task'ları aktif. - Yeni OCPP error code path'ı (örn.
OCPP_GroundFailuretest scenario) artık single-prefixocpp_groundfailureüretir.
Bilinen scope sınırı
PR-H/4-fix migration resolved-notification göndermez (raw SQL UPDATE; engine _auto_close_open_alarm tetiklemez). 24.5h-old alarm sessizce kapanır — support için: müşteri "kapanma bildirimi neden gelmedi?" derse manuel cleanup gerekçesi açıklanır.
[B Paketi — Adım 4-5] 2026-05-13 — DeviceResponse.subregion_id (OCPP charger consumption feature gap)
OCPP charger için /api/devices ve /api/devices/{id} response'larında subregion bilgisi yoktu. Frontend analysis-filters charger seçicisini subregion'a göre daraltamıyor, ayrıca region/subregion bazlı energy consumption aggregation hesaplamasında ek sorgu gerekiyordu. Non-breaking, additive alan eklendi.
Eklendi (backend)
backend/app/features/devices/schemas.py:172-184—DeviceResponse.subregion_id: UUID | None(opsiyonel, defaultNone). Description'da semantik netleştirildi: OCPP charger içinOcppCharger.subregion_idLEFT JOIN ile doldurulur; gateway tabanlı cihazlarda şu anNonedöner (gateway resolution bu endpoint'te uygulanmadı).backend/app/features/devices/service.py:244-310—get_devices()veget_device()OcppCharger'a LEFT OUTER JOIN; charger olmayan cihazlardaNonekalır. ORMDevicemodeline kolon eklemeden runtime attribute olarak set edilir → DB migration yok.
Doküman (bu PR)
docs-site/docs/api-reference/endpoints.md—GET /api/devicesörnek response'asubregion_idalanı eklendi + cihaz tipi başına davranış tablosu (charger: dolu, gateway tabanlı: null, dangling: null).
Geriye dönük uyumluluk
- Non-breaking, additive. Mevcut clientlar (frontend eski sürüm, integrators) yeni alanı görmezden gelebilir —
subregion_idPydantic'teOptional[UUID]. - DB schema değişmedi —
devicestablosuna kolon eklenmedi; alan response sırasında JOIN ile türetiliyor. - API versioning gerekmez —
/api/v1/...(ya da prefix neyse) prefix korunur.
Cross-client uyum
- Frontend (Adım 6 handoff):
frontend/src/types/devices.ts(veya eşdeğer) içindekiDeviceinterface'inesubregion_id?: string | nulleklenecek. Analysis-filters charger seçicisi bu alanıdevice_role === 'charger'filtreleme ile beraber kullanacak. - Embedded / OCPP charger firmware: Bu endpoint cihaz tarafından tüketilmiyor — etkisiz.
- External integrators: Backward-compatible, ek alan opsiyonel.
Sonraki adımlar
- Adım 6 — frontend type/UI entegrasyonu (
frontend-experience-architect). - (Opsiyonel) Gateway tabanlı cihazlarda subregion resolution backend tarafında:
Device LEFT JOIN ModbusGateway/MQTTGatewayile gateway.subregion_id fallback. Şu an açık değil; gerekirse ayrı PR.
Doküman
- API Endpoint Listesi → Devices →
subregion_idalanı - Cross-reference: PR-E2 — Region/Subregion charger entegrasyonu (OcppChargerListItem.subregion_id; bu kez
DeviceResponseortak yüzeyine taşındı).
[PR-B] 2026-05-11 — OCPP MeterValues 3-fazlı persist + handler refactor + observability
Sahada ZEUS-Q95KREQY7U1FAP4H charger'ında 11+ dakikalık MeterValues persist outage gözlendi. Aynı timestamp + measurand altında L1/L2/L3 phase'li sample'lar (time, charger_id, measurand) 3-tuple PK'sini ihlal edip UniqueViolationError atıyordu. Eski handler içinde persist + broadcast tek try'da bulunduğu için tek bir DB hatası tüm pipeline'ı (UI dahil) bloke ediyordu. Hiçbir alert tetiklenmedi (gözlemlenebilirlik boşluğu).
Commit zinciri:
b8c474b— Migration 0042 (backend/alembic/versions/0042_widen_meter_samples_pk_with_phase.py)6b3a022— Handler refactor (backend/app/core/ocpp/v16/handlers/core.py::on_meter_values)ed2440e— Test paketi (backend/tests/features/ocpp/test_meter_values_persist.py)8aca714— Observability paketi (Prometheus rules + Grafana dashboard + runbook + structured-logging spec)
Değişti (DB schema — Migration 0042, expand-only)
ocpp_meter_samples(TimescaleDB hypertable) birincil anahtarı(time, charger_id, measurand)→(time, charger_id, measurand, phase).OcppMeterSample.phasekolonu:nullable=True→primary_key=True, nullable=False, server_default=''. NULLphasesatırlar migration içinde''sentinel'ine backfill edildi. ORM modelbackend/app/core/database/models.py:2532-2537ile hizalandı.- Sentinel
''semantiği: OCPP spec'indephaseopsiyonel; vendor atlarsa backend''literal'i yazar (DB NOT NULL koruması). Backend response layer (backend/app/features/ocpp/service.py:982raw path +:1023downsample path) bu sentinel'i frontend'eNone/nullolarak normalize eder.
Değişti (handler refactor — on_meter_values)
- Persist (
session.add_all + commit) ve broadcast (publish_meter_tick) ayrı try/except bloklarında. Persist patlasa bile broadcast yine denenir → LiveMeterChart UI canlı kalır. IntegrityErrorayrı dallanma —ocpp_meter_values_persist_conflictwarning event'i (vendor replay / NTP drift sinyali).- Generic
Exception→ocpp_meter_values_persist_failederror event'i.
Eklendi (Prometheus + Grafana — observability paketi)
- Metric label'ları:
ocpp_messages_total{action="MeterValues_persist", status="ok|conflict|error|partial"}ve{action="MeterValues_broadcast", status="ok|error"}— granüler hata izleme. status="partial"semantiği: persist FAIL ama broadcast OK (tarihsel veri kaybı + UI canlı). Yeni status değeri.- 4 yeni alert (
prometheus/rules/ocpp_meter_values.yml):OcppMeterValuesPersistErrorRateHigh(critical, 5m, rate > 0 → P1)OcppMeterValuesPartialPersist(warning, 10m, rate > 0.1/sn → P2)OcppMeterValuesBroadcastError(warning, 5m, rate > 0 → P3)OcppMeterValuesPersistConflictSpike(info, 15m, rate > 1/sn → P4)
- Grafana dashboard
ocpp-meter-values(17 panel): success rate, status breakdown, conflict heatmap, per-charger top-N. - Runbook: OCPP MeterValues Persist/Broadcast Failure — 4 senaryo + eskalasyon matrisi (L1/L2/L3 + P4 vendor).
- Structured logging spec:
docs/observability/structured-logging.md(repo path, docs-site dışında) — 4 yeni event (ocpp_meter_values_persist_conflict,ocpp_meter_values_persist_failed,ocpp_event_meter_publish_failed,ocpp_meter_values_failed) için required field tanımı.
Geriye dönük uyumluluk (BREAKING DEĞİL)
- API contract / OCPP wire format / response schema değişmedi.
GET /api/ocpp/sessions/{id}/meter-samplesresponse'undaphasealanı tipistring | nullaynı (PydanticMeterSampleResponse.phase: str | None). - Frontend
LiveMeterChartpreferL1 ['L1','L2','L3', null, undefined]order'ı korunur; backend''sentinel'i frontend'e ulaşmaz. - Vendor side hiçbir değişiklik yok — Beny vb. cihazlar mevcut payload'larını gönderir; backend tarafında schema-uyumlu absorb edilir.
- DB seviyesinde expand-only: PK genişledi, eski satırlar backfill ile uyumlu, geriye dönük migration'a gerek yok.
Doküman
- OCPP MeterValues Persist/Broadcast Failure Runbook
- Structured logging spec:
docs/observability/structured-logging.md - Prometheus alert dosyası:
prometheus/rules/ocpp_meter_values.yml - Grafana dashboard:
grafana/dashboards/ocpp-meter-values.json
[Hot-fix #4] 2026-04-28 — Frontend OCPP komut hata mesajı [object Object] parse fix (PR #297)
OCPP komut dialog'larında (RemoteStart/Stop/Reset/Unlock/ChangeAvailability/SendLocalList) backend'den dönen 4xx/5xx hatalarda kullanıcıya Hata: [object Object] gösteriliyordu. Backend canonical envelope ({error_code, message, details, request_id}) doğru dönüyordu; frontend JSON.stringify(err) yerine envelope'tan message alanını çıkarmıyordu.
Eklendi
frontend/src/lib/api/ocpp.ts—extractErrorMessage(payload)helper. Envelopemessagefield'ı varsa onu döner; yoksaerror_codeveya HTTP status fallback. PII guard:id_tag/password/tokengibi alanlardetailsiçinde olsa bile mesaja sızdırılmaz.- 6 OCPP komut dialog'unda (
RemoteStartDialog,RemoteStopDialog,ResetDialog,UnlockDialog,ChangeAvailabilityDialog,SendLocalListDialog)extractErrorMessage()entegrasyonu — başarısız komutlarda inline result block'ta düzgün Türkçe hata mesajı.
Kök sebep
fetchWithHeaders axios interceptor'ı response body'yi raw object olarak Error.message'a koyuyordu (String(body) → [object Object]). Frontend extractErrorMessage ile envelope'un message field'ını öncelikli okur.
Doküman
[Hot-fix #3] 2026-04-28 — Dispatcher 409 GERÇEK kök fix + WS close 1006 fix (PR #294, PR #295, PR #296)
İki bağımsız incident aynı pencerede çıktı:
(1) Dispatcher 409 — saha bug: OCPP komut RPC'leri (POST /api/ocpp/chargers/{id}/commands/*) sahada OCPP_REMOTE_REJECTED 409 dönüyordu — charger reddetmediği halde. Dispatcher python-ocpp ChargePoint.call(payload) çağrısı dict ile yapıyordu; python-ocpp ≥ 2.x dataclass instance bekliyor. Reset round-trip 25 saniyeye çıkmıştı, BootNotification gözle görülür gecikiyordu.
(2) WS close 1006 — frontend /ws/telemetry: Frontend WS bağlantısı abnormal 1006 close (peer reset) ile düşüyordu. Backend telemetry handler'ında catch-all except Exception WebSocketDisconnect'i de yutuyordu → close-frame native gönderilmediği için browser tarafında 1006 görünüyordu.
Eklendi/Değişti
- PR #296 (D₂ — saha 409 GERÇEK fix):
backend/app/core/ocpp/dispatcher.py—_invoke_charger_call()payload dict →action_class(**payload)dataclass instance mapping. python-ocpp call/reply path uyumlu hale getirildi. Saha smoke YEŞİL: Reset round-trip 25sn → ~1.5sn, BootNotification kanıtlı. - PR #295 (D₁ — defansif reply path):
backend/app/core/ocpp/dispatcher.py_serialize_payload()dataclasses.asdict()recursive guard. Reply path'te dataclass döner gelirse JSON serialize hatası vermiyor. (Tek başına saha bug'ı çözmedi — D₂ asıl çözüm; D₁ defansif katman.) - PR #294 (B — WS close 1006 fix):
backend/app/core/websocket/router.py/ws/telemetryhandler — catch-allexcept Exceptionöncesiexcept WebSocketDisconnectpropagate.await ws.close(code=1011, reason="...")gerçek hatalarda gönderiliyor; client1006yerine1011görüyor + close reason string okunabilir.
Kök sebep
- python-ocpp ≥ 2.x
ChargePoint.call(payload)API'si dataclass instance zorunlu kılar. PR #295 (D₁) sadece reply path'i serialize ediyordu; gerçek bug call path'inde dict gönderilmesi → python-ocppTypeError→ outer retry loop genericexcept→ 30sn timeout → 504/409 mapping. PR #296 (D₂)action_class(**payload)ile dataclass instance üreterek sorunu kökten çözdü.
Doküman
PR-E Bütünleşik Plan + Saha Bug Fix Wave (2026-04-27 → 2026-04-28)
OCPP UI gap analizi sonrası açılan PR-E1 → PR-E6 entegre planı + ardından gelen saha incident hot-fix'leri. Aşağıdaki entry'ler kompakt format'tadır; her PR için kullanıcı/operatör için pratik özet.
[PR-E6 — Dispatcher canary monitoring] 2026-04-28 (PR #293)
Eklendi
- Yeni Prometheus metrikleri:
ocpp_dispatcher_listening_active(Gauge) — aktiflisten_commandstask sayısı.ocpp_dispatcher_loop_errors_total{error_kind}(Counter) —error_kindwhitelist (5 değer:redis_connection,os_error,timeout,cancelled,unknown).
- Alert rules (3 yeni): dispatcher restart loop, listening task yokluk anomalisi,
cancellederror_kind sızıntısı (BUG sinyali). - Grafana dashboard panel'leri:
OCPP Charger Fleet > PR-E6 Dispatcher Canaryrow. - Runbook: PR-E6 Canary Monitoring — 24h checkpoint tablosu + sapma aksiyonları.
Doküman
[PR-E6 — Dispatcher pubsub fix] 2026-04-28 (PR #292)
Değişti
backend/app/core/ocpp/dispatcher.py—pubsub.listen()async-iterator pattern →pubsub.get_message(timeout=1.0, ignore_subscribe_messages=True)polling pattern. Saatler sonrası 504 timeout fix.
Kök sebep
redis-py 5.x async-iterator generator'ında asyncio.CancelledError yutuluyordu → outer retry loop generic except Exception path'ine düşüyor → sonsuz restart loop + pubsub session corrupt. Polling pattern'a geçince CancelledError doğru propagate eder.
[PR-E5 audit/QA bulguları] 2026-04-28 (PR #291)
Değişti
backend/app/features/ocpp/router.py— Reservationstatusquery param PydanticLiteral["active", "used", "cancelled", "expired"]whitelist (422 invariant).frontend/src/lib/api/ocpp.ts— WSS URL encode (special char tolerant).backend/tests/features/ocpp/— Cross-tenant erişim testleri (404 enumeration koruması doğrulandı).frontend/src/components/chargers/SessionDetailDrawer.tsx— Drawer kapanıştauseStatereset (state leak fix).
[PR-E5 — Komut sonuç görünürlüğü + reservations list + provisioning PDF TLS/WSS] 2026-04-27 (PR #290)
Eklendi
- Yeni endpoint'ler:
GET /api/ocpp/chargers/{id}/command-logs(PR-E5 A0) — son OCPP komutlarının istek/yanıt audit trail'i (READ_CHARGER, default limit 20, max 100).GET /api/ocpp/chargers/{id}/reservations(PR-E5 A) — charger bazlı rezervasyon listesi (CancelReservation dropdown için,statusfilteractive|used|cancelled|expired, defaultactive).
- Frontend:
CommandLogsPanel.tsx(yeni component, charger detayda son 20 komut audit trail),CommandResultAlert.tsx(inline result block), 6 dialog'da inline result,useCommandResultStatehook. - Provisioning kit PDF (PR-E5 B): PDF'te yeni satırlar —
TLS: ENABLEDveWSS URL: wss://<host>/ocpp/1.6/<cpid>(saha teknisyeni Beny UI'a doğrudan kopyalayabilir).
Doküman
[PR-E4 — Tenant-wide RFID kart yönetimi] 2026-04-27 (PR #289)
Eklendi
- Yeni endpoint family
/api/ocpp/auth-list/tenant(5 endpoint):GET /api/ocpp/auth-list/tenant— tenant geneli RFID kart listesi (charger_id IS NULL).POST /api/ocpp/auth-list/tenant— yeni kart kaydı (OCPP_AUTH_LIST_DUPLICATE409).PATCH /api/ocpp/auth-list/tenant/{id_tag}— kart status/expiry/parent_id_tag güncelle.DELETE /api/ocpp/auth-list/tenant/{id_tag}— kart sil.POST /api/ocpp/auth-list/tenant/push— seçili charger'laraSendLocalListdifferential push (per-charger sonuç:accepted | rejected | offline | error).
- Frontend:
frontend/src/app/chargers/rfid-cards/page.tsx— tenant-wide kart yönetim sayfası.
Mimari notu
Charger-spesifik override /chargers/{id}/local-auth-list (Faz 3) kalıyor; tenant-wide kayıtlar yeni endpoint family ile yönetiliyor. Authorize handler _lookup_id_tag charger-spesifik → tenant-wide fallback yapıyor (mevcut davranış korundu, geriye dönük uyumlu).
Doküman
[PR-E3 — UI Refresh] 2026-04-27 (PR #288)
Değişti (frontend-only — backend kontratı değişmedi)
- Charger detay header:
Düzenleprimary button +⋯ Daha FazlaDropdownMenu altındaRotatePasswordveDelete(yanlışlıkla tıklama koruması). - Commands tab kategorize: Şarj (RemoteStart/Stop/Unlock) / Yapılandırma (ChangeAvailability/ChangeConfig/SendLocalList) / Bakım (Reset/TriggerMessage/UpdateFirmware).
- Liste sayfası: Server-side pagination + URL state (
?limit=50&offset=100), status dot + canlı şarj süre/güç chip, list kartında inline edit kalem ikonu. - Overview ek alanlar: subregion, last_heartbeat, OCPP version, vendor/model.
Doküman
[PR-E2 — Region/Subregion charger entegrasyonu] 2026-04-27 (PR #287)
Eklendi
OcppChargerListItem.subregion_id+subregion_namefield'ları (response schema).- Yeni query parametre:
GET /api/ocpp/chargers?subregion_id={uuid}— belirli subregion'a atanmış charger'lar.
Değişti (frontend)
- Charger form: region → subregion cascade select.
- Liste sayfası: subregion filter chip.
- Detay sayfası: subregion link (region listing'e route).
Doküman
[PR-E1 — Registry TTL refresh] 2026-04-26 (PR #286)
Değişti
backend/app/features/ocpp/zeus_charge_point.py— Heartbeat handler'daregistry.refresh_ttl(cpid, heartbeat_interval)çağrısı.
Kök sebep (Reset 504 fix)
Redis owner-registry TTL heartbeat'lerle yenilenmiyordu → backend uptime arttıkça stale entry birikiyor → Reset komutu kayıp owner'a route oluyor → 30sn timeout → 504. Heartbeat handler artık her cihaz heartbeat'inde TTL uzatıyor (zombie temizlik + owner consistency).
[Migration 0039 — registration_status lowercase normalize] 2026-04-27 (PR #279)
Değişti
backend/alembic/versions/0039_*.py—OcppCharger.registration_statusenum case mismatch fix (eskiPending/Accepted/Rejected→ lowercasepending/accepted/rejected). Replay-safe: eski CHECK constraint drop + recreate pattern.
Kök sebep
schemas.py Literal "pending" | "accepted" | "rejected" (lowercase) bekliyordu; DB CHECK constraint ('Pending','Accepted','Rejected') (PascalCase) kabul ediyordu. BootNotification handler insert'i CHECK ihlali atıyordu.
[Hot-fix #2] 2026-04-26 — OCPP Charger Create HTTP 500 düzeltmesi (PR #277)
POST /api/ocpp/chargers endpoint'i production'da HTTP 500 dönüyordu — manuel charger oluşturma form submit'inde "Hata: Internal server error".
Eklendi
backend/alembic/versions/0038_chk_device_source_ocpp.py—chk_device_sourcewhitelist'ine'ocpp'ekleyen migration. NOT VALID + VALIDATE pattern (production lock minimize). Defansif_constraint_exists()helper. Convention adıck_devices_chk_device_source+ legacy adchk_device_sourcedefansif drop.backend/tests/integration/test_ocpp_charger_create.py— 10 REGRESSION test (RBAC, Pydantic, retry, multi-tenant). 0038 olmadan FAIL.backend/tests/integration/test_schema_model_contract.py— 7 schema-model drift detection test. App device_source literal'leri ⊆ DB whitelist invariant'ı. CI'da gelecek incident'leri yakalar.
Değişti
.github/workflows/deploy.yml:244—MIGRATION_TARGET="${MIGRATION_TARGET:-merge_sofar_mqtt}"→head. Önceden 0037+ revisionlar otomatik deploy'a giremiyordu (workflow_dispatchoverride veya manuel SSH apply gerektiriyordu). Production drift kapatıldı. Detay: CI/CD Pipeline.backend/app/core/database/models.py:360-364—device_sourceyorumu güncellendi (whitelist + migration pattern + contract test referansı).
Kök sebep
0037_add_ocpp_tables migration'ı OCPP 1.6J şemasını eklerken devices.chk_device_source CHECK constraint'ine 'ocpp' eklemeyi atladı. Mevcut whitelist ('modbus','zigbee','virtual','manual'). Backend service.create_charger (backend/app/features/ocpp/service.py:305) device_source="ocpp" insert ediyor → CheckViolationError → endpoint 500.
Bilinen issue (follow-up)
alembic_version.version_num kolonu VARCHAR(32) — revision id 32 char altında olmalı. İlk denemede 0038_extend_chk_device_source_ocpp (34 char) StringDataRightTruncationError verdi → 0038_chk_device_source_ocpp (27 char) ile düzeltildi. Follow-up: alembic_version VARCHAR(64) migration ayrı PR.
Doküman
[Hot-fix #1] 2026-04-26 — Frontend WS refactor + Backend Origin guard (PR #276)
Production'da https://enerji.kepmark.com tüm sayfalarda "Application error: a client-side exception has occurred" gösteriyordu. Browser console: SecurityError: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
Frontend (eklendi/değişti)
frontend/src/config/site.ts—getWsUrl()host-relativewss://${window.location.host}/wsüretiyor;NEXT_PUBLIC_WS_URLenv override sadece protokol uyumluysa kabul (HTTPS sayfadaws://env reddediliyor + console.warn).frontend/src/lib/websocket/client.ts—connect()öncesi mixed-content guard (https:+ws://→setState("error")early return) +new WebSocket()try/catch.frontend/src/lib/websocket/types.ts—WebSocketStatetipine"error"eklendi.frontend/src/lib/websocket/context.tsx—WebSocketProvidertry/catch+client = nullfallback (subscribe no-op).frontend/src/lib/websocket/WebSocketErrorBoundary.tsx— YENİ class component.componentDidCatchile sync exception yakalanırsa fallback children render.frontend/src/components/providers.tsx— BoundaryWebSocketProvider'ı sarıyor; fallback<>{children}</>.frontend/Dockerfile—ARG NEXT_PUBLIC_WS_URLveENV NEXT_PUBLIC_WS_URLsatırları silindi.docker-compose.yml— frontend service'inbuild.args+environmentblokları temizlendi.
Backend (eklendi/değişti)
backend/app/core/websocket/security.py— YENİ.is_allowed_websocket_origin(origin, allowed_origins)helper. CSWSH (Cross-Site WebSocket Hijacking) koruması.backend/app/core/websocket/router.py:78-101— telemetry_websocket başındaOriginheader doğrulama. Uyumsuzsaawait websocket.close(code=4003, reason="Forbidden origin")+ structured logwebsocket_origin_rejected. Dev fallback:cors_origins_listboşsa Origin guard skip.backend/app/core/config/settings.py:65-78—cors_origins_listproperty: whitespace + trailing slash normalize.docker-compose.yml:213—CORS_ORIGINSraw IP origin'lerinden temizlendi.- TODO Faz 2 (router.py:97-101): Token query string yerine
Sec-WebSocket-Protocolsubprotocol veya HttpOnly+Secure+SameSite=Lax cookie'ye taşı.
Güvenlik
- OCPP charger endpoint'leri (
/ocpp/1.6/{cpid},/ocpp/2.0.1/{cpid}) Origin guard'sız kaldı (RFC 6455 — native client için, browser değil). Detay: WebSocket Origin Guard.
CI/CD
- 3 workflow'dan
NEXT_PUBLIC_WS_URLbuild-arg + env injection kaldırıldı. frontend-testjob CI'a eklendi (vitest 43 test) — BLOCKING.- 17 backend test eklendi (test_ocpp_charger_create.py + test_schema_model_contract.py — fixture refactor için integration mark'a alındı).
Doküman
[Faz 7] 2026-04-25 — Production Deployment + Release Gate Handoff (PR #272, PR #273)
Eklendi (PR #272)
- Nginx
/ocpp/location bloğu (TLS, WS upgrade, 4h read/send timeout, Authorization passthrough, ayrıocpp.accesslog,set_real_ip_fromprivate RFC1918,proxy_buffering off). - Docker Compose 11 OCPP env x 3 servis (backend + celery-worker + celery-beat) — Faz 4 Celery beat task'ları için passthrough zorunlu.
.env.exampleOCPP production placeholder satırları.- Prometheus rules: 5 alert (
FleetOfflineDrop,AuthFailureSpike,CommandTimeoutHigh,BootstrapSlow,NoActiveSessionsAnomaly). - Grafana dashboard: 10 panel + auto-provisioning (
grafana/provisioning/dashboards/ocpp.yml). - CI integration test infrastructure (Postgres + Redis service container'lar,
alembic upgrade headstep).
Eklendi (PR #273 — release gate handoff)
/healthzminimal endpoint (k8s convention liveness probe, OpenAPI gizli).MQTT_ENABLED+CELERY_ALWAYS_EAGERstartup bypass flag'leri (CI test için MQTT broker bağlantı denemesi yapılmaz).zeus_ocpp_chargers_totalPrometheus Gauge (fleet ratio alarm paydası,tenant_idlabel, cardinality kontrol — charger_id YOK).- Celery beat task
refresh_charger_count(60sn,Gauge.clear()stale tenant cleanup). - 8 yeni test (
backend/tests/features/ocpp/test_health_and_metrics.py). - CI integration test step ENABLE (
if: falsekaldırıldı).
Güvenlik
- 15 maddelik
code-audit-sentinelsecurity audit (11 PASS / 2 PARTIAL / 1 FAIL bcrypt karar — canary öncesi accept). - Nginx Authorization passthrough log leak yok (default
mainlog_format$http_authorizationiçermez — denetlendi). - Prometheus rules PII leak yok (alert annotations'ta charger_id/id_tag yok).
Gauge.clear()stale tenant cleanup — Prometheus cardinality kontrol altında.- Production-readiness skoru: 8.7/10 (PR #272 sonrası) → 9.5/10 (PR #273 sonrası).
- Release gate: CONDITIONAL → APPROVED for canary.
Doküman
[Faz 6] 2026-04-25 — Session Geçmişi + Raporlar + SLD + SmartCharging Editor (PR #270)
Eklendi (Faz 6-A — Session History + Drilldown, ~1 080 satır)
app/chargers/[id]/sessions/page.tsx— sessions list + tarih/status/id_tag filter + offset pagination.SessionTable.tsx— locale-aware tarih/süre, status badge, id_tag son-4 mask.SessionDetailDrawer.tsx— shadcnSheet+ Recharts, measurand/downsample/aggregate selector, TanStack auto-refetch (active session 10sn), session-scoped CSV indirme.
Eklendi (Faz 6-B — Reports + CSV Export, ~900 satır)
- Backend
calculate_charging_report()— 4 group_by (daydate_trunc,charger,id_tag,tariffLEFT JOIN); tenant filter; Wh→kWh ve sec→min dönüşümü SQL içinde. GET /api/reports/charging-sessions(READ_CHARGERpermission,?format=csvvariant).- Frontend
ChargingReport.tsx— DateRangePicker + 2 Select; 4 SummaryCard; Recharts BarChart (energy + sessions çift Y-axis); detay tablo; CSV indirme. - Reports page'e yeni "EV Şarj" Tabs sekmesi.
Eklendi (Faz 6-C — SLD Charger Node + Live Energy Flow, ~570 satır)
- Backend
sld/service.py—device_role='charger'device'lar OcppCharger join + active session subquery + Redis live power lookup. - Frontend
OcppChargerNode.tsx— status renkli pulse (charging→mavi pulse, online→yeşil, offline→gri, faulted→kırmızı, unavailable→sarı), CPID/güç/enerji/connector chip, hover tooltip, click →/chargers/{id}route. DeviceHierarchySldCanvas—useOcppLiveStreamileocpp.meter|status|session_*aboneliği,setNodes+setEdgescanlı güncelleme (200ms throttle).
Eklendi (Faz 6-D — SmartCharging Schedule Editor, ~1 120 satır)
ChargingProfileEditor.tsx— RHF + Zod +useFieldArray. Profile meta (connector_id, stack_level, purpose, kind, recurrency_kind, valid_from/to, transaction_id), schedule meta (charging_rate_unit A/W, duration, start_schedule, min_charging_rate), period listesi (yukarı/aşağı swapmove(index, ±1)).- Cross-field Zod refine:
Recurring→ recurrency_kind zorunlu,TxProfile→ transaction_id zorunlu, periods start_period strictly artan. - Idempotency-Key her submit'te
crypto.randomUUID(). ChargingProfileList.tsx— shadcnAlertDialogonaylı silme (ClearChargingProfile push).- Charger detayda yeni "Şarj Profilleri" tab.
i18n
- 130 yeni key TR + EN parite (sessions 53 + reports 34 + sld 11 + profile 32).
Doküman
[Faz 5] 2026-04-25 — Frontend MVP (PR #267)
Eklendi
- Charger yönetim sayfaları (liste / detay / yeni).
TechnicianKitPanel+PasswordRevealDialog(saha kurulum kit'i tek seferde gösterim).LiveMeterChart(Recharts sliding window — Power/Voltage/Current multi-series).- Beny OCPP Dashboard (device-dashboards registry —
ZJ-Beny:BCP-AT2N-P). - 22 endpoint TanStack hook (
useOcppChargers.ts). useOcppLiveStreamhook'u (chargerId + eventTypes filtreli WS subscribe).- 7 WebSocket event TypeScript discriminated union (
ocpp-events.ts). - TR + EN i18n parite (
locales/{tr,en}/ocpp.json). - A11y:
role="status"/role="alert", keyboard navigation, WCAG AA contrast.
Güvenlik
- Idempotency-Key her mutation'da
crypto.randomUUID()ile otomatik üretilir. - PasswordRevealDialog ESC + outside-click engellenir, onay checkbox zorunlu.
- id_tag son-4 mask UI'da tutarlı.
siteConfig.apiUrl/wsUrlzorunlu (CLAUDE.md §2 build-time bake tuzağı koruması).
Doküman
[Faz 4] 2026-04-25 — Real-time Events + Celery Jobs (PR #266)
Eklendi
- 7 WebSocket event publish helper (
backend/app/core/ocpp/events.py):ocpp.boot,ocpp.status,ocpp.meter,ocpp.session_started,ocpp.session_stopped,ocpp.command_result,ocpp.firmware_status. - 4 Celery beat task (
backend/app/tasks/ocpp_tasks.py):heartbeat_watchdog(60sn),reconcile_charging_profiles(5dk),purge_command_log(günlük 02:00 UTC),sync_firmware_status(saatlik). - 10 publish çağrısı handler entegrasyonu (
zeus_charge_point.py). - Prometheus metric:
ocpp_ws_events_published_total{event_type, tenant_id}.
Hardening
- Best-effort publish pattern (try/except + warning log; OCPP yanıtı kesilmez).
- Lazy import (events.py ↔ service.py circular koruma).
- DetachedInstanceError snapshot (StartTransaction/StopTransaction).
- id_tag son-4 mask backend tarafında uygulandı (KVKK).
- Tenant izolasyonu her event publish'te zorunlu.
MeterValues Downsample
- Whitelist measurand'lar:
Power.Active.Import,Voltage,Current.Import,Energy.Active.Import.Register,SoC. - Whitelist context'ler:
Sample.Periodic,Transaction.Begin,Transaction.End. - Diğer örnekler DB'ye yazılır ama broadcast edilmez.
Doküman
[Faz 3] 2026-04-25 — REST API + Business Logic (PR #265)
Eklendi
- 22 REST endpoint (
backend/app/features/ocpp/router.py):- CRUD:
chargers(5) - Komutlar: 8 (RemoteStart/Stop/Reset/Unlock/ChangeAvailability/ChangeConfig/TriggerMessage/UpdateFirmware)
- Provisioning:
rotate-password,provisioning-kit.pdf(2) - Sessions:
list,detail,meter-samples,export.csv(4) - Charging Profiles: 4
- Local Auth List:
GET,PUT(2) - Reservations:
POST,DELETE(2)
- CRUD:
- 33 Pydantic schema (
backend/app/features/ocpp/schemas.py). - Idempotency middleware (
backend/app/features/ocpp/idempotency.py):Idempotency-Keyheader — 24h Redis cache.- Server-side dedup window — 10sn
(charger_id, action, connector_id, id_tag)tuple.
- ReportLab provisioning kit PDF üretici (password PDF'te yazmaz — sadece URL alanları + checklist).
- 6 yeni Permission:
READ_CHARGER,WRITE_CHARGER,CONTROL_CHARGER,CONFIGURE_CHARGER,OTA_CHARGER,MANAGE_CHARGER_SECRETS. - Canonical error envelope (
error_code,message,details,request_id). - Alarm engine entegrasyonu (Faz 2 TODO closeup).
Karar Verilenler
- URL versioning: yok (
/api/ocpp/); breaking için ileride/api/v2/ocpp/. - HTTP mapping: Rejected→409, NotImplemented→501, Charger offline→503, Timeout→504.
- Pagination: offset-based, default
limit=50, max200. - Time series downsample: PromQL-style (
30s,1m,5m,15m,1h,4h,1d). - Sessions endpoint: flat (nested charger path yok).
- Charging profiles: 4 ayrı endpoint (PUT yok — POST replace semantik).
Doküman
[Faz 1+2] 2026-04-25 — DB Şema + Protokol Çekirdeği (PR #264)
Eklendi
- 8 yeni tablo:
ocpp_chargers,ocpp_connectors,ocpp_sessions,ocpp_meter_samples(TimescaleDB hypertable),ocpp_command_log,ocpp_auth_list_entries,ocpp_charging_profiles,ocpp_reservations.
- WebSocket server endpoint'leri:
/ocpp/1.6/{cpid}(production)./ocpp/2.0.1/{cpid}(stub — handshake1011close).
- python-ocpp
ZeusChargePoint16handler (5 feature profile):- Core (BootNotification, Heartbeat, StatusNotification, Authorize, StartTransaction, StopTransaction, MeterValues, DataTransfer)
- FirmwareManagement (UpdateFirmware, FirmwareStatusNotification, GetDiagnostics, DiagnosticsStatusNotification)
- LocalAuthList (GetLocalListVersion, SendLocalList)
- Reservation (ReserveNow, CancelReservation)
- SmartCharging (SetChargingProfile, ClearChargingProfile, GetCompositeSchedule)
- HTTP Basic Auth + bcrypt (cost ≥ 12) + Redis rate-limit (3 fail / 60sn) + XFF + fail-closed pattern.
- Multi-worker WebSocket registry (Redis pub/sub command dispatcher).
- ZJ-Beny vendor DataTransfer handler (
DLBCloud,DLBStatus,PVStatus,MeterSnapshot). - Boot post-config (10 ChangeConfiguration anahtarı).
- WebSocket close code reference:
1000,1011,4400,4401,4404,4429,4503. - Prometheus metric'leri:
ocpp_messages_total,ocpp_command_latency_seconds,ocpp_command_timeouts_total,ocpp_active_chargers.
Doküman
Roadmap (önümüzdeki fazlar)
Faz 8 — Genişlemeler (planlanıyor)
- OCPP 2.0.1 tam implementation — şu an stub (
/ocpp/2.0.1/{cpid}1011 close). - mTLS / Profile 3 — client certificate provisioning.
- Charging Network Operator (CNO) Federation — eMSP/CPO roaming (OCPI 2.x bridge).
- AI-driven Smart Charging — load forecast + price-aware schedule generation.
- AsyncAPI 2.6 spec — frontend WS event kontrat dökümantasyonu.
- Audit PARTIAL maddelerin kapatılması — Faz 4 Celery log path id_tag mask filter, Redis pool exhaustion alert.
Versiyonlama Politikası
OCPP entegrasyon contract'ı Faz 3 — REST API §14 kararlarına bağlıdır:
- Non-breaking ekler: Yeni endpoint, yeni opsiyonel field, yeni event type, yeni close code (4500+), yeni error code → minor version.
- Breaking değişiklikler: URL format, auth header, CPID regex, mevcut close code anlamı, mevcut field rename/silme, mevcut enum value değişimi → major version + ≥30 gün deprecation notice +
Deprecationheader.
Tüm breaking değişiklikler bu changelog'ta DEPRECATED etiketiyle önceden duyurulur.