etm-powersync-app/test/fixtures/schema_hems75_etm15.json
Patrick Schurig 2a8ad95120 feat(config): lot C — un seul écran de configuration, et le grisage vient du schéma
FUSION. L'écran « Ordre de service » est supprimé ; sa route redirige. C'est son CHEMIN DE
DONNÉES (LoadConfigProvider) qui a migré dans la présentation de « Rôles & appareils »,
jamais l'inverse. EmsRole fige six rôles dont dhw ET heatPump séparés — or LM-201 dit qu'une
PAC sans ballon séparé est UNE charge : le modèle des rôles aurait exigé deux charges sur un
canal unique, ce que validateSet() refuse à raison. Les charges viennent donc de
GetLoadConfig. Les compteurs restent sur EnergySetupProvider (autre contrat, SetRootMeter).

La note « le pilote peut bousculer l'ordre les jours Tempo rouge ou à l'approche d'une
échéance » est retirée : aucun de ces comportements n'existe dans le moteur.

ANNONCE AVANT ÉCRITURE. Miroir exact de EnergyArbitrator::sameHardware(). Le piège, vérifié
sur .75 : la comparaison des relais est INDEXÉE, donc réordonner relays[] sans rien changer
d'autre reconstruit l'adaptateur — contacts ouverts (applySafeState, force) et verrous
réarmés à froid. Un glisser-déposer dans la liste des contacteurs est un geste matériel.
L'écran le nomme, avec la durée de réarmement.

GRISAGE PAR INTROSPECTION. RpcSchema lit JSONRPC.Introspect à la connexion ; trois états et
non deux — écrivable, r: (mesure, pas fonction manquante), absent — plus « inconnu » quand le
schéma n'a pas pu être lu. Chaque champ grisé porte sa raison et le nom du champ manquant.
La version affichée est celle de l'API d'expérience (NymeaEnergy 0.8) : le paquet +etm15
n'est exposé par aucune méthode.

CORRIGÉ AU PASSAGE — les modes de recharge étaient INOPÉRANTS. setChargingInfo() appelait
EnergyPlugin.SetChargingInfo (namespace inexistant) avec mode/targetSoc/endTime/minCurrent
au lieu de chargingMode/targetPercentage/endDateTime, et minCurrent n'existe dans aucun
champ. Vérifié par appel réel : « Missing required key: chargingMode ». Le refus était de
plus avalé et l'état local mis à jour quand même. Désormais l'état ne suit que si la box
accepte, et le refus remonte sans en inventer la cause.

ZONES — constaté, pas corrigé. L'hypothèse « champs grisés » est fausse : AirConditioning
publie 8 méthodes toutes en écriture. ac_screen.dart n'en appelle aucune, ses 4 pièces sont
des constantes de maquette, et .75 déclare zéro zone. getZones() ajouté + bandeau qui lit le
vrai compte et annonce la maquette. Le câblage est un lot en soi, non exerçable sans zone.

Tests 80/80, analyze 0 erreur. Relevé : docs/RELEVE_LOTC.md — dont §6, ce qui n'a PAS pu
être vérifié : l'IU n'a pas pu être pilotée sur l'appareil, MIUI refusant INJECT_EVENTS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
2026-08-25 22:17:16 +02:00

678 lines
21 KiB
JSON

