Skip to content

Commit 78e32ba

Browse files
committed
Fix Custom mode for SWI68 SWI 165
1 parent 8c8abf0 commit 78e32ba

4 files changed

Lines changed: 213 additions & 66 deletions

File tree

‎app/src/main/java/com/mg4/control/hardware/MG4Hardware.kt‎

Lines changed: 95 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.lang.reflect.InvocationHandler
2020
import java.lang.reflect.Proxy
2121
import com.mg4.control.debug.AppLogger
2222
import com.mg4.control.model.AirFlow
23+
import com.mg4.control.model.CustomDriveScale
2324
import com.mg4.control.model.DriveMode
2425
import com.mg4.control.model.RegenLevel
2526
import com.mg4.control.util.FirmwareInfo
@@ -1703,98 +1704,126 @@ object MG4Hardware {
17031704
private const val SIG_CUSTOM_STEERING = 0x2040004
17041705
private const val SIG_CUSTOM_PEDAL = 0x2040005
17051706

1706-
/** Index manipulés par l'application — jamais les valeurs véhicule, voir [valeurPuissance]. */
1707+
/** Index manipulés par l'application — jamais les valeurs véhicule, voir [CustomDriveScale]. */
17071708
object CustomDrive {
17081709
const val ECO_COMFORT = 0
17091710
const val NORMAL = 1
17101711
const val SPORT = 2
17111712
}
17121713

1714+
/** Propriétés écrites par le service véhicule SWI68/165 derrière les méthodes du SDK — sondées. */
1715+
private const val AAD_CUSTOM_POWER = 0x2140a18c
1716+
private const val AAD_CUSTOM_STEERING = 0x2140a18d
1717+
private const val AAD_CUSTOM_PEDAL = 0x2140a18e
1718+
17131719
/**
1714-
* A9 : SWI69, SWI131, SWI132. Même ensemble que la clim, mais fonction distincte à dessein —
1715-
* changer la règle d'un domaine ne doit pas déplacer l'autre en silence.
1720+
* Voie d'accès du mode Personnalisé selon le firmware :
1721+
* - A9 (SWI69/131/132) : méthodes `…Mode` du CarVehicleSettingClient ;
1722+
* - SWI68/165 : méthodes `…Level` du VehicleSettingManager SAIC (le VehiclePropertyManager
1723+
* n'existe pas sur ces firmwares, d'où les trois lignes absentes jusqu'ici) ;
1724+
* - SWI133 : propriétés VPM.
17161725
*/
1717-
private fun isDriveCustomA9(): Boolean {
1726+
private fun customFamily(): CustomDriveScale.Family {
17181727
val gen = FirmwareInfo.getGeneration()
1719-
return gen == FirmwareInfo.Gen.SWI69 || gen == FirmwareInfo.Gen.SWI131 ||
1720-
gen == FirmwareInfo.Gen.SWI132
1728+
return when (gen) {
1729+
FirmwareInfo.Gen.SWI69, FirmwareInfo.Gen.SWI131, FirmwareInfo.Gen.SWI132 -> CustomDriveScale.Family.A9
1730+
FirmwareInfo.Gen.SWI68, FirmwareInfo.Gen.SWI165 -> CustomDriveScale.Family.VSM_68
1731+
else -> CustomDriveScale.Family.VPM_133
1732+
}
17211733
}
17221734

1723-
// ⚠️ TROIS ÉCHELLES DIFFÉRENTES, relevées dans le code d'origine des deux familles :
1724-
// • direction : 1/2/3 partout ;
1725-
// • pédale : 1/0/2 partout — Normal vaut ZÉRO, et non la valeur du milieu. Le smali SWI133
1726-
// réassigne le registre entre deux branches, ce qui se lit 1/2/3 si on va trop vite ;
1727-
// le dispatch A9 sur les libellés comfort/normal/sport donne le même résultat ;
1728-
// • puissance : 1/2/3 en old-SDK mais 2/3/4 sur A9, où la voiture réutilise l'échelle des
1729-
// modes de conduite (ÉCO=2, NORMAL=3, SPORT=4).
1730-
//
1731-
// D'où l'index 0/1/2 exposé au reste de l'application : personne d'autre ne manipule ces
1732-
// nombres, et une échelle qui changerait ne se corrige qu'ici.
1733-
private fun valeurPuissance(index: Int): Int = index + (if (isDriveCustomA9()) 2 else 1)
1734-
private fun valeurDirection(index: Int): Int = index + 1
1735-
private fun valeurPedale(index: Int): Int = when (index) {
1736-
CustomDrive.ECO_COMFORT -> 1
1737-
CustomDrive.SPORT -> 2
1738-
else -> 0
1739-
}
1740-
1741-
private fun indexPuissance(v: Int): Int? =
1742-
(v - (if (isDriveCustomA9()) 2 else 1)).takeIf { it in 0..2 }
1743-
private fun indexDirection(v: Int): Int? = (v - 1).takeIf { it in 0..2 }
1744-
private fun indexPedale(v: Int): Int? = when (v) {
1745-
1 -> CustomDrive.ECO_COMFORT
1746-
0 -> CustomDrive.NORMAL
1747-
2 -> CustomDrive.SPORT
1748-
else -> null
1749-
}
1750-
1751-
/** Puissance en chevaux du mode Personnalisé. [index] : 0=Éco, 1=Normal, 2=Sport. */
1752-
fun setCustomPower(index: Int): Boolean {
1753-
if (index !in 0..2) return false
1754-
val v = valeurPuissance(index)
1755-
if (logEnabled) AppLogger.i(TAG, "setCustomPower → index=$index valeur=$v")
1756-
return if (isDriveCustomA9()) callVsmVoid("setDrivingPowerTrainMode", v)
1757-
else setIntPropertyVpmRecovery(SIG_CUSTOM_POWER, v)
1735+
/** Méthode du SDK selon la famille (null = voie propriété VPM). */
1736+
private fun customMethod(setting: CustomDriveScale.Setting, write: Boolean): String? {
1737+
val prefix = if (write) "set" else "get"
1738+
return when (customFamily()) {
1739+
CustomDriveScale.Family.A9 -> prefix + when (setting) {
1740+
CustomDriveScale.Setting.POWER -> "DrivingPowerTrainMode"
1741+
CustomDriveScale.Setting.STEERING -> "SteeringMode"
1742+
CustomDriveScale.Setting.PEDAL -> "BrakePedalMode"
1743+
}
1744+
CustomDriveScale.Family.VSM_68 -> prefix + when (setting) {
1745+
CustomDriveScale.Setting.POWER -> "ElectricPowertrainLevel"
1746+
CustomDriveScale.Setting.STEERING -> "SteeringLevel"
1747+
CustomDriveScale.Setting.PEDAL -> "BrakePedalLevel"
1748+
}
1749+
CustomDriveScale.Family.VPM_133 -> null
1750+
}
17581751
}
17591752

1760-
/** Direction du mode Personnalisé. [index] : 0=Confort, 1=Normal, 2=Sport. */
1761-
fun setCustomSteering(index: Int): Boolean {
1762-
if (index !in 0..2) return false
1763-
val v = valeurDirection(index)
1764-
if (logEnabled) AppLogger.i(TAG, "setCustomSteering → index=$index valeur=$v")
1765-
// A9 : setSteeringMode, et NON setDrivingEpsMode — les deux existent, seule la première
1766-
// est appelée par l'app d'origine. Vérifié en traçant fragment → presenter → model.
1767-
return if (isDriveCustomA9()) callVsmVoid("setSteeringMode", v)
1768-
else setIntPropertyVpmRecovery(SIG_CUSTOM_STEERING, v)
1753+
private fun customVpmProperty(setting: CustomDriveScale.Setting): Int = when (setting) {
1754+
CustomDriveScale.Setting.POWER -> SIG_CUSTOM_POWER
1755+
CustomDriveScale.Setting.STEERING -> SIG_CUSTOM_STEERING
1756+
CustomDriveScale.Setting.PEDAL -> SIG_CUSTOM_PEDAL
17691757
}
17701758

1771-
/** Force exercée sur la pédale. [index] : 0=Confort, 1=Normal, 2=Sport. */
1772-
fun setCustomPedal(index: Int): Boolean {
1773-
if (index !in 0..2) return false
1774-
val v = valeurPedale(index)
1775-
if (logEnabled) AppLogger.i(TAG, "setCustomPedal → index=$index valeur=$v")
1776-
return if (isDriveCustomA9()) callVsmVoid("setBrakePedalMode", v)
1777-
else setIntPropertyVpmRecovery(SIG_CUSTOM_PEDAL, v)
1759+
private fun setCustom(setting: CustomDriveScale.Setting, index: Int): Boolean {
1760+
val value = CustomDriveScale.value(setting, index, customFamily()) ?: return false
1761+
if (logEnabled) AppLogger.i(CUSTOM_TAG, "$setting ← index=$index valeur=$value (${customFamily()})")
1762+
val method = customMethod(setting, write = true)
1763+
return if (method != null) callVsmVoid(method, value)
1764+
else setIntPropertyVpmRecovery(customVpmProperty(setting), value)
1765+
}
1766+
1767+
private fun getCustom(setting: CustomDriveScale.Setting): Int? {
1768+
val method = customMethod(setting, write = false)
1769+
val raw = if (method != null) (callVsm(method) as? Int) ?: -1
1770+
else getIntPropertyVpm(customVpmProperty(setting))
1771+
return CustomDriveScale.index(setting, raw, customFamily())
17781772
}
17791773

1774+
/** Puissance du mode Personnalisé. [index] : 0=Éco, 1=Normal, 2=Sport. */
1775+
fun setCustomPower(index: Int): Boolean = setCustom(CustomDriveScale.Setting.POWER, index)
1776+
1777+
/** Direction du mode Personnalisé. [index] : 0=Confort, 1=Normal, 2=Sport. */
1778+
fun setCustomSteering(index: Int): Boolean = setCustom(CustomDriveScale.Setting.STEERING, index)
1779+
1780+
/** Force exercée sur la pédale. [index] : 0=Confort, 1=Normal, 2=Sport. */
1781+
fun setCustomPedal(index: Int): Boolean = setCustom(CustomDriveScale.Setting.PEDAL, index)
1782+
17801783
/**
17811784
* Lectures — `null` si le véhicule ne répond pas OU rend une valeur hors échelle.
17821785
*
17831786
* C'est le seul garde-fou honnête pour savoir si la voiture porte réellement ces réglages :
17841787
* ils existent sur les six firmwares, mais rien ne dit qu'ils sont montés sur toutes les
17851788
* finitions. Un `null` fait masquer la ligne plutôt que d'offrir un bouton sans effet.
17861789
*/
1787-
fun getCustomPower(): Int? = indexPuissance(
1788-
if (isDriveCustomA9()) (callVsm("getDrivingPowerTrainMode") as? Int) ?: -1
1789-
else getIntPropertyVpm(SIG_CUSTOM_POWER))
1790+
fun getCustomPower(): Int? = getCustom(CustomDriveScale.Setting.POWER)
1791+
1792+
fun getCustomSteering(): Int? = getCustom(CustomDriveScale.Setting.STEERING)
17901793

1791-
fun getCustomSteering(): Int? = indexDirection(
1792-
if (isDriveCustomA9()) (callVsm("getSteeringMode") as? Int) ?: -1
1793-
else getIntPropertyVpm(SIG_CUSTOM_STEERING))
1794+
fun getCustomPedal(): Int? = getCustom(CustomDriveScale.Setting.PEDAL)
17941795

1795-
fun getCustomPedal(): Int? = indexPedale(
1796-
if (isDriveCustomA9()) (callVsm("getBrakePedalMode") as? Int) ?: -1
1797-
else getIntPropertyVpm(SIG_CUSTOM_PEDAL))
1796+
// ── Sonde du mode Personnalisé ────────────────────────────────────────────
1797+
private const val CUSTOM_TAG = "MG4_CUSTOM"
1798+
private const val CUSTOM_PROBE_THROTTLE_MS = 30_000L
1799+
@Volatile private var sCustomProbeMs = 0L
1800+
1801+
/**
1802+
* Relève les valeurs BRUTES des trois réglages par toutes les voies du firmware, pour vérifier
1803+
* l'échelle retenue ([CustomDriveScale]) — surtout sur SWI68/165 où elle est supposée.
1804+
* Limitée à une fois toutes les 30 s : l'écran réessaie toutes les 3 s quand rien n'est lisible.
1805+
*/
1806+
fun probeCustomDrive(origin: String) {
1807+
val now = SystemClock.elapsedRealtime()
1808+
if (now - sCustomProbeMs < CUSTOM_PROBE_THROTTLE_MS) return
1809+
sCustomProbeMs = now
1810+
val family = customFamily()
1811+
AppLogger.i(CUSTOM_TAG, "SONDE [$origin] firmware=${FirmwareInfo.getGeneration()} famille=$family " +
1812+
"vsm=${sVsm != null} vpm=${sVpm != null} cpm=${sCarPropertyManager != null}")
1813+
CustomDriveScale.Setting.entries.forEach { setting ->
1814+
val voies = mutableListOf<String>()
1815+
customMethod(setting, write = false)?.let { m -> voies += "$m=${callVsm(m) ?: "null"}" }
1816+
if (sVpm != null) voies += "VPM 0x${customVpmProperty(setting).toString(16)}=${getIntPropertyVpm(customVpmProperty(setting))}"
1817+
val aad = when (setting) {
1818+
CustomDriveScale.Setting.POWER -> AAD_CUSTOM_POWER
1819+
CustomDriveScale.Setting.STEERING -> AAD_CUSTOM_STEERING
1820+
CustomDriveScale.Setting.PEDAL -> AAD_CUSTOM_PEDAL
1821+
}
1822+
voies += "CPM 0x${aad.toString(16)}@global=${getIntPropertyCPM(aad, AREA_GLOBAL)} @0=${getIntPropertyCPM(aad, 0)}"
1823+
AppLogger.i(CUSTOM_TAG, "SONDE [$origin] $setting : ${voies.joinToString(" · ")} " +
1824+
"→ index retenu=${getCustom(setting) ?: "inconnu"}")
1825+
}
1826+
}
17981827

17991828
fun setRegenLevel(level: RegenLevel): Boolean {
18001829
if (logEnabled) AppLogger.i(TAG, "setRegenLevel → ${level.label} (${level.value})")
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.mg4.control.model
2+
3+
/**
4+
* Mode de conduite Personnalisé : index manipulés par l'application (0 = Éco/Confort, 1 = Normal,
5+
* 2 = Sport) ↔ valeurs attendues par le véhicule.
6+
*
7+
* Trois familles, relevées dans le code d'origine de chaque firmware :
8+
* - [Family.VPM_133] : propriétés SAIC `0x2040002` / `0x2040004` / `0x2040005` ;
9+
* - [Family.A9] : méthodes `setDrivingPowerTrainMode` / `setSteeringMode` / `setBrakePedalMode` —
10+
* la puissance y réutilise l'échelle des modes de conduite (Éco = 2) ;
11+
* - [Family.VSM_68] : méthodes `setElectricPowertrainLevel` / `setSteeringLevel` /
12+
* `setBrakePedalLevel` du SDK SAIC, qui écrivent les propriétés `0x2140a18c` à `0x2140a18e`.
13+
* Le service véhicule SWI68 borne ces trois valeurs à 0..3 ; l'échelle 1/2/3 retenue ici reste
14+
* une hypothèse, que la sonde `MG4_CUSTOM` confirmera en voiture (surtout pour la pédale, qui
15+
* vaut 1/0/2 sur les deux autres familles).
16+
*/
17+
object CustomDriveScale {
18+
19+
enum class Setting { POWER, STEERING, PEDAL }
20+
21+
enum class Family { VPM_133, A9, VSM_68 }
22+
23+
/** Valeur à écrire, ou null si [index] n'est pas dans 0..2. */
24+
fun value(setting: Setting, index: Int, family: Family): Int? {
25+
if (index !in 0..2) return null
26+
return when (setting) {
27+
Setting.POWER -> index + if (family == Family.A9) 2 else 1
28+
Setting.STEERING -> index + 1
29+
Setting.PEDAL -> if (family == Family.VSM_68) index + 1 else PEDAL_VALUES[index]
30+
}
31+
}
32+
33+
/** Index correspondant à une valeur lue, ou null si elle est hors échelle (équipement absent). */
34+
fun index(setting: Setting, value: Int, family: Family): Int? = when (setting) {
35+
Setting.POWER -> (value - if (family == Family.A9) 2 else 1).takeIf { it in 0..2 }
36+
Setting.STEERING -> (value - 1).takeIf { it in 0..2 }
37+
Setting.PEDAL -> if (family == Family.VSM_68) (value - 1).takeIf { it in 0..2 }
38+
else PEDAL_VALUES.indexOf(value).takeIf { it >= 0 }
39+
}
40+
41+
/** Pédale, familles VPM_133 et A9 : confort = 1, normal = 0, sport = 2. */
42+
private val PEDAL_VALUES = intArrayOf(1, 0, 2)
43+
44+
private fun IntArray.indexOf(value: Int): Int = indices.firstOrNull { this[it] == value } ?: -1
45+
}

‎app/src/main/java/com/mg4/control/ui/DashboardFragment.kt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,9 @@ class DashboardFragment : Fragment() {
983983
val puissance = MG4Hardware.getCustomPower()
984984
val direction = MG4Hardware.getCustomSteering()
985985
val pedale = MG4Hardware.getCustomPedal()
986+
// Une ligne illisible : on relève les valeurs brutes de toutes les voies (tag MG4_CUSTOM),
987+
// seul moyen de vérifier l'échelle retenue sur un firmware qu'on n'a pas sous la main.
988+
if (puissance == null || direction == null || pedale == null) MG4Hardware.probeCustomDrive("mode Personnalisé")
986989
withContext(Dispatchers.Main) {
987990
if (!isAdded) return@withContext
988991
// L'état a pu changer pendant la lecture (l'utilisateur quitte le mode) : on
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.mg4.control.model
2+
3+
import com.mg4.control.model.CustomDriveScale.Family
4+
import com.mg4.control.model.CustomDriveScale.Setting
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertNull
7+
import org.junit.Test
8+
9+
/**
10+
* Mode de conduite Personnalisé : correspondance index de l'app ↔ valeur envoyée au véhicule.
11+
*
12+
* Trois familles, trois échelles relevées dans le code d'origine : une erreur ici met la voiture
13+
* dans le mauvais réglage sans rien signaler.
14+
*/
15+
class CustomDriveScaleTest {
16+
17+
private val indexes = 0..2
18+
19+
@Test
20+
fun `puissance - ancien SDK 1-2-3, A9 2-3-4, SWI68 1-2-3`() {
21+
assertEquals(listOf(1, 2, 3), indexes.map { CustomDriveScale.value(Setting.POWER, it, Family.VPM_133) })
22+
assertEquals(listOf(2, 3, 4), indexes.map { CustomDriveScale.value(Setting.POWER, it, Family.A9) })
23+
assertEquals(listOf(1, 2, 3), indexes.map { CustomDriveScale.value(Setting.POWER, it, Family.VSM_68) })
24+
}
25+
26+
@Test
27+
fun `direction - 1-2-3 partout`() {
28+
Family.entries.forEach { family ->
29+
assertEquals("famille $family", listOf(1, 2, 3),
30+
indexes.map { CustomDriveScale.value(Setting.STEERING, it, family) })
31+
}
32+
}
33+
34+
@Test
35+
fun `pedale - Normal vaut ZERO sauf sur SWI68-165`() {
36+
// Relevé dans le smali SWI133 et le dispatch A9 : confort=1, normal=0, sport=2.
37+
assertEquals(listOf(1, 0, 2), indexes.map { CustomDriveScale.value(Setting.PEDAL, it, Family.VPM_133) })
38+
assertEquals(listOf(1, 0, 2), indexes.map { CustomDriveScale.value(Setting.PEDAL, it, Family.A9) })
39+
// SWI68/165 : service véhicule borné à 0..3 ; échelle supposée 1/2/3, à confirmer par la sonde.
40+
assertEquals(listOf(1, 2, 3), indexes.map { CustomDriveScale.value(Setting.PEDAL, it, Family.VSM_68) })
41+
}
42+
43+
@Test
44+
fun `toute valeur ecrite se relit en le meme index`() {
45+
Family.entries.forEach { family ->
46+
Setting.entries.forEach { setting ->
47+
indexes.forEach { index ->
48+
val v = CustomDriveScale.value(setting, index, family)!!
49+
assertEquals("$family/$setting/$index", index, CustomDriveScale.index(setting, v, family))
50+
}
51+
}
52+
}
53+
}
54+
55+
@Test
56+
fun `index hors 0-2 refuse`() {
57+
assertNull(CustomDriveScale.value(Setting.POWER, -1, Family.VPM_133))
58+
assertNull(CustomDriveScale.value(Setting.PEDAL, 3, Family.A9))
59+
}
60+
61+
@Test
62+
fun `valeur hors echelle = inconnue`() {
63+
// Le véhicule qui ne porte pas l'équipement rend une valeur hors échelle (ou -1 en échec).
64+
assertNull(CustomDriveScale.index(Setting.POWER, -1, Family.VPM_133))
65+
assertNull(CustomDriveScale.index(Setting.POWER, 1, Family.A9)) // A9 commence à 2
66+
assertNull(CustomDriveScale.index(Setting.PEDAL, 3, Family.VPM_133)) // pédale : 1/0/2 seulement
67+
assertNull(CustomDriveScale.index(Setting.PEDAL, 0, Family.VSM_68)) // SWI68 : 1/2/3
68+
assertNull(CustomDriveScale.index(Setting.STEERING, 255, Family.VSM_68))
69+
}
70+
}

0 commit comments

Comments
 (0)