test(mock): les deux derniers manques — compteur sans énergie, sonde thermique

`meterNoEnergy` remplace le `powerSwitch` détourné en instrument de mesure. Le
détour marchait — le moteur ne teste que `hasStateType("totalEnergyConsumed")` —
mais il modélisait un montage que personne ne fait, et faisait tenir DEUX rôles à
la même Thing dans le même scénario : relais d'une charge et compteur d'une autre.

La classe ne déclare PAS `energymeter` : cette interface EXIGE `totalEnergyConsumed`
et `totalEnergyProduced`. Promettre une interface qu'on ne tient pas ferait mentir
le mock exactement là où il doit être littéral. `smartmeter` est la base sans état
obligatoire, et c'est précisément ce qu'est ce matériel.

`thermalProbe` ouvre le volet thermique. La branche « sonde présente » de
attachMeasurements() était écrite depuis toujours et n'avait JAMAIS été parcourue :
aucune classe du mock ne portait l'état `temperature`. C'était la seule entrée du
volet thermique, et rien ne l'exerçait.

CE QUE L'OUVERTURE A RÉVÉLÉ, comme les trois précédentes. `setupThing()` du mock
n'avait AUCUNE sortie par défaut : une classe sans branche ne rencontrait jamais
d'`info->finish()`. Le setup ne échouait pas — il ne se terminait jamais. Le
symptôme est un Thing éternellement « en cours de configuration » et un test qui
expire ailleurs, très loin de la cause. Même mécanisme que le contrôleur
déréférencé pour toute classe : une hypothèse du mock que son homogénéité
protégeait. Une classe oubliée se dit maintenant, en qCWarning + SetupFailed.

UNE GARDE A MORDU sur son auteur le jour même. La première version du test
thermique lisait `loads[]` sans compteur racine — donc une liste vide, et trois
assertions qui ne se seraient jamais exécutées. `QCOMPARE(parId.count(), 3)` l'a
attrapé. C'est la parade écrite dans INVENTAIRE_MOCK.md, appliquée à elle-même.

RELEVÉ, NON CORRIGÉ : une sonde DÉSIGNÉE mais incapable produit la même charge
utile qu'aucune sonde du tout (`temperatureC` absent), alors que le geste de
réparation diffère — désigner une sonde, ou en changer. Silence de la famille
`progressUnmeasurableCause` ; le fermer demande un catalogue de causes thermiques
qui n'existe pas encore.

Suites : simulation 67/67 (+1), loadmodel 23/23, charging 48/48, spotmarket 32/32.
This commit is contained in:
Patrick Schurig 2026-09-05 08:52:24 +02:00
parent ae5e8639c4
commit 119cffc745
7 changed files with 371 additions and 10 deletions

View File

