[3c-7a] mock powerswitch + helpers energytestbase

ThingClass mockPowerSwitch (interface power : power bool writable + currentPower
double, params port/nominalPower) pour piloter l'EcsRelayAdapter en test.
setupThing + executeAction (power → état + currentPower dérivé du nominal, override
HTTP possible). energytestbase : mockPowerSwitchThingClassId + addPowerSwitch()
+ setPowerSwitchStates(). Cascade N paliers = N instances (matériel réel).
Build mock (-Werror) + binaire de test : 0 erreur / 0 warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-09 18:11:01 +02:00
parent f71e0405b4
commit a471a23aeb
4 changed files with 129 additions and 0 deletions

View File

@ -257,6 +257,25 @@ QNetworkReply *EnergyTestBase::setEnergyStorageStates(uint batteryLevel, int cur
return m_networkAccessManager->post(request, QByteArray());
}
QNetworkReply *EnergyTestBase::setPowerSwitchStates(bool power, double currentPower, quint16 port)
{
QUrl requestUrl;
requestUrl.setScheme("http");
requestUrl.setHost("127.0.0.1");
requestUrl.setPort(port);
requestUrl.setPath("/setstates");
QUrlQuery query;
query.addQueryItem("power", power ? "true" : "false");
query.addQueryItem("currentPower", QString::number(currentPower));
requestUrl.setQuery(query);
QNetworkRequest request(requestUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
return m_networkAccessManager->post(request, QByteArray());
}
QNetworkReply *EnergyTestBase::getActionHistory(quint16 port)
{
QUrl requestUrl;
@ -438,6 +457,31 @@ QUuid EnergyTestBase::addEnergyStorage(uint capacity, double maxChargingPowerUpp
return response.toMap().value("params").toMap().value("thingId").toUuid();
}
QUuid EnergyTestBase::addPowerSwitch(double nominalPower, quint16 port)
{
QVariantList thingParams;
QVariantMap portParam;
portParam.insert("paramTypeId", "{e3398429-45fd-4add-a789-4d11bfd9560f}");
portParam.insert("value", port);
QVariantMap nominalPowerParam;
nominalPowerParam.insert("paramTypeId", "{b850a4d1-af0f-477d-ac73-56071f371884}");
nominalPowerParam.insert("value", nominalPower);
thingParams.append(portParam);
thingParams.append(nominalPowerParam);
QVariantMap params;
params.insert("thingClassId", mockPowerSwitchThingClassId.toString());
// Nom unique par port : plusieurs relais (paliers ECS) peuvent coexister.
params.insert("name", QString("Power switch %1").arg(port));
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
return response.toMap().value("params").toMap().value("thingId").toUuid();
}
void EnergyTestBase::removeDevices()
{
QVariant configuredDevices = injectAndWait("Integrations.GetThings");

View File

@ -48,6 +48,7 @@ static QUuid mockChargerWithPhaseSwitchingThingClassId = QUuid("9208d9f0-280c-46
static QUuid mockSimpleChargerThingClassId = QUuid("29bcf255-b654-4764-be92-399bc26fe7c3");
static QUuid mockCarThingClassId = QUuid("4513f801-836e-40a7-8784-c02650a9bdc6");
static QUuid mockEnergyStorageThingClassId = QUuid("d0d5bbf0-249c-46ed-ac6a-5f271b2b0b0f");
static QUuid mockPowerSwitchThingClassId = QUuid("841f8905-d1d7-4053-909f-01123b497747");
using namespace nymeaserver;
@ -81,6 +82,7 @@ public:
QNetworkReply *setChargerWithPhaseCountSwitchingStates(bool connected, bool power, bool pluggedIn, const QString &phases, int maxChargingCurrent, int maxChargingCurrentMaxValue, uint desiredPhaseCount, quint16 port = 26658);
QNetworkReply *setSimpleChargerStates(bool connected, bool power, bool pluggedIn, int phaseCount, int maxChargingCurrent, quint16 port = 26659);
QNetworkReply *setEnergyStorageStates(uint batteryLevel, int currentPower, quint16 port = 26660);
QNetworkReply *setPowerSwitchStates(bool power, double currentPower, quint16 port = 26661);
QNetworkReply *getActionHistory(quint16 port);
QNetworkReply *clearActionHistroy(quint16 port);
@ -91,6 +93,7 @@ public:
QUuid addChargerWithPhaseCountSwitching(const QString &phases = "All", double maxChargingCurrentUpperLimit = 32, quint16 port = 26658);
QUuid addSimpleCharger(double maxChargingCurrentUpperLimit = 32, quint16 port = 26659);
QUuid addEnergyStorage(uint capacity = 10, double maxChargingPowerUpperLimit = 5000, double maxDischargingPowerUpperLimit = 11500, quint16 port = 26660);
QUuid addPowerSwitch(double nominalPower = 2000, quint16 port = 26661);
void removeDevices();
QVariant removeDevice(const QUuid &thingId);
@ -104,6 +107,7 @@ protected:
quint16 m_mockChargerWithPhaseCountSwitchingDefaultPort = 26658;
quint16 m_mockSimpleChargerDefaultPort = 26659;
quint16 m_mockEnergyStorageDefaultPort = 26660;
quint16 m_mockPowerSwitchDefaultPort = 26661;
bool verifyActionExecuted(const QVariantList &actionHistory, const QString &actionName);
QVariant getLastValueFromExecutedAction(const QVariantList &actionHistory, const QString &actionName, const QString &paramName);

View File

@ -358,6 +358,31 @@ void IntegrationPluginEnergyMocks::setupThing(ThingSetupInfo *info)
thing->setStateValue("capacity", thing->paramValue(energyStorageThingCapacityParamTypeId));
return;
} else if (thing->thingClassId() == mockPowerSwitchThingClassId) {
EnergyMockController *controller = new EnergyMockController(thing, this);
ParamType paramType = thing->thingClass().paramTypes().findByName("port");
quint16 port = thing->paramValue(paramType.id()).toUInt();
if (!controller->listen(QHostAddress::Any, port)) {
qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString();
delete controller;
info->finish(Thing::ThingErrorThingInUse);
return;
}
connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){
// Permet au test d'imposer power / currentPower directement (ex. émuler une
// mesure dérivée, ou un thermostat coupé : power=true mais currentPower=0).
if (query.hasQueryItem("power"))
thing->setStateValue("power", QVariant(query.queryItemValue("power")).toBool());
if (query.hasQueryItem("currentPower"))
thing->setStateValue("currentPower", QVariant(query.queryItemValue("currentPower")).toDouble());
});
m_controllers.insert(thing, controller);
qCDebug(dcEnergyMocks()) << "Setting up power switch" << thing->name() << "finished successfully";
info->finish(Thing::ThingErrorNoError);
return;
}
}
@ -474,6 +499,19 @@ void IntegrationPluginEnergyMocks::executeAction(ThingActionInfo *info)
}
}
if (thing->thingClassId() == mockPowerSwitchThingClassId) {
if (actionType.name() == "power") {
bool power = action.paramValue(actionType.paramTypes().findByName("power").id()).toBool();
double nominal = thing->paramValue(thing->thingClass().paramTypes().findByName("nominalPower").id()).toDouble();
// Relais ON → consomme sa puissance nominale ; OFF → 0 W. Le test peut écraser
// currentPower via /setstates (ex. émuler une mesure dérivée).
thing->setStateValue("power", power);
thing->setStateValue("currentPower", power ? nominal : 0.0);
qCDebug(dcEnergyMocks()) << "Mock power switch" << thing->name() << "power" << power
<< "currentPower" << thing->stateValue("currentPower");
}
}
info->finish(Thing::ThingErrorNoError);
}

View File

@ -816,6 +816,49 @@
"defaultValue": false
}
]
},
{
"name": "mockPowerSwitch",
"displayName": "Mocked Power Switch (relais ECS)",
"id": "841f8905-d1d7-4053-909f-01123b497747",
"createMethods": ["user"],
"interfaces": ["power"],
"paramTypes": [
{
"id": "e3398429-45fd-4add-a789-4d11bfd9560f",
"name": "port",
"displayName": "Port",
"type": "uint",
"defaultValue": 26661
},
{
"id": "b850a4d1-af0f-477d-ac73-56071f371884",
"name": "nominalPower",
"displayName": "Nominal power when ON",
"type": "double",
"unit": "Watt",
"defaultValue": 2000
}
],
"stateTypes": [
{
"id": "9fa6457c-6adb-4d4a-8d47-7bdb2db2c271",
"name": "power",
"displayName": "Power",
"displayNameAction": "Switch power",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "0e7e6cd5-601b-4616-8bc9-191c10e9dac7",
"name": "currentPower",
"displayName": "Current power",
"type": "double",
"unit": "Watt",
"defaultValue": 0
}
]
}
]
}