[3a] structs protocole + interfaces LoadAdapter/Scheduler (zéro comportement)

LoadAction (kind+funding+§6 fields), LoadDescriptor, SurplusContext (§5),
Plan/Slot, ILoadAdapter, IScheduler — noms de champs = OPTIMIZER_PROTOCOL.md.
energyplugin.pri inclut etm/etm.pri. Build Qt6 vert, aucun fichier upstream touché.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-07 22:02:18 +02:00
parent f4d5b20297
commit 4ae1939f93
8 changed files with 283 additions and 0 deletions

View File

@ -37,6 +37,8 @@ HEADERS += \
$$PWD/types/smartchargingstate.h \
$$PWD/types/timeframe.h \
include($$PWD/etm/etm.pri)
SOURCES += \
$$PWD/energymanagerconfiguration.cpp \
$$PWD/energysettings.cpp \

View File

@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include "../types/loadaction.h"
#include "../types/loaddescriptor.h"
#include "../types/surpluscontext.h"
// Vue runtime minimale qu'un adaptateur expose à l'arbitre.
struct LoadTelemetry {
double currentPowerW = 0;
bool available = true; // faux si l'appareil nymea est absent/erreur
QDateTime lastActionAt;
};
// Interface pure — les implémentations concrètes héritent de QObject + ILoadAdapter.
// Signaux (telemetryChanged, descriptorChanged) déclarés dans les classes concrètes.
class ILoadAdapter {
public:
virtual ~ILoadAdapter() = default;
// Déclaration statique : capacités, limites, priorité, needs.
virtual LoadDescriptor descriptor() const = 0;
// Télémétrie runtime (courant, disponibilité, dernière action).
virtual LoadTelemetry telemetry() const = 0;
// Construit l'entrée §5 loads[] pour SurplusContext.
// Inclut declared, telemetry type-spécifique, learned courant.
virtual LoadContext toLoadContext() const = 0;
// Applique l'action. Retourne ce qui a réellement été appliqué
// (après écrêtage matériel). L'arbitre a déjà écrêté selon les limites
// et le budget — c'est le second filet.
virtual LoadAction applyAction(const LoadAction &action) = 0;
};

7
energyplugin/etm/etm.pri Normal file
View File

@ -0,0 +1,7 @@
HEADERS += \
$$PWD/types/loadaction.h \
$$PWD/types/loaddescriptor.h \
$$PWD/types/surpluscontext.h \
$$PWD/types/plan.h \
$$PWD/adapters/iloadadapter.h \
$$PWD/scheduler/ischeduler.h \

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include "../types/surpluscontext.h"
#include "../types/plan.h"
// Interface pure — implémentations concrètes héritent de QObject + IScheduler.
//
// Règle : getPlan() DOIT retourner immédiatement (modèle cache §AGENTS invariant 5).
// SocketScheduler renvoie son dernier plan en cache et recalcule en fond.
// SocketScheduler embarque un RuleBasedScheduler comme fallback et renvoie
// TOUJOURS un Plan exploitable — l'abstain du protocole n'atteint jamais l'arbitre.
class IScheduler {
public:
virtual ~IScheduler() = default;
virtual Plan getPlan(const SurplusContext &ctx) = 0;
};

View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QString>
// Noms de champs : identiques à OPTIMIZER_PROTOCOL.md §6 (font autorité).
// `funding` est interne à l'arbitre — absent du JSON protocole.
struct LoadAction {
enum Kind { Setpoint, Stage, State, Constraint };
enum Funding { Surplus, Grid }; // interne, non sérialisé
enum Source { Solar, GridSource }; // "solar"|"grid" — Setpoint battery
enum Permission { Allow, Forbid }; // "allow"|"forbid" — Constraint
QString loadId;
Kind kind = Setpoint;
Funding funding = Surplus;
// Setpoint — evcharger
bool chargingEnabled = false;
double currentA = 0;
uint phaseCount = 0;
// Setpoint — battery
double powerW = 0;
Source source = Solar;
// Stage — relay-stages (ECS)
int stage = 0;
// State — sg-ready
int state = 0;
// Constraint — battery v1
Permission charge = Allow;
Permission discharge = Allow;
QString reason; // obligatoire, non vide, français (AGENTS invariant 7)
double estimatedPowerW = 0; // hint pour comptabilité arbitre (rempli par le scheduler)
};

View File