@ -62,9 +62,16 @@ ne bouge.
> conclusion tenait, la raison non**, et rien ne pouvait le révéler tant que le déséquilibre était
> infabricable.
**3. `temperature`.** Le code teste `hasState("temperature")` et **aucune classe du mock ne le
porte** : la branche « sonde présente » n'a jamais été exécutée. Elle est écrite, elle n'est pas
testée.
**3. ~~`temperature`.~~ — OUVERT le 2026-09-05.** La classe `thermalProbe` porte l'état, et
`testThermalProbeFeedsTelemetry` exerce enfin la branche « sonde présente » de
`attachMeasurements()`, écrite depuis toujours et **jamais parcourue**. C'était la seule entrée du
volet thermique, et rien ne l'exerçait.
> **Ce qu'il reste à dire, et qui n'est pas corrigé ici** : une sonde DÉSIGNÉE mais incapable —
> un Thing sans état `temperature` — produit exactement la même charge utile qu'aucune sonde du
> tout (`temperatureC` absent). Le geste de réparation, lui, diffère : désigner une sonde, ou en
> changer. C'est un silence de la famille `progressUnmeasurableCause`, relevé par le test et
> laissé ouvert : le fermer demande un catalogue de causes thermiques qui n'existe pas encore.
**4. ~~Une triphasée déséquilibrée.~~ — OUVERT le 2026-09-03.**
`testWorstPhaseBindsWhenPhasesAreUnbalanced` exerce enfin `LM-1210-a` : phase B saturée à 30 A
@ -105,12 +112,35 @@ monotone — le mock fabriquerait l'anomalie que le moteur est censé détecter.
**Quatrième occurrence.** Après les six états cohérents du `rootmeter`, les classes portant toutes
une puissance, et la triphasée équilibrée.
**6. ~~Le compteur sans énergie et la sonde thermique.~~ — OUVERTS le 2026-09-05**, et les deux
derniers de la liste. `meterNoEnergy` remplace le `powerSwitch` détourné en instrument de mesure —
un montage que personne ne fait, et qui faisait tenir DEUX rôles à la même Thing dans le même
scénario. `thermalProbe` ouvre le volet thermique.
> **Et l'ouverture a révélé un défaut, comme les trois précédentes.** `setupThing()` du mock
> n'avait **aucune sortie par défaut** : une classe sans branche ne rencontrait jamais de
> `info->finish()`. Le setup ne échouait pas — **il ne se terminait jamais**. Le symptôme est un
> Thing éternellement « en cours de configuration » et un test qui expire ailleurs, très loin de
> la cause.
>
> C'est le même mécanisme que le contrôleur déréférencé pour toute classe : **une hypothèse du
> mock que son homogénéité protégeait.** Tant que chaque classe avait sa branche, l'absence de
> sortie par défaut ne pouvait pas se voir. Corrigé : une classe oubliée se dit maintenant en
> `qCWarning` et rend `ThingErrorSetupFailed`.
>
> **Et une garde a mordu au passage** : la première version du test thermique lisait `loads[]`
> sans compteur racine — donc une liste vide, et trois assertions qui ne se seraient jamais
> exécutées. `QCOMPARE(parId.count(), 3)` l'a attrapé. C'est la parade écrite plus haut dans ce
> document, appliquée le jour même à son auteur.
## Ce qu'il faudrait ajouter, par ordre d'utilité
1. **Une classe pilotable SANS aucune puissance** — un relais sec. Débloque `source: none` sur
une charge saine, et sépare enfin « non mesurée » de « en panne ».
2. **Un compteur sans `totalEnergyConsumed`** en tant que classe déclarée, plutôt qu'un
`powerSwitch` détourné comme aujourd'hui.
2. ~~**Un compteur sans `totalEnergyConsumed`** en tant que classe déclarée.~~ — **FAIT le
2026-09-05** : `meterNoEnergy`. Elle ne déclare PAS l'interface `energymeter`, qui exige
`totalEnergyConsumed` — promettre une interface qu'on ne tient pas ferait mentir le mock là où
il doit être littéral.
3. **Une tension par phase réglable** indépendamment des puissances — pour exercer le correctif
amont ailleurs qu'en inversant la fonction.

View File