{
"_source": "JSONRPC.Introspect sur 192.168.1.75, 2026-08-25 — plugin 1.15.2+etm15 (lot B-bis).",
"_note": "Tranche : methodes NymeaEnergy.* et AirConditioning.*, types et enums utiles. Les prefixes portent le sens : 'o:' optionnel-ecrivable, 'r:' lecture seule. La version du PAQUET (+etm15) n'est exposee par aucune methode ; JSONRPC.Hello rend la version nymea et les versions d'API d'experience (NymeaEnergy 0.8).",
"_hello_experiences": {
"NymeaEnergy": "0.8",
"AirConditioning": "1.1",
"Energy": "1.0"
},
"_hello_version": "1.15.2+202606191336~trixie1",
"methods": {
"AirConditioning.AddZone": {
"description": "Create a zones.",
"params": {
"name": "String",
"o:indoorSensors": [
"Uuid"
],
"o:notifications": [
"Uuid"
],
"o:outdoorSensors": [
"Uuid"
],
"o:thermostats": [
"Uuid"
],
"o:valves": [
"Uuid"
],
"o:windowSensors": [
"Uuid"
]
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"airConditioningError": "$ref:AirConditioningError",
"o:zone": "$ref:ZoneInfo"
}
},
"AirConditioning.GetZones": {
"description": "Get all Zones.",
"params": {
"o:zoneId": "Uuid"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"airConditioningError": "$ref:AirConditioningError",
"zones": "$ref:ZoneInfos"
}
},
"AirConditioning.RemoveZone": {
"description": "Remove a zone.",
"params": {
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"AirConditioning.SetZoneName": {
"description": "Set zone name.",
"params": {
"name": "String",
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"AirConditioning.SetZoneSetpointOverride": {
"description": "Set zone setpoint override temperature. Parameter minutes gives the minutes until the setpoint should return to the standby/schedule.",
"params": {
"mode": "$ref:SetpointOverrideMode",
"o:minutes": "Int",
"setpointOverride": "Double",
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"AirConditioning.SetZoneStandbySetpoint": {
"description": "Set zone standby setpoint temperature.",
"params": {
"standbySetpoint": "Double",
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"AirConditioning.SetZoneThings": {
"description": "Set Zone things",
"params": {
"o:indoorSensors": [
"Uuid"
],
"o:notifications": [
"Uuid"
],
"o:outdoorSensors": [
"Uuid"
],
"o:thermostats": [
"Uuid"
],
"o:valves": [
"Uuid"
],
"o:windowSensors": [
"Uuid"
],
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"AirConditioning.SetZoneWeekSchedule": {
"description": "Set the time schedule for a zone.",
"params": {
"weekSchedule": "$ref:TemperatureWeekSchedule",
"zoneId": "Uuid"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"airConditioningError": "$ref:AirConditioningError"
}
},
"NymeaEnergy.ClearLoadFault": {
"description": "Clear the fault latch of a controllable load (ECS-410). Deliberate operator action: the fault never clears by itself.",
"params": {
"loadId": "String"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.GetAcquisitionTolerance": {
"description": "Get the acquisition tolerance. This value will control how much solar surplus is required to start the charging.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"acquisitionTolerance": "Double"
}
},
"NymeaEnergy.GetAvailableSpotMarketProviders": {
"description": "Get the list of available spot market data providers.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"providers": [
"$ref:SpotMarketProviderInfo"
]
}
},
"NymeaEnergy.GetBatteryLevelConsideration": {
"description": "Get the battery level consideration. This value will control how much the energy storage is taking in account",
"params": {},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"batteryLevelConsideration": "Double"
}
},
"NymeaEnergy.GetChargingInfos": {
"description": "Get the charging info for all or a single EV charger",
"params": {
"o:evChargerId": "Uuid"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"chargingInfos": [
"$ref:ChargingInfo"
]
}
},
"NymeaEnergy.GetChargingSchedules": {
"description": "Get the planed charging schedules, and whether the L2 degraded mode is currently active (meter silent > 90s, planning suspended).",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"chargingSchedules": [
"$ref:ChargingSchedule"
],
"degradedMode": "Bool"
}
},
"NymeaEnergy.GetEnergyRatios": {
"description": "Get the current energy ratios (self-consumption / autonomy) in percent, derived from the energy manager's cumulative counters over the current local day. A rate field is omitted (never null) when not available (denominator <= 0).",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"o:autonomyRate": "Double",
"o:selfConsumptionRate": "Double"
}
},
"NymeaEnergy.GetLoadConfig": {
"description": "Get the configured controllable loads (etmvariableload).",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"loadConfigs": [
"$ref:LoadConfig"
]
}
},
"NymeaEnergy.GetLoadTelemetry": {
"description": "Get a snapshot of the arbitration runtime state: surplus budget, per-load allocation, availability, fault code, active lock (seconds remaining), mechanism payload and decision reason. Reason and fault are CODES with numeric params - never human-readable sentences: the client renders them in the user's language. Identical payload to the LoadTelemetryChanged notification. 'timestamp' is the end of the last arbitration cycle and is omitted while no cycle has run yet.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"degradedMode": "Bool",
"loads": [
{
"allocatedW": "Double",
"available": "Bool",
"decision": {
"code": "String",
"params": "Variant"
},
"loadId": "String",
"o:faultCode": "String",
"o:lock": {
"kind": "String",
"remainingS": "Int"
},
"o:mechanism": {
"kind": "String",
"o:chargingEnabled": "Bool",
"o:currentA": "Double",
"o:estimatedPowerW": "Double",
"o:maxPowerW": "Double",
"o:maxStageW": "Double",
"o:percent": "Double",
"o:phaseCount": "Uint",
"o:pluggedIn": "Bool",
"o:setpointW": "Double",
"o:stageW": "Double",
"o:state": "Int"
}
}
],
"o:budget": {
"allocatedW": "Double",
"evReservedW": "Double",
"recreditedW": "Double",
"remainingW": "Double",
"surplusW": "Double"
},
"o:timestamp": "String"
}
},
"NymeaEnergy.GetLockOnUnplug": {
"description": "Get the lock on unplug setting.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"lockOnUnplug": "Bool"
}
},
"NymeaEnergy.GetPhasePowerLimit": {
"description": "Get the phase power consumption limit. 0 if unset. This needs to be set in order for the smart charging to do anything.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"phasePowerLimit": "Uint"
}
},
"NymeaEnergy.GetSpotMarketConfiguration": {
"description": "Get the current spotmarket configuration.",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"available": "Bool",
"enabled": "Bool",
"o:providerId": "Uuid"
}
},
"NymeaEnergy.GetSpotMarketScoreEntries": {
"description": "Get the current score entries from the current spot market. If spot market is not enabled or not available, the list will be empty. The weighting of the available score entries goes from 0 (worst) to 1.0 (best).",
"params": {},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"spotMarketScoreEntries": [
"$ref:ScoreEntry"
]
}
},
"NymeaEnergy.SetAcquisitionTolerance": {
"description": "Set the acquisition tolerance for the charging.",
"params": {
"acquisitionTolerance": "Double"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetBatteryLevelConsideration": {
"description": "Set the battery level consideration for the charging.",
"params": {
"batteryLevelConsideration": "Double"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetChargingInfo": {
"description": "Set the charging info for an EV charger. Only given properties will be set, others will be untouched.",
"params": {
"chargingInfo": "$ref:ChargingInfo"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetLoadConfig": {
"description": "Replace the full set of controllable load configs. Rejected as a whole (energyError) if any entry is invalid; persisted and applied atomically.",
"params": {
"loadConfigs": [
{
"adapter": "String",
"enabled": "Bool",
"id": "String",
"label": "String",
"mode": "String",
"o:domain": "String",
"o:maxPowerW": "Uint",
"o:minOffS": "Uint",
"o:minOnS": "Uint",
"o:needs": {
"o:dailyDeadline": "String",
"o:minEnergyWhPerDay": "Uint"
},
"o:powerLevels": [
"Uint"
],
"o:relays": [
{
"powerW": "Uint",
"thingId": "String"
}
],
"o:sgReady": {
"o:minStateHoldS": "Uint",
"o:states": [
{
"estimatedPowerW": "Double",
"relays": [
"String"
],
"state": "Uint"
}
]
},
"priority": "Uint"
}
]
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetLockOnUnplug": {
"description": "Set the lock on unplug setting.",
"params": {
"lockOnUnplug": "Bool"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetPhasePowerLimit": {
"description": "Set the phase power consumption limit. This is the maximum allowed Ampere per phase. A value of 0 means unset. All smart charging will be disabled.",
"params": {
"phasePowerLimit": "Uint"
},
"permissionScope": "PermissionScopeAdmin",
"returns": {
"energyError": "$ref:EnergyError"
}
},
"NymeaEnergy.SetSpotMarketConfiguration": {
"description": "Set the current spotmarket configuration. If enabled, the provideId must be valid. You can get the available providers using GetAvailableSpotMarketProviders.",
"params": {
"enabled": "Bool",
"o:providerId": "Uuid"
},
"permissionScope": "PermissionScopeControlThings",
"returns": {
"energyError": "$ref:EnergyError"
}
}
},
"notifications": {
"AirConditioning.ZoneAdded": {
"description": "Emitted whenever a zone is added",
"params": {
"zone": "$ref:ZoneInfo"
}
},
"AirConditioning.ZoneChanged": {
"description": "Emitted whenever a zone changes",
"params": {
"zone": "$ref:ZoneInfo"
}
},
"AirConditioning.ZoneRemoved": {
"description": "Emitted whenever a zone is removed",
"params": {
"zoneId": "Uuid"
}
},
"NymeaEnergy.AcquisitionToleranceChanged": {
"description": "Emitted whenever the acquisition tolerance changes.",
"params": {
"acquisitionTolerance": "Double"
}
},
"NymeaEnergy.BatteryLevelConsiderationChanged": {
"description": "Emitted whenever the battery level consideration changes.",
"params": {
"batteryLevelConsideration": "Double"
}
},
"NymeaEnergy.ChargingInfoAdded": {
"description": "Emitted whenever a charging info is added.",
"params": {
"chargingInfo": "$ref:ChargingInfo"
}
},
"NymeaEnergy.ChargingInfoChanged": {
"description": "Emitted whenever a charging info changes.",
"params": {
"chargingInfo": "$ref:ChargingInfo"
}
},
"NymeaEnergy.ChargingInfoRemoved": {
"description": "Emitted whenever a charging info is removed.",
"params": {
"evChargerThingId": "Uuid"
}
},
"NymeaEnergy.ChargingSchedulesChanged": {
"description": "Emitted whenever the planed charging schedules have changed.",
"params": {
"chargingSchedules": [
"$ref:ChargingSchedule"
],
"o:degradedMode": "Bool"
}
},
"NymeaEnergy.EnergyRatiosChanged": {
"description": "Emitted whenever the energy ratios (self-consumption / autonomy) change. A rate field is omitted (never null) when not available.",
"params": {
"o:autonomyRate": "Double",
"o:selfConsumptionRate": "Double"
}
},
"NymeaEnergy.LoadConfigChanged": {
"description": "Emitted whenever the controllable load configuration changed.",
"params": {
"loadConfigs": [
"$ref:LoadConfig"
]
}
},
"NymeaEnergy.LoadTelemetryChanged": {
"description": "Emitted whenever the arbitration runtime state changed significantly, and periodically (heartbeat) even when unchanged, so that a frozen arbitrator is detectable by a timestamp that stops advancing. Same payload as GetLoadTelemetry.",
"params": {
"degradedMode": "Bool",
"loads": [
{
"allocatedW": "Double",
"available": "Bool",
"decision": {
"code": "String",
"params": "Variant"
},
"loadId": "String",
"o:faultCode": "String",
"o:lock": {
"kind": "String",
"remainingS": "Int"
},
"o:mechanism": {
"kind": "String",
"o:chargingEnabled": "Bool",
"o:currentA": "Double",
"o:estimatedPowerW": "Double",
"o:maxPowerW": "Double",
"o:maxStageW": "Double",
"o:percent": "Double",
"o:phaseCount": "Uint",
"o:pluggedIn": "Bool",
"o:setpointW": "Double",
"o:stageW": "Double",
"o:state": "Int"
}
}
],
"o:budget": {
"allocatedW": "Double",
"evReservedW": "Double",
"recreditedW": "Double",
"remainingW": "Double",
"surplusW": "Double"
},
"o:timestamp": "String"
}
},
"NymeaEnergy.LockOnUnplugChanged": {
"description": "Emitted whenever the lock on unplug setting changes.",
"params": {
"lockOnUnplug": "Bool"
}
},
"NymeaEnergy.PhasePowerLimitChanged": {
"description": "Emitted whenever the phase power limit configuration changes.",
"params": {
"phasePowerLimit": "Uint"
}
},
"NymeaEnergy.SpotMarketConfigurationChanged": {
"description": "Emitted whenever a spot market configuration has changed.",
"params": {
"available": "Bool",
"enabled": "Bool",
"o:providerId": "Uuid"
}
},
"NymeaEnergy.SpotMarketScoreEntriesChanged": {
"description": "Emitted whenever the score entries of the current spot market provider changed. The weighting of the available score entries goes from 0 (worst) to 1.0 (best).",
"params": {
"spotMarketScoreEntries": [
"$ref:ScoreEntry"
]
}
},
"NymeaEnergy.SpotMarketStatusChanged": {
"description": "Emitted whenever the spot market manager status changed.",
"params": {
"available": "Bool",
"enabled": "Bool"
}
}
},
"types": {
"LoadConfig": {
"adapter": "String",
"domain": "String",
"enabled": "Bool",
"id": "String",
"label": "String",
"maxPowerW": "Int",
"minOffS": "Int",
"minOnS": "Int",
"mode": "String",
"needs": "$ref:LoadConfigNeeds",
"powerLevels": [
"Variant"
],
"priority": "Int",
"relays": [
"Variant"
],
"sgReady": "Object"
},
"LoadConfigNeeds": {
"dailyDeadline": "String",
"minEnergyWhPerDay": "Int"
},
"ChargingInfo": {
"chargingMode": "$ref:ChargingMode",
"evChargerId": "Uuid",
"o:assignedCarId": "Uuid",
"o:dailySpotMarketPercentage": "Uint",
"o:endDateTime": "Uint",
"o:repeatDays": [
"Int"
],
"o:spotMarketChargingEnabled": "Bool",
"o:targetPercentage": "Uint",
"r:chargingState": "$ref:ChargingState"
},
"ZoneInfo": {
"r:currentSetpoint": "Double",
"r:humidity": "Double",
"r:id": "Uuid",
"r:indoorSensors": [
"Uuid"
],
"r:name": "String",
"r:notifications": [
"Uuid"
],
"r:outdoorSensors": [
"Uuid"
],
"r:pm25": "Double",
"r:setpointOverride": "Double",
"r:setpointOverrideEnd": "Uint",
"r:setpointOverrideMode": "$ref:SetpointOverrideMode",
"r:standbySetpoint": "Double",
"r:temperature": "Double",
"r:thermostats": [
"Uuid"
],
"r:valves": [
"Uuid"
],
"r:voc": "Uint",
"r:weekSchedule": "$ref:TemperatureWeekSchedule",
"r:windowSensors": [
"Uuid"
],
"r:zoneStatus": "$ref:ZoneStatus"
},
"ZoneInfos": [
"$ref:ZoneInfo"
],
"TemperatureSchedule": {
"endTime": "Time",
"startTime": "Time",
"temperature": "Double"
},
"TemperatureDaySchedule": [
"$ref:TemperatureSchedule"
],
"TemperatureWeekSchedule": [
"$ref:TemperatureDaySchedule"
]
},
"enums": {
"ChargingMode": [
"ChargingModeNormal",
"ChargingModeEco",
"ChargingModeEcoWithTargetTime",
"ChargingModeEcoWithMinCurrent",
"ChargingModeEcoMinWithTargetTime"
],
"ChargingState": [
"ChargingStateIdle",
"ChargingStateSurplusCharging",
"ChargingStateSpotMarketCharging",
"ChargingStateTimeRequirement"
],
"EnergyError": [
"EnergyErrorNoError",
"EnergyErrorMissingParameter",
"EnergyErrorInvalidParameter"
],
"AirConditioningError": [
"AirConditioningErrorNoError",
"AirConditioningErrorZoneNotFound",
"AirConditioningErrorInvalidTimeSpec",
"AirConditioningErrorThingNotFound",
"AirConditioningErrorInvalidThingType"
],
"SetpointOverrideMode": [
"SetpointOverrideModeNone",
"SetpointOverrideModeTimed",
"SetpointOverrideModeUnlimited",
"SetpointOverrideModeEventual"
]
}
}