@@ -20,6 +20,7 @@ import java.lang.reflect.InvocationHandler
2020import java.lang.reflect.Proxy
2121import com.mg4.control.debug.AppLogger
2222import com.mg4.control.model.AirFlow
23+ import com.mg4.control.model.CustomDriveScale
2324import com.mg4.control.model.DriveMode
2425import com.mg4.control.model.RegenLevel
2526import 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} )" )
0 commit comments