@ -501,6 +501,38 @@ QUuid EnergyTestBase::addDryRelay(quint16 port)
return response.toMap().value("params").toMap().value("thingId").toUuid();
}
QUuid EnergyTestBase::addMeterNoEnergy(quint16 port)
{
QVariantMap portParam;
portParam.insert("paramTypeId", "{eec1f5a2-5c59-4ce9-a5ae-5dc7cad70772}");
portParam.insert("value", port);
QVariantMap params;
params.insert("thingClassId", mockMeterNoEnergyThingClassId.toString());
params.insert("name", QString("Meter without energy %1").arg(port));
params.insert("thingParams", QVariantList() << portParam);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
return response.toMap().value("params").toMap().value("thingId").toUuid();
}
QUuid EnergyTestBase::addThermalProbe(quint16 port)
{
QVariantMap portParam;
portParam.insert("paramTypeId", "{b898397c-c291-47f9-82ec-662250b932e1}");
portParam.insert("value", port);
QVariantMap params;
params.insert("thingClassId", mockThermalProbeThingClassId.toString());
params.insert("name", QString("Thermal probe %1").arg(port));
params.insert("thingParams", QVariantList() << portParam);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
return response.toMap().value("params").toMap().value("thingId").toUuid();
}
QUuid EnergyTestBase::addPowerSwitch(double nominalPower, quint16 port)
{
QVariantList thingParams;

View File

@ -59,6 +59,8 @@ static QUuid mockPowerSwitchThingClassId = QUuid("841f8905-d1d7-4053-909f-01123b
//! puissance, si bien qu'aucun test ne pouvait distinguer « non mesurée » de « en défaut »
//! (docs/INVENTAIRE_MOCK.md).
static QUuid mockDryRelayThingClassId = QUuid("5b2c7e41-9a06-4d3f-b8c1-6e0f2a7d4b39");
static QUuid mockMeterNoEnergyThingClassId = QUuid("50dacc7b-7142-4716-8f59-97d6ee81f39c");
static QUuid mockThermalProbeThingClassId = QUuid("37e61ea3-e4b2-4d3e-bc82-66e350c29f66");
using namespace nymeaserver;
@ -118,6 +120,25 @@ public:
* ce qui traîne `available: false` et mélange deux affirmations distinctes.
*/
QUuid addDryRelay(quint16 port = 26680);
/*!
* \brief Ajoute un COMPTEUR SANS CUMUL D'ÉNERGIE : il publie une puissance, il ne totalise rien.
* \param port Port du contrôleur mock.
* \return L'identifiant du Thing créé.
* \note Remplace le détournement d'un \c powerSwitch en rôle de compteur — un interrupteur
* dans le rôle d'un instrument, et la même Thing tenant deux rôles dans le même scénario.
* C'est le cas `meterWithoutEnergy`, mesuré sur l'ECS du banc le 2026-09-01.
*/
QUuid addMeterNoEnergy(quint16 port = 26671);
/*!
* \brief Ajoute une SONDE DE TEMPÉRATURE — la seule entrée du volet thermique.
* \param port Port du contrôleur mock.
* \return L'identifiant du Thing créé.
* \note Aucune classe du mock ne portait l'état \c temperature : la branche « sonde
* présente » de \c attachMeasurements() était écrite et n'avait jamais été exécutée.
*/
QUuid addThermalProbe(quint16 port = 26681);
//! Mock thing implémentant l'interface etmvariableload (states currentPowerW / powerSetpoint).
QUuid addEtmVariableLoad(quint16 port = 27001);

View File

@ -7419,8 +7419,9 @@ void Simulation::testRecreditIsALoanAgainstObedience()
*
* \par Et `meterThingId` ne permet pas de le déduire
* Un compteur peut publier `currentPower` **sans** `totalEnergyConsumed` : mesuré le 2026-09-01
* sur l'ECS du banc, et reproduit ici avec un `powerSwitch` désigné comme compteur. Un client qui
* déduirait la mesurabilité de la présence du champ se tromperait sur ce cas précis.
* sur l'ECS du banc, et reproduit ici par la classe `meterNoEnergy` — un compteur DÉCLARÉ tel,
* et non plus un `powerSwitch` détourné en instrument de mesure. Un client qui déduirait la
* mesurabilité de la présence du champ se tromperait sur ce cas précis.
*/
void Simulation::testProgressMeasurabilityIsDeclaredAtConfigTime()
{
@ -7436,10 +7437,25 @@ void Simulation::testProgressMeasurabilityIsDeclaredAtConfigTime()
ThingManager *tm = NymeaCore::instance()->thingManager();
QUuid compteur = addMeter(26702); // meter : porte totalEnergyConsumed
QUuid r1 = addPowerSwitch(1000, 26703); // powerSwitch : power + currentPower SEULEMENT
QUuid r1 = addPowerSwitch(1000, 26703);
QUuid r2 = addPowerSwitch(1000, 26704);
QUuid r3 = addPowerSwitch(1000, 26705);
// COMPTEUR SANS CUMUL, classe déclarée — plus un `powerSwitch` détourné en instrument de
// mesure. Le détour marchait (le moteur ne teste que `hasStateType`), mais il modélisait un
// montage que personne ne fait, et faisait tenir DEUX rôles à la même Thing dans le même
// scénario : relais de « sans-compteur » et compteur de « compteur-sans-energie ».
QUuid compteurSansEnergie = addMeterNoEnergy(26706);
QVERIFY(tm->findConfiguredThing(compteur) && tm->findConfiguredThing(r1));
QVERIFY2(tm->findConfiguredThing(compteurSansEnergie),
"la classe meterNoEnergy doit être configurable — sinon la branche par défaut du "
"mock a parlé, et c'est elle qu'il faut lire");
// Le contrat de la classe, vérifié plutôt que supposé : elle publie une puissance et ne
// totalise RIEN. Si un jour quelqu'un lui ajoutait le cumul, ce test-ci se tairait et le
// cas `meterWithoutEnergy` redeviendrait infabricable sans que rien ne le signale.
Thing *tSans = tm->findConfiguredThing(compteurSansEnergie);
QVERIFY2(tSans->thingClass().hasStateType("currentPower"), "un compteur publie une puissance");
QVERIFY2(!tSans->thingClass().hasStateType("totalEnergyConsumed"),
"meterNoEnergy ne doit PAS totaliser — c'est toute sa raison d'être");
// Écrit par la FRONTIÈRE RPC, comme l'app le ferait — c'est le magasin que le handler
// connaît. Un store créé à côté ne serait pas celui qu'il interroge : constaté ici même,
@ -7457,7 +7473,7 @@ void Simulation::testProgressMeasurabilityIsDeclaredAtConfigTime()
};
aPoser << relais("sans-compteur", r1, QString());
aPoser << relais("compteur-ok", r2, compteur.toString());
aPoser << relais("compteur-sans-energie", r3, r1.toString());
aPoser << relais("compteur-sans-energie", r3, compteurSansEnergie.toString());
QCOMPARE(injectAndWait("NymeaEnergy.SetLoadConfig",
QVariantMap({{"loadConfigs", aPoser}})).toMap()
.value("params").toMap().value("energyError").toString(),
@ -7493,6 +7509,118 @@ void Simulation::testProgressMeasurabilityIsDeclaredAtConfigTime()
#endif
}
/*!
* \brief [Inventaire du mock] La branche THERMIQUE, écrite depuis toujours et jamais exécutée.
*
* \par Le manque
* `attachMeasurements()` teste `hasState("temperature")` sur le Thing désigné par
* `sensorThingId`. **Aucune classe du mock ne portait cet état** : la branche « sonde présente »
* n'avait jamais été parcourue, et rien n'exerçait la seule entrée du volet thermique.
*
* \par Les trois cas, et le troisième est celui qu'on n'aurait pas pensé à écrire
* Aucune sonde désignée, une sonde qui mesure, et une sonde DÉSIGNÉE QUI NE MESURE PAS — un
* Thing existant sans état `temperature`. Le troisième produit la même charge utile que le
* premier (`temperatureC` absent), alors que le geste de réparation n'est pas le même : dans un
* cas il faut désigner une sonde, dans l'autre en changer.
*/
void Simulation::testThermalProbeFeedsTelemetry()
{
#ifndef ETM_ARBITRATOR
QSKIP("nécessite ETM_ARBITRATOR.");
#else
const QString cfgPath = QDir::tempPath() + "/etm-loadcfg-thermo.json";
QFile::remove(cfgPath);
qputenv("NYMEA_ENERGY_LOAD_CONFIG", cfgPath.toUtf8());
cleanupTestCase();
m_energyLogDbFilePath = ":/databases/2022-06-22-energylogs.sqlite";
initTestCase();
EnergyArbitrator *arb = dynamic_cast<EnergyArbitrator *>(m_experiencePlugin->smartChargingManager());
QVERIFY(arb);
ThingManager *tm = NymeaCore::instance()->thingManager();
// Sans compteur racine, AUCUN cycle d'arbitrage ne tourne et `loads[]` reste vide : la
// garde d'assertion vacante ci-dessous l'a attrapé à la première exécution de ce test.
QUuid meterId = addMeter(26710);
m_experiencePlugin->energyManager()->setRootMeter(meterId);
Thing *meter = tm->findConfiguredThing(meterId);
QVERIFY(meter);
meter->setStateValue("connected", true);
QUuid r1 = addPowerSwitch(1000, 26711);
QUuid r2 = addPowerSwitch(1000, 26712);
QUuid r3 = addPowerSwitch(1000, 26713);
QUuid sonde = addThermalProbe(26714);
QVERIFY2(tm->findConfiguredThing(sonde), "la classe thermalProbe doit être configurable");
Thing *tSonde = tm->findConfiguredThing(sonde);
QVERIFY2(tSonde->thingClass().hasStateType("temperature"),
"sans cet état, la branche testée reste inatteignable et le test serait VACANT");
tSonde->setStateValue("temperature", 54.5);
int rang = 0;
QVariantList aPoser;
auto charge = [&](const QString &id, const QUuid &relais, const QString &capteur) {
++rang;
QVariantMap m{{"id", id}, {"label", id}, {"adapter", "relay-router"}, {"mode", "fixed"},
{"priority", rang}, {"enabled", true}, {"domain", "ecs"},
{"relays", QVariantList() << QVariantMap{{"thingId", relais.toString()},
{"powerW", 1000}}}};
if (!capteur.isEmpty()) m.insert("sensorThingId", capteur);
return m;
};
aPoser << charge("sans-sonde", r1, QString());
aPoser << charge("avec-sonde", r2, sonde.toString());
// Une sonde DÉSIGNÉE qui ne mesure pas la température : un relais, ici. Le cas existe sur le
// terrain dès qu'on désigne le mauvais Thing, et il ne se distingue pas du premier.
aPoser << charge("sonde-muette", r3, r1.toString());
QCOMPARE(injectAndWait("NymeaEnergy.SetLoadConfig",
QVariantMap({{"loadConfigs", aPoser}})).toMap()
.value("params").toMap().value("energyError").toString(),
QString("EnergyErrorNoError"));
// Cycle DÉCLENCHÉ par l'horloge de simulation, jamais attendu de l'horloge murale.
const QDateTime t0 = utcDateTime(QDate(2026, 9, 5), QTime(13, 0, 0));
meter->setStateValue("currentPower", -6000); // 6000 W exportés : de quoi servir
arb->simulationCallUpdate(t0);
QCoreApplication::processEvents();
QVariantList loads = injectAndWait("NymeaEnergy.GetLoadTelemetry").toMap()
.value("params").toMap().value("loads").toList();
QHash<QString, QVariantMap> parId;
for (const QVariant &v : loads) parId.insert(v.toMap().value("loadId").toString(), v.toMap());
// Garde contre l'assertion VACANTE — sans les trois charges, tout ce qui suit passerait
// sans rien vérifier.
QCOMPARE(parId.count(), 3);
// 1. SONDE QUI MESURE — la branche qui n'avait jamais été exécutée.
QVERIFY2(parId.value("avec-sonde").contains("temperatureC"),
"une sonde désignée ET capable doit publier sa lecture");
QCOMPARE(parId.value("avec-sonde").value("temperatureC").toDouble(), 54.5);
// 2. AUCUNE SONDE — le champ est ABSENT, jamais zéro. Un 0 se lirait « il fait 0 °C ».
QVERIFY2(!parId.value("sans-sonde").contains("temperatureC"),
"sans sonde, le champ est absent — un zéro serait une mesure");
// 3. SONDE DÉSIGNÉE MAIS MUETTE — même charge utile que le cas 2, geste de réparation
// DIFFÉRENT. C'est un silence de la famille `progressUnmeasurableCause`, relevé ici et
// non corrigé : le corriger demande un catalogue de causes thermiques qui n'existe pas.
QVERIFY2(!parId.value("sonde-muette").contains("temperatureC"),
"une sonde incapable ne publie rien — mais rien ne dit POURQUOI");
// 4. La lecture SUIT la sonde : une valeur figée passerait les trois assertions ci-dessus.
tSonde->setStateValue("temperature", 61.25);
arb->simulationCallUpdate(t0.addSecs(60));
QCoreApplication::processEvents();
loads = injectAndWait("NymeaEnergy.GetLoadTelemetry").toMap()
.value("params").toMap().value("loads").toList();
for (const QVariant &v : loads)
if (v.toMap().value("loadId").toString() == "avec-sonde")
QCOMPARE(v.toMap().value("temperatureC").toDouble(), 61.25);
#endif
}
/*!
* \brief [Inventaire du mock] « Saine mais NON MESURÉE » n'est pas « en défaut ».
*

View File

@ -199,6 +199,7 @@ private slots:
void testEcoFloorMissedSaysWhatWasLostAndWhy();
void testCommandDivergenceIsPublishedNotDiagnosed();
void testProgressMeasurabilityIsDeclaredAtConfigTime();
void testThermalProbeFeedsTelemetry();
void testHealthyButUnmeasuredDiffersFromFaulty();
void testLifetimeCounterDropNeverRebasesTheProgress();
void testWorstPhaseBindsWhenPhasesAreUnbalanced();

View File

@ -487,7 +487,72 @@ void IntegrationPluginEnergyMocks::setupThing(ThingSetupInfo *info)
qCDebug(dcEnergyMocks()) << "Setting up etmVariableLoad" << thing->name() << "finished successfully";
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == meterNoEnergyThingClassId) {
// COMPTEUR SANS CUMUL D'ÉNERGIE — il publie une puissance, il ne totalise rien.
//
// Le cas était jusqu'ici fabriqué en désignant un `powerSwitch` comme compteur : un
// INTERRUPTEUR dans le rôle d'un instrument de mesure, et de surcroît la même Thing
// jouant deux rôles dans le même scénario. Le moteur ne teste que
// `hasStateType("totalEnergyConsumed")`, donc le détour marchait — mais il modélisait
// un montage que personne ne fait, et il empêchait de voir ce que le vrai cas produit.
//
// La classe ne déclare PAS `energymeter` : cette interface EXIGE `totalEnergyConsumed`
// et `totalEnergyProduced`. Promettre une interface qu'on ne tient pas ferait mentir le
// mock exactement là où il doit être littéral. `smartmeter` est la base sans état
// obligatoire, et c'est précisément ce qu'est ce matériel — mesuré sur l'ECS du banc
// le 2026-09-01.
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){
for (const QString &etat : {QStringLiteral("currentPower"),
QStringLiteral("voltagePhaseA"),
QStringLiteral("currentPhaseA")}) {
if (query.hasQueryItem(etat))
thing->setStateValue(etat, QVariant(query.queryItemValue(etat)).toDouble());
}
});
m_controllers.insert(thing, controller);
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == thermalProbeThingClassId) {
// SONDE DE TEMPÉRATURE — la seule entrée du volet thermique, et rien ne l'exerçait.
// `EnergyArbitrator::attachMeasurements()` teste `hasState("temperature")` sur le Thing
// désigné par `sensorThingId` : aucune classe du mock ne portait cet état, donc la
// branche « sonde présente » était écrite et n'avait JAMAIS été exécutée.
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){
if (query.hasQueryItem("temperature"))
thing->setStateValue("temperature", QVariant(query.queryItemValue("temperature")).toDouble());
});
m_controllers.insert(thing, controller);
info->finish(Thing::ThingErrorNoError);
return;
}
// SORTIE PAR DÉFAUT, et elle manquait. Une classe sans branche ne tombait sur AUCUN
// `info->finish()` : le setup ne échouait pas, il ne se terminait jamais. Le symptôme est
// un Thing éternellement « en cours de configuration » et un test qui expire ailleurs, très
// loin de la cause. Une classe oubliée doit se dire, pas se taire.
qCWarning(dcEnergyMocks()) << "Aucune branche de setup pour la classe"
<< thing->thingClass().name() << "— Thing non configurable.";
info->finish(Thing::ThingErrorSetupFailed);
}
void IntegrationPluginEnergyMocks::thingRemoved(Thing *thing)

View File

@ -1127,8 +1127,92 @@
"writable": true
}
]
},
{
"name": "meterNoEnergy",
"displayName": "Mocked Meter (puissance SEULE, sans cumul d'énergie)",
"id": "50dacc7b-7142-4716-8f59-97d6ee81f39c",
"createMethods": [
"user"
],
"interfaces": [
"smartmeter"
],
"paramTypes": [
{
"id": "eec1f5a2-5c59-4ce9-a5ae-5dc7cad70772",
"name": "port",
"displayName": "Port",
"type": "uint",
"defaultValue": 26671
}
],
"stateTypes": [
{
"id": "354ab572-b272-4ff0-b99c-51791543a3df",
"name": "currentPower",
"displayName": "Current power",
"displayNameAction": "Set current power",
"type": "double",
"unit": "Watt",
"defaultValue": 0,
"writable": true
},
{
"id": "7a3eabcc-d124-40b2-b801-f51767400d4c",
"name": "voltagePhaseA",
"displayName": "Voltage phase A",
"displayNameAction": "Set voltage phase A",
"type": "double",
"unit": "Volt",
"defaultValue": 230,
"writable": true
},
{
"id": "4fea5d43-5d92-49f5-b045-46f471d76b00",
"name": "currentPhaseA",
"displayName": "Current phase A",
"displayNameAction": "Set current phase A",
"type": "double",
"unit": "Ampere",
"defaultValue": 0,
"writable": true
}
]
},
{
"name": "thermalProbe",
"displayName": "Mocked Thermal Probe (sonde de température)",
"id": "37e61ea3-e4b2-4d3e-bc82-66e350c29f66",
"createMethods": [
"user"
],
"interfaces": [
"temperaturesensor"
],
"paramTypes": [
{
"id": "b898397c-c291-47f9-82ec-662250b932e1",
"name": "port",
"displayName": "Port",
"type": "uint",
"defaultValue": 26681
}
],
"stateTypes": [
{
"id": "098b3a75-59dd-4cd5-8595-9cd66fedfd84",
"name": "temperature",
"displayName": "Temperature",
"displayNameAction": "Set temperature",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 20.0,
"writable": true
}
]
}
]
}
]
}
}