11package com.mg4.control.model
22
33/* *
4- * Sièges chauffants — atteindre un niveau sur une voiture qui ne sait qu'AVANCER d'un cran.
4+ * Sièges chauffants — atteindre un niveau sur une voiture qui ne connaît qu'une commande : « un
5+ * cran ».
56 *
6- * La commande du véhicule est un appui : éteint → 1 → 2 → 3 → éteint, toujours dans ce sens
7- * (l'écran clim d'origine envoie 1 à chaque clic). Et la voiture annonce son nouveau niveau avec
8- * retard. Lire, cliquer, relire, recliquer ne marche donc pas : un cran part en trop dès que la
9- * lecture tarde, et il faut refaire tout le tour — « 1 → 2 » donnait 2, 3, éteint, 1, 2
10- * (constaté sur SWI133 le 2026-10-09).
7+ * L'écran clim d'origine envoie 1 à chaque clic et c'est la voiture qui choisit le niveau
8+ * suivant ; le sens n'est écrit nulle part dans le firmware. Il a été établi sur véhicule
9+ * (SWI133, 2026-10-09) : **chaque cran fait DESCENDRE** — éteint → 3 → 2 → 1 → éteint. Sept
10+ * essais sur les boutons de niveau, faits alors que le compte supposait à tort que le cran
11+ * montait, l'ont montré sans ambiguïté (demander 1 depuis éteint donnait 3, demander 2 depuis 3
12+ * éteignait le siège…) ; ils sont rejoués dans les tests.
1113 *
12- * D'où la règle : on lit UNE fois, on compte, on envoie, et on ne reclique jamais d'après une
13- * relecture. Sans vue ni véhicule : la lecture, le cran et l'attente sont fournis par l'appelant.
14+ * Conséquence : descendre d'un niveau coûte un cran, MONTER d'un niveau en coûte trois, en
15+ * passant par « éteint ». Aucun compte ne peut y échapper.
16+ *
17+ * La voiture annonce aussi son nouveau niveau avec retard : on lit donc UNE fois, on compte, on
18+ * envoie, puis on vérifie sans jamais recliquer. Sans vue ni véhicule : la lecture, le cran et
19+ * l'attente sont fournis par l'appelant.
1420 */
1521object SeatHeat {
1622
1723 const val MAX_LEVEL = 3
24+ private const val CYCLE = MAX_LEVEL + 1
1825
1926 /* * Écart entre deux crans : trop rapprochés, la voiture pourrait en ignorer un. */
2027 const val CLICK_INTERVAL_MS = 600L
2128
22- /* * Délai après lequel le niveau LU reflète un cran qui vient de partir. */
23- const val READBACK_MS = 1_200L
29+ /* *
30+ * Délai après lequel le niveau LU reflète un cran qui vient de partir. Valeur prudente, non
31+ * mesurée : compter d'après une lecture en retard enverrait un cran de trop ou de moins.
32+ */
33+ const val READBACK_MS = 2_000L
2434
2535 private const val VERIFY_POLL_MS = 250L
2636 private const val VERIFY_TIMEOUT_MS = 3_000L
2737
38+ /* *
39+ * Niveau où un cran amène la voiture : éteint → 3 → 2 → 1 → éteint. C'est ce qu'il faut
40+ * annoncer après un appui (pop-up HVAC, raccourci) ; null si le niveau de départ est illisible.
41+ */
42+ fun next (actuel : Int? ): Int? =
43+ actuel?.let { (it.coerceIn(0 , MAX_LEVEL ) + CYCLE - 1 ) % CYCLE }
44+
2845 /* *
2946 * Nombre de crans à envoyer pour passer de [actuel] à [cible] ; null si l'un des deux n'est
30- * pas un niveau (lecture en échec, cible inconnue). Descendre d'un niveau en coûte trois :
31- * la voiture ne recule pas.
47+ * pas un niveau (lecture en échec, cible inconnue).
3248 */
3349 fun steps (actuel : Int , cible : Int ): Int? {
3450 if (actuel !in 0 .. MAX_LEVEL || cible !in 0 .. MAX_LEVEL ) return null
35- val cycle = MAX_LEVEL + 1
36- return ((cible - actuel) % cycle + cycle) % cycle
51+ return ((actuel - cible) % CYCLE + CYCLE ) % CYCLE
3752 }
3853
3954 /* *
@@ -43,7 +58,10 @@ object SeatHeat {
4358 * Rend false sans rien envoyer si le niveau est illisible — envoyer des crans à l'aveugle
4459 * ferait tourner le siège sans fin — et false aussi si la voiture n'a pas annoncé [cible] au
4560 * bout de la vérification. Dans ce cas on n'insiste pas : avec une lecture en retard, un cran
46- * de rattrapage serait justement le cran de trop.
61+ * de rattrapage partirait d'un niveau faux.
62+ *
63+ * La vérification ne peut pas être trompée par un niveau traversé en route : une série compte
64+ * au plus trois crans, donc quatre niveaux distincts, et [cible] est le dernier.
4765 *
4866 * @param lire niveau annoncé par la voiture (négatif = illisible)
4967 * @param cran envoie UN cran
0 commit comments