J'ai fouiné dans /api/homesdata et j'ai trouvé deux familles de plannings. Chez moi, 12 au total : 6 de type therm et 6 de type event, avec les mêmes noms, les mêmes identifiants de zone et des timetables identiques. Les deux sont marqués selected en même temps.
- les zones
therm portent les températures, et leur tableau modules est toujours vide
- les zones
event portent modules: [{"id": ..., "dhw_enabled": true|false}], soit exactement l'interrupteur « Production d'eau chaude » qu'on voit dans l'appli sur chaque jeu de température
Extrait d'un planning event sélectionné (~vacances à la maison consigne basse) :
{
"name": "Vacances int Light",
"type": "event",
"selected": true,
"timetable": [
{"zone_id": 1, "m_offset": 0},
{"zone_id": 7, "m_offset": 465},
{"zone_id": 0, "m_offset": 555}
],
"zones": [
{"id": 1, "modules": [{"dhw_enabled": false}]},
{"id": 7, "name": "Matin", "modules": [{"dhw_enabled": false}]},
{"id": 0, "modules": [{"dhw_enabled": true}]}
]
}
Les m_offset sont des minutes depuis lundi 00:00 en heure locale, et ça colle au créneau près avec ce que montre l'appli (465 = lundi 07:45, 555 = lundi 09:15).
J'ai patché mon intégration locale en prenant le planning event sélectionné, dans lequel je cherche la dernière entrée du timetable dont l'offset n'est pas dans le futur, et je lis dhw_enabled sur la zone correspondante.
J'ai implémenté ça en local sous forme d'un binary_sensor sur la gateway, avec des attributs schedule_name / zone_name / zone_id pour le debug, et un état unavailable plutôt qu'une valeur inventée si le flag n'est pas trouvé. Ça tourne chez moi depuis cet aprèm et ça suit le planning tout seul, sans plus rien de codé en dur.
J'ai la flème de pousser une PR mais je peux fournir les 3 fichiers modifiés si besoin 
Je regarde pour le mode absent dès que j'ai un instant, qu'il faudra croiser avec dhw_enabled qui visiblement reste à true quand le mode absent est activé (qui, j'espère, doit couper l'ECS).
EDIT :
J'ai poussé un peu plus les tests et je pense être tombé sur un bug d'affichage du mode. Si les capteurs, configurations et diagnostics ont l'air bons, aussi bien pour la gateway que le thermostat, l'entité climate n'indique pas le bon état, dans mon cas tout du moins, et je crois avoir trouvé pourquoi.
MiGo empile trois notions distinctes
- le mode chaudière (Mode normal / Eau chaude seulement / En veille [hors-gel]), accessible via les Actions rapides de l'appli
- le mode maison (Absent, avec son bouton « Je suis de retour »)
- la consigne de la pièce
Tout ça sans compter le boost eau chaude. De toute façon, d'après les tests effectués, l'ECS est toujours activée par le planning, quel que soit le mode chaudière (même hors-gel), j'ai testé au robinet dans les différents cas.
Toutefois, l'entité climate ne regarde que therm_setpoint_mode (la pièce), donc les deux premiers n'apparaissent nulle part dans HA, et surtout, le mode chaudière écrase la consigne de la pièce, ce qui produit un affichage faux.
Ce que remonte l'API MiGo
J'ai dumpé /api/homesdata et /api/homestatus dans 5 configurations (la 6ème est impossible, les Actions rapides sont grisées quand la veille est active) :
| # |
Mode chaudière |
Absent |
therm_mode |
therm_setpoint_mode |
Consigne |
| 1 |
Normal |
non |
schedule |
home |
21 °C |
| 2 |
Normal |
oui |
away |
home |
18,5 °C |
| 3 |
Eau chaude seulement |
non |
schedule |
hg |
7 °C |
| 4 |
Eau chaude seulement |
oui |
schedule |
hg |
7 °C |
| 5 |
Veille (hors-gel) |
non |
hg |
home |
7 °C |
| 6 |
Veille + Absent |
|
non testable |
|
|
Trois choses ressortent de ce tableau.
therm_mode mélange deux notions indépendantes, il vaut away pour le mode maison et hg pour la veille, alors que ce sont deux réglages distincts dans l'appli.
Le hg de la pièce ne signifie pas hors-gel mais « chauffage coupé par le mode ECS seule », alors que la vraie veille remonte home. C'est inversé par rapport à l'intuition, et c'est ça qui produit l'affichage faux, puisque home est mappé sur HVACMode.HEAT.
Enfin, les dumps 3 et 4 sont rigoureusement identiques, octet pour octet. En mode « Eau chaude seulement », activer Absent ne change strictement rien dans la réponse de l'API.
Côté HA
| Situation réelle |
Consigne affichée |
Mode HA |
Préréglage HA |
| Eau chaude seulement + Absent |
7 °C |
Éteint |
Hors-gel |
| Veille (hors-gel) |
7 °C |
Chauffage |
(aucun) |
| Absent seul |
18,5 °C |
Chauffage |
(aucun) |
En veille (hors-gel), le thermostat de l'intégration m'affiche :
Pourtant, dans les capteurs on peut voir que la chaudière est arrêtée :
Quand je désactive le mode hors-gel :
(la chaudière est toujours affichée "arrêtée" puisqu'il fait plus de 21° dans la pièce).
Quand j'active le mode "eau chaude seulement" :
Aucun changement quand j'active le mode absent (par dessus eau chaude seulement), ce qui est logique vu que l'API ne remonte rien de différent.
Par contre, mode absent "seul" :
Ca affiche bien la température définie pour la plage horaire "absent", mais rien dans préréglage
Normal finalement, vu que Absent ne remonte jamais dans therm_setpoint_mode, il se traduit seulement par une consigne abaissée à away_temp.
L'info existe, mais sur un autre endpoint
À côté de MiGo j'utilise aussi l'intégration vaillant-vsmart, et elle affiche le mode absent correctement, y compris quand « Eau chaude seulement » est actif. J'ai donc dumpé son API pour comparer.
Elle n'utilise pas les mêmes endpoints : /api/getthermostatsdata sur api.netatmo.com, là où MiGo utilise homesdata et homestatus sur app.netatmo.net. Et son modèle de données colle beaucoup mieux à l'appli :
| Notion dans l'appli |
getthermostatsdata |
homesdata |
| Mode chaudière |
system_mode (winter / summer / frostguard) |
déduit de therm_mode + consigne pièce |
| Mode absent |
setpoint_away.setpoint_activate (booléen) |
therm_mode = away, invisible en mode ECS seule |
| Boost eau chaude |
setpoint_hwb |
dhw_enabled sur la gateway |
| Consigne ECS |
dhw, dhw_min, dhw_max |
absent |
Les trois modes des Actions rapides correspondent exactement à winter / summer / frostguard, sous forme d'un champ unique.
Et surtout, le cas où MiGo perd l'information. En « Eau chaude seulement + Absent », getthermostatsdata renvoie :
system_mode : 'summer'
setpoint_away : activate=True
est_setpoint_temp : 18.5
Donc la donnée existe bien côté Netatmo, elle est juste absente de homesdata et homestatus.
Une réserve importante
Cet endpoint est à réserver aux modes. Il porte aussi un flag ECS par zone, hw, directement dans les programmes de chauffage, mais ses valeurs ne sont plus synchronisées avec l'appli. Sur mon planning actif, 3 zones sur 6 divergent :
| Zone |
Appli MiGo |
hw (getthermostatsdata) |
dhw_enabled (homesdata) |
| Nuit |
OFF |
True |
false |
| À la maison |
ON |
False |
true |
| Week End |
? |
False |
true |
| Soirée |
ON |
True |
true |
| Fin de soirée |
ON |
True |
true |
| Matin |
OFF |
False |
false |
C'est l'appli qui a raison, donc le planning ECS doit rester sur homesdata. Probablement un vestige que Netatmo ne met plus à jour depuis l'introduction des plannings de type event.
Pistes
Si l'ajout d'un second endpoint te semble trop lourd, exposer therm_mode en entité séparée (un select ou un sensor) couvrirait déjà une partie du besoin. Il est déclaré dans models.py (HomeConfig.therm_mode) mais un grep ne le trouve nulle part ailleurs que dans l'appel d'écriture set_therm_mode, il n'est jamais lu.
Sinon, interroger getthermostatsdata en complément donnerait system_mode et setpoint_away proprement, avec les mêmes identifiants Saunier Duval.
Je peux fournir les dumps complets des deux API si ça peut aider, dis-moi.
Dernière info au passage, ni le mode Absent, ni la veille (hors-gel), ni Eau chaude seulement ne coupent la production d'eau chaude. Vérifié au robinet, 5 minutes d'eau chaude en continu avec la veille active. Seul le planning ECS semble décider.
En attendant j'utilise les 2 intégrations modifiées un peu à l'arrache à la main en complément, au moins j'ai les infos dont j'ai besoin et qui collent au terrain 
EDIT 2 : en fait le mode absent coupe bien l'eau (cf tranche horaire associée), jme suis fait avoir par les ballons qui étaient déjà chauds 
EDIT 3 (je ne peux pas faire plus de 3 réponses sur le topic :x) :
Suite et fin de mes aventures, avec une bonne nouvelle : tout tient dans MiGo, pas besoin de garder vSMART à côté.
Le token MiGo passe sur l'endpoint legacy
Je me demandais s'il fallait un second jeu d'identifiants pour interroger getthermostatsdata. Réponse : non. Le token obtenu avec les identifiants MiGo (na_client_ios_sdbg) est accepté tel quel sur api.netatmo.com/api/getthermostatsdata. Une intégration unique suffit donc, sans rien demander de plus à la configuration.
J'ai ajouté l'appel dans le coordinateur, isolé dans un try : s'il échoue, le reste de l'intégration continue de fonctionner et les entités concernées passent en unavailable plutôt que de mentir.
Ce que ça donne comme entités
binary_sensor Mode absent, lu depuis setpoint_away
sensor Mode chaudière, exposant system_mode (Chauffage / Eau chaude seulement / Veille)
switch Mode absent, écrit via setthermmode (celui qui existait déjà dans l'API MiGo), donc sans effet de bord sur le mode chaudière
Ce dernier point corrige au passage un bug de vSMART : son async_set_preset_mode envoie systématiquement TemperatureControlMode.HEATING en même temps que le mode absent, ce qui désactive le mode été sans prévenir. Faudrait que je leur signale...
Correction sur ce que j'écrivais hier
Dans mon tableau, le cas 4 (Eau chaude seulement + Absent) montrait therm_mode à schedule, d'où ma conclusion que l'info était perdue. En relisant les logs de debug, quand l'absence est activée via setthermmode, therm_mode passe bien à away même avec system_mode: summer. La perte que j'avais constatée venait probablement du fait que j'avais activé l'absence depuis l'appli. Je n'ai pas retesté proprement les deux chemins, donc à prendre avec des pincettes.
Du coup mon capteur combine les deux sources : therm_mode en priorité (immédiat), setpoint_away en repli. C'est nécessaire parce que le flag legacy reflète l'état du thermostat lui-même, synchronisé par radio avec plusieurs minutes de retard : juste après avoir activé l'absence, therm_mode dit déjà away alors que setpoint_away répond encore False.
Et le mode absent coupe bien l'ECS
Confirmé proprement cette fois, même planning, même zone (« À la maison », ECS active), seule l'absence change :
- sans absence : capteur
on, overridden_by_away: false
- avec absence : capteur
off, overridden_by_away: true
Logique, puisque « Absent » est un jeu de température comme les autres, avec « Production d'eau chaude » décochée, et qu'il remplace le créneau en cours. Mon capteur ECS renvoie donc off dès que l'absence est active, avec l'attribut pour distinguer la cause.
Détail amusant : l'appli grise le boost eau chaude quand le mode absent est actif, mais l'API l'accepte sans broncher, et le bandeau « Boost eau chaude sanitaire » apparaît ensuite dans l'appli. L'intégration fait donc mieux que l'outil officiel sur ce coup, ce qui est pratique quand on rentre plus tôt que prévu.
Deux correctifs sur du code existant
En testant, je suis tombé sur un souci de latence d'affichage qui ne vient pas de mon ajout :
- le switch boost ECS n'utilise aucun cache optimiste, il lit
dhw_enabled depuis homestatus, donc l'état ne bouge qu'au rafraîchissement suivant
- le switch anticipation remplit son cache après
_call_api_and_refresh, sans écriture d'état ensuite : le rafraîchissement recalcule l'état avant que le cache ne soit posé, donc l'interface reste sur l'ancienne valeur
J'ai factorisé un _call_api_optimistically dans MigoApiControlMixin qui remplit le cache et écrit l'état avant l'appel, et restaure la valeur précédente si l'appel échoue. Les trois switches l'utilisent. Les entités number étaient déjà dans le bon ordre, je n'y ai pas touché.
Bilan
15 fichiers touchés, 84 tests qui passent (24 nouveaux couvrant la résolution des créneaux, le bouclage dimanche→lundi, l'appariement linked_schedules, la priorité entre les deux sources d'absence et les cas inconnus), ruff propre à part deux PLR0917 préexistants dans api.py. Traductions ajoutées dans les 5 langues du dépôt.
J'ai aussi découvert linked_schedules dans homesdata, qui appaire explicitement les plannings therm et event sous forme de paires d'identifiants. C'est plus robuste que de se fier au double selected, mon code s'en sert avec repli sur l'ancienne méthode.