@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include <QList>
#include <QString>
#include "loadaction.h"
// Capacités déclarées par l'installateur (plaque signalétique, câblage).
// Noms de champs : OPTIMIZER_PROTOCOL.md §5 loads[].declared
struct LoadDeclared {
// evcharger
double minA = 0;
double maxA = 0;
int phases = 0;
// relay-stages (ECS) — puissances en W : [0, 1200, 2400]
QList<int> stages;
// battery
double maxChargeW = 0;
double maxDischargeW = 0;
double capacityWh = 0;
int reserveSocPercent = 0;
// sg-ready : états toujours 1-4, pas de déclaration nécessaire
};
// OPTIMIZER_PROTOCOL.md §5 loads[].limits
struct LoadLimits {
int minOnS = 0;
int minOffS = 0;
int chargingEnabledLockS = 0; // evcharger
int currentChangeLockS = 0; // evcharger
int minStateHoldS = 0; // sg-ready
};
// OPTIMIZER_PROTOCOL.md §5 loads[].needs
struct LoadNeeds {
int targetSocPercent = 0;
QDateTime deadline;
QString dailyDeadline; // "18:00"
int minEnergyWhPerDay = 0;
};
// Déclaration statique qu'un ILoadAdapter expose à l'arbitre.
struct LoadDescriptor {
QString id;
QString label;
QString adapter; // "evcharger"|"relay-stages"|"sg-ready"|"battery"
int priority = 0;
LoadDeclared declared;
LoadLimits limits;
LoadNeeds needs;
QList<LoadAction::Kind> supportedKinds;
};

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include <QList>
#include <QString>
#include "loadaction.h"
// OPTIMIZER_PROTOCOL.md §6 (noms de champs identiques).
struct Slot {
QDateTime from;
QDateTime to;
QList<LoadAction> actions; // ordonnées par priorité de LoadDescriptor
};
struct Plan {
QString planId;
QString strategy;
QList<Slot> slots;
// Créneau couvrant dt — créneau vide (from==to==invalid) si aucun.
Slot slotCovering(const QDateTime &dt) const {
for (const Slot &s : slots) {
if (dt >= s.from && dt < s.to)
return s;
}
return {};
}
bool isValid() const { return !slots.isEmpty(); }
};

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
#pragma once
#include <QDateTime>
#include <QList>
#include <QString>
#include "loaddescriptor.h"
// Structures miroirs de OPTIMIZER_PROTOCOL.md §5 (noms de champs identiques).
struct SurplusSite {
double contractedPowerW = 0;
QList<double> phaseLimitA; // [63.0, 63.0, 63.0]
};
struct SurplusMeter {
double importW = 0;
double exportW = 0;
QList<double> perPhaseA;
};
struct SurplusPv {
double currentW = 0;
};
struct SurplusBattery {
bool present = false;
double socPercent = 0;
double powerW = 0;
double capacityWh = 0;
int reserveSocPercent = 0;
double maxChargeW = 0;
double maxDischargeW = 0;
};
struct TariffEntry {
QDateTime from;
QString label;
double priceCtkWh = 0;
};
struct SurplusTariff {
QString provider;
TariffEntry current;
QList<TariffEntry> next;
};
// Télémétrie d'une charge dans le contexte §5 loads[].telemetry
struct LoadContextTelemetry {
double currentPowerW = 0;
// evcharger
bool pluggedIn = false;
bool charging = false;
double sessionWh = 0;
// relay-stages
int stage = 0;
// sg-ready
int state = 0;
// battery / electricvehicle
double socPercent = 0;
QDateTime lastSwitch;
};
// Données apprises par l'optimiseur §8 (renvoyées pour persistance)
struct LoadLearned {
double dailyEnergyWh = 0;
double confidence = 0.0; // 01 ; < 0.7 → "profil en apprentissage"
};
// Entrée loads[] dans le SurplusContext envoyé au scheduler.
// Construit par l'arbitre depuis ILoadAdapter::descriptor() + telemetry().
struct LoadContext {
QString id;
QString adapter; // "evcharger"|"relay-stages"|"sg-ready"|"battery"
QString label;
int priority = 0;
LoadDeclared declared;
LoadLearned learned;
LoadContextTelemetry telemetry;
LoadNeeds needs;
LoadLimits limits;
};
// OPTIMIZER_PROTOCOL.md §5 — transmis au scheduler à chaque cycle.
struct SurplusContext {
QDateTime timestamp;
SurplusSite site;
SurplusMeter meter;
SurplusPv pv;
SurplusBattery battery;
QList<LoadContext> loads;
SurplusTariff tariff;
// forecast : opaque, transmis tel quel si présent — réservé V2
};