EVHardware: rename "Katman" (Turkish for layer) to "Layer"

Function names (whenLayer1Ready, initLayer4Swi69, isLayer4Ready, …), log
lines, comments and the library's README and changelog. Nothing outside
EVHardware used the names. The header now lists all five layers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-25 21:58:45 +01:00
co-authored by Claude Opus 5.5
parent 0ff56285bc
commit 8b2acc3e70
7 changed files with 174 additions and 170 deletions
+1 -1
View File
@@ -824,7 +824,7 @@ All notable changes to this project are documented here. Format follows
- **The glass probe refused to run on a car whose ignition was on.** It read
`getCurrentIgnitionState()`, which answers on the AAOS `IgnitionState` scale, against
`CarIgnitionItem.RUN` — and the two scales do not line up: RUN is 2 for Katman5, where 2 is
`CarIgnitionItem.RUN` — and the two scales do not line up: RUN is 2 for Layer5, where 2 is
OFF. Contact mis read as 4 and was refused; contact coupé read as 2 and was let through.
`setEsc` compared the same two scales and now refuses only a *known* non-RUN ignition, as its
own documentation always said it did.
+1 -1
View File
@@ -60,7 +60,7 @@ Discover the rest of the suite:
## Overview
| Area | Types |
|---|---|
| Vehicle access | `EVHardware` — Katman1/4/5 reflection over `android.car` + SAIC SDK, per-firmware routing |
| Vehicle access | `EVHardware` — Layer1/4/5 reflection over `android.car` + SAIC SDK, per-firmware routing |
| Safety | `VehicleWriteGate` (0 km/h, fail-closed) + `@RequiresStandstill` on every gated setter |
| Vendor services | `saic.*` — the AIDL the head unit's own HVAC, charging, radio, hands-free and TTS apps use |
| Radio | `SaicRadio` — play, silence, fail-closed play/pause toggle, FM/AM tune, station stepping (DAB included), screen |
@@ -22,10 +22,13 @@ import kotlinx.coroutines.launch
/**
* Hardware abstraction layer for MG4 vehicle control.
*
* Runtime communication layers ("Katman", Turkish for layer, as the names in this file call them):
* Katman1 — android.car.Car → CarPropertyManager / CarHvacManager (async ServiceConnection)
* Katman2 — ServiceManager.getService("vehiclesetting") → raw IBinder (often SELinux-blocked)
* Katman3 — bindService(VehicleService) → IHubService → sub-services (not needed for our use case)
* Runtime communication layers, numbered as the names in this file use them (Layer1 … Layer5):
* Layer1 — android.car.Car → CarPropertyManager / CarHvacManager (async ServiceConnection)
* Layer2 — ServiceManager.getService("vehiclesetting") → raw IBinder (often SELinux-blocked)
* Layer3 — bindService(VehicleService) → IHubService → sub-services (not needed for our use case)
* Layer4 — SAIC's settings SDK in the launcher's context: VehiclePropertyManager (SWI133),
* VehicleSettingManager (SWI68/SWI165), CarVehicleSettingClient via CarAdapterClient (A9)
* Layer5 — ignition state: VehicleConditionManager, or ICarGeneralService via CarAdapterClient (A9)
*/
object EVHardware {
@@ -146,11 +149,11 @@ object EVHardware {
private const val VSM_SERVICE_CODE = 0x8 // queryClient(0x8) → ICarVehicleSettingService
private const val VEHICLE_SETTING_PKG = "com.saicmotor.vehiclesetting" // SWI131: carapi inside VS
// Katman5 — VehicleConditionManager (IVehicleConditionService via IHubService "vehiclecondition")
// Layer5 — VehicleConditionManager (IVehicleConditionService via IHubService "vehiclecondition")
private const val VCM_CLASS = "com.saicmotor.sdk.vehiclesettings.manager.VehicleConditionManager"
private const val VCM_LISTENER_CLASS = "com.saicmotor.sdk.vehiclesettings.IVehicleConditionListener"
// Katman5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// Layer5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
private const val CAR_GENERAL_CLIENT_CLASS = "com.saicmotor.carapi.client.CarGeneralClient"
private const val BIND_CODE_CAR_GENERAL = 0x1 // ICarAdapterService.queryClient(0x1)
@@ -177,7 +180,7 @@ object EVHardware {
}
/**
* Values returned by IVehicleConditionService.getVehicleIgnition() (Katman5).
* Values returned by IVehicleConditionService.getVehicleIgnition() (Layer5).
* Values exposed by the supported SWI133 runtime interface.
*/
object CarIgnitionItem {
@@ -232,9 +235,9 @@ object EVHardware {
/** Set once the getters above have been looked up, present or absent. */
@Volatile private var sCpmMethodsResolved = false
@Volatile private var sVehicleBinder: IBinder? = null
@Volatile private var sVpm: Any? = null // VehiclePropertyManager instance (SWI133, Katman4)
@Volatile private var sVpm: Any? = null // VehiclePropertyManager instance (SWI133, Layer4)
@Volatile private var sVpmService: Any? = null // mIVehiclePropertyService field value (SWI133)
@Volatile private var sVsm: Any? = null // VehicleSettingManager instance (SWI68, Katman4)
@Volatile private var sVsm: Any? = null // VehicleSettingManager instance (SWI68, Layer4)
// The A9 adapter, kept after the VSM client is built: it is the door to every other
// carapi client too — the climate one among them — and nothing else can open it.
@Volatile private var sCarAdapter: Any? = null
@@ -253,49 +256,49 @@ object EVHardware {
@Volatile private var sDriveModeListener: DriveModeListener? = null
@Volatile private var sHvacListener: HvacListener? = null
// ── Katman5 — IGNITION_STATE via CarPropertyManager (standard AAOS) ──────
// ── Layer5 — IGNITION_STATE via CarPropertyManager (standard AAOS) ──────
@Volatile private var sIgnitionCallbackProxy: Any? = null
@Volatile private var sIgnitionCallbackRegistered = false
private val ignitionCallbacks = java.util.concurrent.CopyOnWriteArrayList<(Int) -> Unit>()
// ── Katman5 — VehicleConditionManager / ICarGeneralService ───────────────
// ── Layer5 — VehicleConditionManager / ICarGeneralService ───────────────
@Volatile private var sVcm: Any? = null
@Volatile private var sVcmListener: Any? = null
@Volatile private var sVcmCallbackRegistered = false
@Volatile private var sLastVcmIgnitionState = -1 // filters repeated false RUN events
private val vehicleConditionCallbacks = java.util.concurrent.CopyOnWriteArrayList<(Int) -> Unit>()
private val katman5ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
private val layer5ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
/** Listeners notified as soon as Katman1 (CPM + HVAC) is operational. */
private val katman1ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
/** Listeners notified as soon as Layer1 (CPM + HVAC) is operational. */
private val layer1ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
/** Listeners notified as soon as Katman4 (mIVehiclePropertyService) is operational. */
private val katman4ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
/** Listeners notified as soon as Layer4 (mIVehiclePropertyService) is operational. */
private val layer4ReadyListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
/**
* Runs [action] as soon as CarPropertyManager and CarHvacManager are available.
* If already ready, runs immediately. Otherwise queued and triggered on connection.
*/
fun whenKatman1Ready(action: () -> Unit) {
fun whenLayer1Ready(action: () -> Unit) {
if (sCarPropertyManager != null && sCarHvacManager != null) {
action()
} else {
katman1ReadyListeners.add(action)
layer1ReadyListeners.add(action)
}
}
/**
* Runs [action] as soon as the ADAS service (Katman4) is available.
* Runs [action] as soon as the ADAS service (Layer4) is available.
* SWI133 → mIVehiclePropertyService ; SWI68/SWI69/SWI131 → mVehicleSettingService
*/
fun whenKatman4Ready(action: () -> Unit) {
fun whenLayer4Ready(action: () -> Unit) {
val ready = if (FirmwareInfo.isVsmBased()) sVsmService != null else sVpmService != null
if (ready) action() else katman4ReadyListeners.add(action)
if (ready) action() else layer4ReadyListeners.add(action)
}
/** Runs [action] as soon as Katman5 (IVehicleConditionService) is operational. */
fun whenKatman5Ready(action: () -> Unit) {
if (sVcmCallbackRegistered) action() else katman5ReadyListeners.add(action)
/** Runs [action] as soon as Layer5 (IVehicleConditionService) is operational. */
fun whenLayer5Ready(action: () -> Unit) {
if (sVcmCallbackRegistered) action() else layer5ReadyListeners.add(action)
}
/** Registers a callback invoked on every ignition state change (CarIgnitionItem). */
@@ -338,12 +341,12 @@ object EVHardware {
* Current ignition state, as [IgnitionState] (AAOS) values.
*
* CarPropertyManager first. But SWI68 does not expose IGNITION_STATE through CPM: the
* property comes back empty, and the ignition state looked unreadable although Katman5
* reports it correctly — the log shows it (`Katman5: initial state = 2 (RUN/READY)`) while
* property comes back empty, and the ignition state looked unreadable although Layer5
* reports it correctly — the log shows it (`Layer5: initial state = 2 (RUN/READY)`) while
* the diagnostics classed IGNITION_STATE as NOT_READABLE.
*
* Hence the fallback to the last state delivered by IVehicleConditionService, converted:
* Katman5 speaks [CarIgnitionItem], this function returns [IgnitionState], and the two
* Layer5 speaks [CarIgnitionItem], this function returns [IgnitionState], and the two
* scales do not line up (RUN is 2 in one, OFF is 2 in the other).
*
* Returns -1 if nothing is readable, 0 if the property exists but says nothing.
@@ -356,7 +359,7 @@ object EVHardware {
return vcmIgnitionAsAaos() ?: global
}
/** The last Katman5 state translated into [IgnitionState], or null if there was none. */
/** The last Layer5 state translated into [IgnitionState], or null if there was none. */
private fun vcmIgnitionAsAaos(): Int? = when (sLastVcmIgnitionState) {
CarIgnitionItem.OFF -> IgnitionState.OFF
CarIgnitionItem.ACCESSORY -> IgnitionState.ACC
@@ -909,22 +912,22 @@ object EVHardware {
bindCarService(context)
sVehicleBinder = getBinderService("vehiclesetting")
when {
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI68 -> initKatman4Swi68(context)
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI165 -> initKatman4Swi68(context) // same SDK as SWI68
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI132 -> initKatman4Swi69(context) // CarVehicleSettingClient, same path as SWI69
FirmwareInfo.isNewGenVsm() -> initKatman4Swi69(context) // SWI69 + SWI131
else -> initKatman4(context)
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI68 -> initLayer4Swi68(context)
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI165 -> initLayer4Swi68(context) // same SDK as SWI68
FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI132 -> initLayer4Swi69(context) // CarVehicleSettingClient, same path as SWI69
FirmwareInfo.isNewGenVsm() -> initLayer4Swi69(context) // SWI69 + SWI131
else -> initLayer4(context)
}
// Katman5 — IGNITION_STATE push detection (VehicleConditionManager or ICarGeneralService)
// Layer5 — IGNITION_STATE push detection (VehicleConditionManager or ICarGeneralService)
// SWI132 uses the same observed ICarGeneralService path as SWI69/SWI131.
if (FirmwareInfo.isNewGenVsm() || FirmwareInfo.getGeneration() == FirmwareInfo.Gen.SWI132)
initKatman5Swi69(context)
initLayer5Swi69(context)
else
initKatman5(context)
initLayer5(context)
if (sVehicleBinder != null)
AppLogger.i(TAG, " ✓ Katman2: vehiclesetting binder OK")
AppLogger.i(TAG, " ✓ Layer2: vehiclesetting binder OK")
else
AppLogger.w(TAG, " ✗ Katman2: vehiclesetting null (SELinux — expected)")
AppLogger.w(TAG, " ✗ Layer2: vehiclesetting null (SELinux — expected)")
// Start the door watcher at boot if the feature is enabled (all firmwares).
startDoorWatcherIfEnabled()
@@ -935,7 +938,7 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// Katman1 — android.car.Car (async, mirrors original bindCarService exactly)
// Layer1 — android.car.Car (async, mirrors original bindCarService exactly)
// -------------------------------------------------------------------------
private fun bindCarService(context: Context) {
@@ -944,12 +947,12 @@ object EVHardware {
val carClass: Class<*>
try {
carClass = Class.forName("android.car.Car")
AppLogger.i(TAG, " Katman1: android.car.Car class found ✓")
AppLogger.i(TAG, " Layer1: android.car.Car class found ✓")
} catch (e: ClassNotFoundException) {
AppLogger.w(TAG, " Katman1: android.car.Car not found — not Automotive?")
AppLogger.w(TAG, " Layer1: android.car.Car not found — not Automotive?")
return
} catch (e: Exception) {
AppLogger.e(TAG, " Katman1: forName error: ${e.message}")
AppLogger.e(TAG, " Layer1: forName error: ${e.message}")
return
}
@@ -958,7 +961,7 @@ object EVHardware {
// Attempt 1: createCar(Context)
try {
car = carClass.getMethod("createCar", Context::class.java).invoke(null, context)
if (car != null) AppLogger.i(TAG, " Katman1: createCar(Context) → success")
if (car != null) AppLogger.i(TAG, " Layer1: createCar(Context) → success")
} catch (_: Exception) {}
// Attempt 2: createCar(Context, Handler)
@@ -966,7 +969,7 @@ object EVHardware {
try {
car = carClass.getMethod("createCar", Context::class.java, Handler::class.java)
.invoke(null, context, null)
if (car != null) AppLogger.i(TAG, " Katman1: createCar(Context, Handler) → success")
if (car != null) AppLogger.i(TAG, " Layer1: createCar(Context, Handler) → success")
} catch (_: Exception) {}
}
@@ -974,18 +977,18 @@ object EVHardware {
var scMethodFound: java.lang.reflect.Method? = null
try {
scMethodFound = carClass.getMethod("createCar", Context::class.java, ServiceConnection::class.java)
AppLogger.i(TAG, " Katman1: createCar(Context, ServiceConnection) method found")
AppLogger.i(TAG, " Layer1: createCar(Context, ServiceConnection) method found")
} catch (_: Exception) {}
if (car == null && scMethodFound != null) {
try {
val sc = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
AppLogger.i(TAG, " Katman1: ServiceConnection.onServiceConnected")
AppLogger.i(TAG, " Layer1: ServiceConnection.onServiceConnected")
tryGetManagersFromCar(carClass)
}
override fun onServiceDisconnected(name: ComponentName?) {
AppLogger.w(TAG, " Katman1: Car service disconnected")
AppLogger.w(TAG, " Layer1: Car service disconnected")
sCarPropertyManager = null
sCarHvacManager = null
clearCarPropertyMethods()
@@ -993,14 +996,14 @@ object EVHardware {
}
}
car = scMethodFound.invoke(null, context, sc)
if (car != null) AppLogger.i(TAG, " Katman1: createCar(Context, SC) → callback pending")
if (car != null) AppLogger.i(TAG, " Layer1: createCar(Context, SC) → callback pending")
} catch (e: Exception) {
AppLogger.w(TAG, " Katman1: createCar(Context, SC) error: ${e.message}")
AppLogger.w(TAG, " Layer1: createCar(Context, SC) error: ${e.message}")
}
}
if (car == null) {
AppLogger.e(TAG, " Katman1: all createCar methods failed")
AppLogger.e(TAG, " Layer1: all createCar methods failed")
// Not necessarily fatal: on a cold head unit the Car service is simply not up
// yet. Giving up here would leave the whole process without a vehicle forever.
armCarServiceWatchdog(context, carClass)
@@ -1012,11 +1015,11 @@ object EVHardware {
// Call car.connect() if available (required on older builds)
try {
carClass.getMethod("connect").invoke(car)
AppLogger.i(TAG, " Katman1: car.connect() called")
AppLogger.i(TAG, " Layer1: car.connect() called")
} catch (_: NoSuchMethodException) {
// connect() not present on all builds, ignore
} catch (e: Exception) {
AppLogger.w(TAG, " Katman1: car.connect() error: ${e.message}")
AppLogger.w(TAG, " Layer1: car.connect() error: ${e.message}")
}
// Try sync managers immediately
@@ -1075,9 +1078,9 @@ object EVHardware {
(carClass.getMethod("isConnected").invoke(car) as? Boolean) ?: true
} catch (_: Exception) { true }
AppLogger.i(TAG, " Katman1: isConnected() → $connected")
AppLogger.i(TAG, " Layer1: isConnected() → $connected")
if (!connected) {
AppLogger.w(TAG, " Katman1: car not yet connected")
AppLogger.w(TAG, " Layer1: car not yet connected")
return
}
@@ -1088,9 +1091,9 @@ object EVHardware {
val svc = carClass.getField("PROPERTY_SERVICE").get(null) as String
sCarPropertyManager = getCarManager.invoke(car, svc)
cacheCarPropertyMethods(sCarPropertyManager)
AppLogger.i(TAG, " Katman1: CarPropertyManager READY ✓")
AppLogger.i(TAG, " Layer1: CarPropertyManager READY ✓")
} catch (e: Exception) {
AppLogger.w(TAG, " Katman1: CarPropertyManager unavailable: ${e.message}")
AppLogger.w(TAG, " Layer1: CarPropertyManager unavailable: ${e.message}")
}
}
@@ -1098,28 +1101,28 @@ object EVHardware {
try {
val svc = carClass.getField("HVAC_SERVICE").get(null) as String
sCarHvacManager = getCarManager.invoke(car, svc)
AppLogger.i(TAG, " Katman1: CarHvacManager READY ✓")
AppLogger.i(TAG, " Layer1: CarHvacManager READY ✓")
} catch (e: Exception) {
AppLogger.w(TAG, " Katman1: CarHvacManager unavailable: ${e.message}")
AppLogger.w(TAG, " Layer1: CarHvacManager unavailable: ${e.message}")
}
}
// Notify whenKatman1Ready subscribers as soon as both managers are ready
if (sCarPropertyManager != null && sCarHvacManager != null && katman1ReadyListeners.isNotEmpty()) {
val toNotify = katman1ReadyListeners.toList()
katman1ReadyListeners.clear()
// Notify whenLayer1Ready subscribers as soon as both managers are ready
if (sCarPropertyManager != null && sCarHvacManager != null && layer1ReadyListeners.isNotEmpty()) {
val toNotify = layer1ReadyListeners.toList()
layer1ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
}
// Try to register the IGNITION_STATE callback (best effort)
if (sCarPropertyManager != null) registerIgnitionPropertyCallback()
} catch (e: Exception) {
AppLogger.e(TAG, " Katman1: tryGetManagersFromCar error: ${e.message}")
AppLogger.e(TAG, " Layer1: tryGetManagersFromCar error: ${e.message}")
}
}
// -------------------------------------------------------------------------
// Katman2 — ServiceManager raw binder
// Layer2 — ServiceManager raw binder
// -------------------------------------------------------------------------
private fun getBinderService(serviceName: String): IBinder? {
@@ -1136,10 +1139,10 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// Katman4 — VehiclePropertyManager via saicmotor.hmi.launcher context
// Layer4 — VehiclePropertyManager via saicmotor.hmi.launcher context
// -------------------------------------------------------------------------
private fun initKatman4(context: Context) {
private fun initLayer4(context: Context) {
if (sVpm != null) return
val launcherCtx: Context
val vpmClass: Class<*>
@@ -1151,8 +1154,8 @@ object EVHardware {
vpmClass = launcherCtx.classLoader
.loadClass("com.saicmotor.sdk.vehiclesettings.manager.VehiclePropertyManager")
} catch (e: Exception) {
AppLogger.w(TAG, " Katman4: package/class error: ${e.message} — will retry")
Handler(Looper.getMainLooper()).postDelayed({ initKatman4(context.applicationContext) }, 5_000)
AppLogger.w(TAG, " Layer4: package/class error: ${e.message} — will retry")
Handler(Looper.getMainLooper()).postDelayed({ initLayer4(context.applicationContext) }, 5_000)
return
}
@@ -1184,8 +1187,8 @@ object EVHardware {
}
if (vpm == null) {
AppLogger.w(TAG, " Katman4: all attempts failed — will retry")
Handler(Looper.getMainLooper()).postDelayed({ initKatman4(context.applicationContext) }, 10_000)
AppLogger.w(TAG, " Layer4: all attempts failed — will retry")
Handler(Looper.getMainLooper()).postDelayed({ initLayer4(context.applicationContext) }, 10_000)
return
}
@@ -1221,7 +1224,7 @@ object EVHardware {
}
tryGetVpmService(vpm!!)
AppLogger.i(TAG, " Katman4: VPM ready — mIVehiclePropertyService=${if (sVpmService != null) "OK ✓" else "null (en attente)"}")
AppLogger.i(TAG, " Layer4: VPM ready — mIVehiclePropertyService=${if (sVpmService != null) "OK ✓" else "null (en attente)"}")
}
private fun initWithServiceListener(vpm: Any, context: Context, launcherCtx: Context) {
@@ -1231,7 +1234,7 @@ object EVHardware {
val methodSummary = vpmClass.methods.joinToString(", ") { m ->
"${m.name}(${m.parameterTypes.joinToString(",") { it.simpleName }})"
}
AppLogger.d(TAG, " Katman4: VPM methods = $methodSummary")
AppLogger.d(TAG, " Layer4: VPM methods = $methodSummary")
// Strategy 1: Inspect the actual init() signature to get the real listener type
val initMethod2 = vpmClass.methods.firstOrNull { m ->
@@ -1241,7 +1244,7 @@ object EVHardware {
if (initMethod2 != null) {
val listenerType = initMethod2.parameterTypes[1]
AppLogger.i(TAG, " Katman4: init() found, listener type = ${listenerType.name}")
AppLogger.i(TAG, " Layer4: init() found, listener type = ${listenerType.name}")
// Try dynamic proxy with the actual listener interface type
if (listenerType.isInterface) {
@@ -1251,11 +1254,11 @@ object EVHardware {
) { _, method, _ ->
when (method.name) {
"onServiceConnected" -> {
AppLogger.i(TAG, " Katman4: onServiceConnected ✓")
AppLogger.i(TAG, " Layer4: onServiceConnected ✓")
tryGetVpmService(vpm)
}
"onServiceDisconnected" -> {
AppLogger.w(TAG, " Katman4: onServiceDisconnected")
AppLogger.w(TAG, " Layer4: onServiceDisconnected")
sVpmService = null
}
else -> {}
@@ -1263,53 +1266,53 @@ object EVHardware {
null
}
initMethod2.invoke(vpm, context, proxy)
AppLogger.i(TAG, " Katman4: init(Context, proxy) ✓")
AppLogger.i(TAG, " Layer4: init(Context, proxy) ✓")
return
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: init(Context, proxy) failed: ${e.message}")
AppLogger.d(TAG, " Layer4: init(Context, proxy) failed: ${e.message}")
}
}
// Fallback: try init(Context, null) — works if listener is nullable
try {
initMethod2.invoke(vpm, context, null)
AppLogger.i(TAG, " Katman4: init(Context, null) ✓")
AppLogger.i(TAG, " Layer4: init(Context, null) ✓")
return
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: init(Context, null) failed: ${e.message}")
AppLogger.d(TAG, " Layer4: init(Context, null) failed: ${e.message}")
}
}
// Strategy 2: init(Context) single-param
try {
vpmClass.getMethod("init", Context::class.java).invoke(vpm, context)
AppLogger.i(TAG, " Katman4: init(Context) ✓")
AppLogger.i(TAG, " Layer4: init(Context) ✓")
return
} catch (_: NoSuchMethodException) {
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: init(Context) failed: ${e.message}")
AppLogger.d(TAG, " Layer4: init(Context) failed: ${e.message}")
}
// Strategy 3: init() no-arg
try {
vpmClass.getMethod("init").invoke(vpm)
AppLogger.i(TAG, " Katman4: init() ✓")
AppLogger.i(TAG, " Layer4: init() ✓")
return
} catch (_: NoSuchMethodException) {
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: init() failed: ${e.message}")
AppLogger.d(TAG, " Layer4: init() failed: ${e.message}")
}
AppLogger.w(TAG, " Katman4: no working init() — mIVehiclePropertyService stays null")
AppLogger.w(TAG, " Layer4: no working init() — mIVehiclePropertyService stays null")
}
/** Runs [block], returns the result or null, logs the result/error. */
private fun tryInvoke(label: String, block: () -> Any?): Any? = try {
val r = block()
AppLogger.i(TAG, " Katman4: $label → ${if (r != null) "OK ($r)" else "null"}")
AppLogger.i(TAG, " Layer4: $label → ${if (r != null) "OK ($r)" else "null"}")
r
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: $label → ${e.javaClass.simpleName}: ${e.message}")
AppLogger.d(TAG, " Layer4: $label → ${e.javaClass.simpleName}: ${e.message}")
null
}
@@ -1322,10 +1325,10 @@ object EVHardware {
val svc = f.get(vpm)
if (svc != null) {
sVpmService = svc
AppLogger.i(TAG, " Katman4: mIVehiclePropertyService READY ✓")
// Notify any pending Katman4 listeners
val toNotify = katman4ReadyListeners.toList()
katman4ReadyListeners.clear()
AppLogger.i(TAG, " Layer4: mIVehiclePropertyService READY ✓")
// Notify any pending Layer4 listeners
val toNotify = layer4ReadyListeners.toList()
layer4ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
}
return
@@ -1655,10 +1658,10 @@ object EVHardware {
}
}
// Katman4 SWI68 — VehicleSettingManager via saicmotor.hmi.launcher context
// Layer4 SWI68 — VehicleSettingManager via saicmotor.hmi.launcher context
// -------------------------------------------------------------------------
private fun initKatman4Swi68(context: Context) {
private fun initLayer4Swi68(context: Context) {
if (sVsm != null) return
val launcherCtx: Context
val vsmClass: Class<*>
@@ -1671,7 +1674,7 @@ object EVHardware {
AppLogger.i(TAG, " SWI68: VehicleSettingManager class found ✓")
} catch (e: Exception) {
AppLogger.w(TAG, " SWI68: class load error: ${e.message} — retry in 5s")
Handler(Looper.getMainLooper()).postDelayed({ initKatman4Swi68(context.applicationContext) }, 5_000)
Handler(Looper.getMainLooper()).postDelayed({ initLayer4Swi68(context.applicationContext) }, 5_000)
return
}
@@ -1757,8 +1760,8 @@ object EVHardware {
if (svc != null) {
sVsmService = svc
AppLogger.i(TAG, " SWI68: mVehicleSettingService READY ✓")
val toNotify = katman4ReadyListeners.toList()
katman4ReadyListeners.clear()
val toNotify = layer4ReadyListeners.toList()
layer4ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
}
return
@@ -1767,7 +1770,7 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// Katman4 SWI69/SWI131 — CarVehicleSettingClient via CarAdapterClient
// Layer4 SWI69/SWI131 — CarVehicleSettingClient via CarAdapterClient
//
// Runtime integration contract:
// CarAdapterClient.getInstance(ctx).start()
@@ -1779,7 +1782,7 @@ object EVHardware {
// (getAccTjaState, setAccTjaState, getLasWarningSound, getFcwState, etc.)
// -------------------------------------------------------------------------
private fun initKatman4Swi69(context: Context) {
private fun initLayer4Swi69(context: Context) {
if (sVsm != null) return
val launcherCtx: Context
@@ -1795,7 +1798,7 @@ object EVHardware {
AppLogger.i(TAG, " SWI69: CarAdapterClient + CarVehicleSettingClient classes found ✓")
} catch (e: Exception) {
AppLogger.w(TAG, " SWI69: class load error: ${e.message} — retry in 5s")
Handler(Looper.getMainLooper()).postDelayed({ initKatman4Swi69(context.applicationContext) }, 5_000)
Handler(Looper.getMainLooper()).postDelayed({ initLayer4Swi69(context.applicationContext) }, 5_000)
return
}
@@ -1808,7 +1811,7 @@ object EVHardware {
if (adapter == null) {
AppLogger.w(TAG, " SWI69: CarAdapterClient.getInstance() failed — retry in 10s")
Handler(Looper.getMainLooper()).postDelayed({ initKatman4Swi69(context.applicationContext) }, 10_000)
Handler(Looper.getMainLooper()).postDelayed({ initLayer4Swi69(context.applicationContext) }, 10_000)
return
}
@@ -1878,8 +1881,8 @@ object EVHardware {
sVsmService = ibinder
AppLogger.i(TAG, " SWI69: CarVehicleSettingClient READY ✓")
val toNotify = katman4ReadyListeners.toList()
katman4ReadyListeners.clear()
val toNotify = layer4ReadyListeners.toList()
layer4ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
} catch (e: Exception) {
AppLogger.d(TAG, " SWI69: tryInitClientFromAdapter error: ${e.message}")
@@ -1892,7 +1895,7 @@ object EVHardware {
* [a9ClientBinder] returns null for two entirely different reasons — the code is not served,
* or there is no adapter to ask — and a survey that cannot tell them apart records "absent"
* for a question that was never put to the vehicle. `sCarAdapter` is assigned only by
* `initKatman4Swi69`, so on SWI68 every `queryClient` in a probe fails before it reaches a
* `initLayer4Swi69`, so on SWI68 every `queryClient` in a probe fails before it reaches a
* binder. RI-002 was nearly closed on that false negative; the probe now says so out loud.
*/
internal fun hasCarAdapterClient(): Boolean = sCarAdapter != null && sCarAdapterClass != null
@@ -2185,11 +2188,11 @@ object EVHardware {
when (result) {
is Int -> result
is Number -> result.toInt()
null -> { AppLogger.d(TAG, " Katman4: getMixProperty(0x${propId.toString(16)}) = null"); -1 }
else -> { AppLogger.d(TAG, " Katman4: getMixProperty(0x${propId.toString(16)}) = $result (${result.javaClass.simpleName})"); -1 }
null -> { AppLogger.d(TAG, " Layer4: getMixProperty(0x${propId.toString(16)}) = null"); -1 }
else -> { AppLogger.d(TAG, " Layer4: getMixProperty(0x${propId.toString(16)}) = $result (${result.javaClass.simpleName})"); -1 }
}
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: getMixProperty(0x${propId.toString(16)}) exc: ${e.message}")
AppLogger.d(TAG, " Layer4: getMixProperty(0x${propId.toString(16)}) exc: ${e.message}")
-1
}
}
@@ -2201,10 +2204,10 @@ object EVHardware {
vpm.javaClass
.getMethod("setMixProperty", Class::class.java, Int::class.java, Any::class.java)
.invoke(vpm, Int::class.javaObjectType, propId, value)
AppLogger.i(TAG, " Katman4: setMixProperty(0x${propId.toString(16)}, $value) ✓")
AppLogger.i(TAG, " Layer4: setMixProperty(0x${propId.toString(16)}, $value) ✓")
true
} catch (e: Exception) {
AppLogger.d(TAG, " Katman4: setMixProperty(0x${propId.toString(16)}, $value) exc: ${e.message}")
AppLogger.d(TAG, " Layer4: setMixProperty(0x${propId.toString(16)}, $value) exc: ${e.message}")
false
}
}
@@ -2359,7 +2362,7 @@ object EVHardware {
}
/**
* SAIC proprietary binder transact (Katman2 fallback).
* SAIC proprietary binder transact (Layer2 fallback).
* Runtime parcel contract: [interfaceToken, AREA_GLOBAL, 1, value, float[], byte[]]
*/
private fun binderTransact(binder: IBinder?, descriptor: String, txCode: Int, value: Int): Boolean {
@@ -2532,7 +2535,7 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// ADAS API (Katman4)
// ADAS API (Layer4)
// -------------------------------------------------------------------------
fun overspeedAlarmOnOrNull(): Boolean? {
@@ -2603,7 +2606,7 @@ object EVHardware {
return setIntPropertyVpm(PROP_SPEED_LIMIT_TONE, if (on) 1 else 0)
}
/** Returns 0–4 (Off/Limiter/Auto/ACC/ICA), or -1 if Katman4 not ready. */
/** Returns 0–4 (Off/Limiter/Auto/ACC/ICA), or -1 if Layer4 not ready. */
fun getMixedIntelligentDrive(): Int = getMixIntProperty(PROP_MIX_INTELLIGENT_DRIVE)
@RequiresStandstill
fun setMixedIntelligentDrive(value: Int): Boolean {
@@ -3161,7 +3164,7 @@ object EVHardware {
if (!hasDrowsinessAndEsc()) return false
// [IgnitionState], not [CarIgnitionItem]: getCurrentIgnitionState() answers on the
// AAOS scale, where the two do not line up — RUN is 2 for Katman5 and OFF is 2 here.
// AAOS scale, where the two do not line up — RUN is 2 for Layer5 and OFF is 2 here.
// Unreadable is 0 or -1 and is deliberately not a refusal, per the KDoc above.
val ignition = getCurrentIgnitionState()
if (ignition > 0 && ignition != IgnitionState.ON) {
@@ -3439,10 +3442,10 @@ object EVHardware {
}
}
fun isKatman4Ready(): Boolean =
fun isLayer4Ready(): Boolean =
if (FirmwareInfo.isVsmBased()) sVsm != null && sVsmService != null
else sVpm != null && sVpmService != null
fun isKatman4VpmCreated(): Boolean = sVpm != null || sVsm != null
fun isLayer4VpmCreated(): Boolean = sVpm != null || sVsm != null
fun isCarPropertyManagerReady(): Boolean = sCarPropertyManager != null
fun isCarHvacManagerReady(): Boolean = sCarHvacManager != null
@@ -3540,7 +3543,7 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// Katman5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// Layer5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// -------------------------------------------------------------------------
private data class Swi69Ctx(
@@ -3566,16 +3569,16 @@ object EVHardware {
return null
}
private fun initKatman5Swi69(context: Context) {
private fun initLayer5Swi69(context: Context) {
if (sVcmCallbackRegistered) return
val classes = findSwi69Classes(context) ?: run {
AppLogger.w(TAG, " Katman5 SWI69: CarAdapterClient/CarGeneralClient not found — retry in 10s")
Handler(Looper.getMainLooper()).postDelayed({ initKatman5Swi69(context.applicationContext) }, 10_000)
AppLogger.w(TAG, " Layer5 SWI69: CarAdapterClient/CarGeneralClient not found — retry in 10s")
Handler(Looper.getMainLooper()).postDelayed({ initLayer5Swi69(context.applicationContext) }, 10_000)
return
}
val (_, adapterClass, generalClientClass) = classes
AppLogger.i(TAG, " Katman5 SWI69: classes loaded ✓")
AppLogger.i(TAG, " Layer5 SWI69: classes loaded ✓")
fun trySetupGeneralClient(): Boolean {
if (sVcmCallbackRegistered) return true
@@ -3589,14 +3592,14 @@ object EVHardware {
adapterClass.getMethod("queryClient", Int::class.javaPrimitiveType!!)
.invoke(adapter, BIND_CODE_CAR_GENERAL) as? IBinder
} catch (_: Exception) { null } ?: run {
AppLogger.d(TAG, " Katman5 SWI69: queryClient(0x${BIND_CODE_CAR_GENERAL.toString(16)}) → null")
AppLogger.d(TAG, " Layer5 SWI69: queryClient(0x${BIND_CODE_CAR_GENERAL.toString(16)}) → null")
return false
}
val client = try {
generalClientClass.getConstructor(IBinder::class.java).newInstance(ibinder)
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5 SWI69: CarGeneralClient ctor error: ${e.message}")
AppLogger.w(TAG, " Layer5 SWI69: CarGeneralClient ctor error: ${e.message}")
return false
}
sCarGeneral = client // kept for A9 screen brightness (setScreenBrightness)
@@ -3604,13 +3607,13 @@ object EVHardware {
val registMethod = generalClientClass.methods.firstOrNull {
it.name == "registListener" && it.parameterCount == 1
} ?: run {
AppLogger.w(TAG, " Katman5 SWI69: registListener not found")
AppLogger.w(TAG, " Layer5 SWI69: registListener not found")
return false
}
val callbackType = registMethod.parameterTypes[0]
if (!callbackType.isInterface) {
AppLogger.w(TAG, " Katman5 SWI69: callback non-interface — proxy impossible")
AppLogger.w(TAG, " Layer5 SWI69: callback non-interface — proxy impossible")
return false
}
@@ -3629,7 +3632,7 @@ object EVHardware {
0x7 -> { // TRANSACTION_onIgnitionStateChange
data.enforceInterface("com.saicmotor.carapi.general.ICarGeneralCallback")
val ignition = data.readInt()
AppLogger.i(TAG, " Katman5 SWI69: onTransact ignition=$ignition (${carIgnitionName(ignition)})")
AppLogger.i(TAG, " Layer5 SWI69: onTransact ignition=$ignition (${carIgnitionName(ignition)})")
dispatchVehicleConditionIgnition(ignition)
reply?.writeNoException()
true
@@ -3648,7 +3651,7 @@ object EVHardware {
// In-process path (rare) — the service calls the interface directly
val ignition = args?.get(0) as? Int
if (ignition != null) {
AppLogger.i(TAG, " Katman5 SWI69: ignition=$ignition (${carIgnitionName(ignition)})")
AppLogger.i(TAG, " Layer5 SWI69: ignition=$ignition (${carIgnitionName(ignition)})")
dispatchVehicleConditionIgnition(ignition)
}
}
@@ -3657,7 +3660,7 @@ object EVHardware {
null
}
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5 SWI69: proxy creation error: ${e.message}")
AppLogger.w(TAG, " Layer5 SWI69: proxy creation error: ${e.message}")
return false
}
@@ -3665,27 +3668,27 @@ object EVHardware {
registMethod.invoke(client, proxy)
sVcmListener = callbackBinder // strong reference to the Binder to avoid GC
sVcmCallbackRegistered = true
AppLogger.i(TAG, " Katman5 SWI69: ICarGeneralCallback registered ✓")
AppLogger.i(TAG, " Layer5 SWI69: ICarGeneralCallback registered ✓")
val toNotify = katman5ReadyListeners.toList()
katman5ReadyListeners.clear()
val toNotify = layer5ReadyListeners.toList()
layer5ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
Handler(Looper.getMainLooper()).postDelayed({
try {
val ignition = generalClientClass.getMethod("getIgnitionState").invoke(client) as? Int
if (ignition != null) {
AppLogger.i(TAG, " Katman5 SWI69: initial state ignition=$ignition")
AppLogger.i(TAG, " Layer5 SWI69: initial state ignition=$ignition")
dispatchVehicleConditionIgnition(ignition)
}
} catch (e: Exception) {
AppLogger.d(TAG, " Katman5 SWI69: getIgnitionState error: ${e.message}")
AppLogger.d(TAG, " Layer5 SWI69: getIgnitionState error: ${e.message}")
}
}, 500L)
true
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5 SWI69: registListener error: ${e.message}")
AppLogger.w(TAG, " Layer5 SWI69: registListener error: ${e.message}")
false
}
}
@@ -3699,11 +3702,11 @@ object EVHardware {
}
// -------------------------------------------------------------------------
// Katman5 — VehicleConditionManager (IVehicleConditionService via IHubService)
// Layer5 — VehicleConditionManager (IVehicleConditionService via IHubService)
// SWI133 / SWI68 / SWI165
// -------------------------------------------------------------------------
private fun initKatman5(context: Context) {
private fun initLayer5(context: Context) {
if (sVcmCallbackRegistered) return
val launcherCtx = listOf(LAUNCHER68_PKG, LAUNCHER69_PKG).firstNotNullOfOrNull { pkg ->
@@ -3714,15 +3717,15 @@ object EVHardware {
)
} catch (_: Exception) { null }
} ?: run {
AppLogger.w(TAG, " Katman5: launcher package not found")
AppLogger.w(TAG, " Layer5: launcher package not found")
return
}
val vcmClass = try {
launcherCtx.classLoader.loadClass(VCM_CLASS)
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5: VCM class not found: ${e.message} — retry in 10s")
Handler(Looper.getMainLooper()).postDelayed({ initKatman5(context.applicationContext) }, 10_000)
AppLogger.w(TAG, " Layer5: VCM class not found: ${e.message} — retry in 10s")
Handler(Looper.getMainLooper()).postDelayed({ initLayer5(context.applicationContext) }, 10_000)
return
}
@@ -3734,7 +3737,7 @@ object EVHardware {
} catch (_: Exception) { null }
if (existing != null) {
AppLogger.i(TAG, " Katman5: VCM singleton already exists ✓")
AppLogger.i(TAG, " Layer5: VCM singleton already exists ✓")
sVcm = existing
setupVcmCallback(existing, launcherCtx)
return
@@ -3755,7 +3758,7 @@ object EVHardware {
) { _, method, args ->
when (method.name) {
"onServiceConnected" -> {
AppLogger.i(TAG, " Katman5: onServiceConnected ✓")
AppLogger.i(TAG, " Layer5: onServiceConnected ✓")
val mgr = args?.getOrNull(0)
?.takeIf { it.javaClass.name.contains("VehicleConditionManager") }
val instance = mgr ?: try {
@@ -3769,7 +3772,7 @@ object EVHardware {
}
}
"onServiceDisconnected" -> {
AppLogger.w(TAG, " Katman5: onServiceDisconnected")
AppLogger.w(TAG, " Layer5: onServiceDisconnected")
sVcmCallbackRegistered = false
sVcmListener = null
}
@@ -3777,19 +3780,19 @@ object EVHardware {
null
}
} catch (e: Exception) {
AppLogger.d(TAG, " Katman5: proxy init error: ${e.message}")
AppLogger.d(TAG, " Layer5: proxy init error: ${e.message}")
null
}
} else null
try {
initMethod.invoke(null, context.applicationContext, listenerArg)
AppLogger.i(TAG, " Katman5: VehicleConditionManager.init() called")
AppLogger.i(TAG, " Layer5: VehicleConditionManager.init() called")
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5: init() error: ${e.message}")
AppLogger.w(TAG, " Layer5: init() error: ${e.message}")
}
} else {
AppLogger.w(TAG, " Katman5: init(Context, listener) not found")
AppLogger.w(TAG, " Layer5: init(Context, listener) not found")
}
// Retries — the singleton is available after the async connection
@@ -3803,7 +3806,7 @@ object EVHardware {
f.get(null)
} catch (_: Exception) { null }
if (mgr != null && sVcm == null) {
AppLogger.i(TAG, " Katman5: singleton obtained @${delay}ms")
AppLogger.i(TAG, " Layer5: singleton obtained @${delay}ms")
sVcm = mgr
setupVcmCallback(mgr, launcherCtx)
} else if (sVcm != null && !sVcmCallbackRegistered) {
@@ -3824,14 +3827,14 @@ object EVHardware {
m.parameterTypes[0].isInterface &&
m.parameterTypes[0].methods.any { it.name.contains("ConditionChange", ignoreCase = true) }
} ?: run {
AppLogger.w(TAG, " Katman5: registerVehicleConditionCallback not found — methods: ${
AppLogger.w(TAG, " Layer5: registerVehicleConditionCallback not found — methods: ${
vcm.javaClass.methods.filter { it.name.startsWith("register") }.joinToString { it.name }
}")
return
}
val callbackType = registerMethod.parameterTypes[0]
AppLogger.i(TAG, " Katman5: callback type = ${callbackType.name}")
AppLogger.i(TAG, " Layer5: callback type = ${callbackType.name}")
val proxy = try {
java.lang.reflect.Proxy.newProxyInstance(
@@ -3846,18 +3849,18 @@ object EVHardware {
// moved. Only the transitions are logged; every event is still
// dispatched, because suppressing one would change behaviour.
if (sIgnitionLog.accept(ignition)) {
AppLogger.i(TAG, " Katman5 event: ignition=$ignition (${carIgnitionName(ignition)})")
AppLogger.i(TAG, " Layer5 event: ignition=$ignition (${carIgnitionName(ignition)})")
}
dispatchVehicleConditionIgnition(ignition)
}
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5: onChangeEvent parse error: ${e.message}")
AppLogger.w(TAG, " Layer5: onChangeEvent parse error: ${e.message}")
}
}
null
}
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5: proxy creation error: ${e.message}")
AppLogger.w(TAG, " Layer5: proxy creation error: ${e.message}")
return
}
@@ -3865,10 +3868,10 @@ object EVHardware {
registerMethod.invoke(vcm, proxy)
sVcmListener = proxy
sVcmCallbackRegistered = true
AppLogger.i(TAG, " Katman5: callback registered ✓")
AppLogger.i(TAG, " Layer5: callback registered ✓")
val toNotify = katman5ReadyListeners.toList()
katman5ReadyListeners.clear()
val toNotify = layer5ReadyListeners.toList()
layer5ReadyListeners.clear()
Handler(Looper.getMainLooper()).post { toNotify.forEach { it() } }
// Immediate read (the callback only fires on CHANGE)
@@ -3877,13 +3880,13 @@ object EVHardware {
vcm.javaClass.getMethod("getVehicleIgnition").invoke(vcm) as? Int
} catch (_: Exception) { null }
if (ignition != null) {
AppLogger.i(TAG, " Katman5: initial state = $ignition (${carIgnitionName(ignition)})")
AppLogger.i(TAG, " Layer5: initial state = $ignition (${carIgnitionName(ignition)})")
dispatchVehicleConditionIgnition(ignition)
}
}, 500L)
} catch (e: Exception) {
AppLogger.w(TAG, " Katman5: registerVehicleConditionCallback error: ${e.message}")
AppLogger.w(TAG, " Layer5: registerVehicleConditionCallback error: ${e.message}")
}
}
@@ -3938,15 +3941,15 @@ object EVHardware {
sb.appendLine()
sb.appendLine("── Services ──")
sb.appendLine("Katman1 CPM : ${if (sCarPropertyManager != null) "✓" else "✗"}")
sb.appendLine("Katman1 HVAC : ${if (sCarHvacManager != null) "✓" else "✗"}")
sb.appendLine("Katman4 created : ${if (isKatman4VpmCreated()) "✓" else "✗"}")
sb.appendLine("Katman4 ready : ${if (isKatman4Ready()) "✓" else "✗"}")
sb.appendLine("Layer1 CPM : ${if (sCarPropertyManager != null) "✓" else "✗"}")
sb.appendLine("Layer1 HVAC : ${if (sCarHvacManager != null) "✓" else "✗"}")
sb.appendLine("Layer4 created : ${if (isLayer4VpmCreated()) "✓" else "✗"}")
sb.appendLine("Layer4 ready : ${if (isLayer4Ready()) "✓" else "✗"}")
sb.appendLine("Binder vsett : ${if (sVehicleBinder != null) "✓ OK" else "✗ null"}")
val katman5Path = if (FirmwareInfo.isNewGenVsm() || gen == FirmwareInfo.Gen.SWI132)
val layer5Path = if (FirmwareInfo.isNewGenVsm() || gen == FirmwareInfo.Gen.SWI132)
"ICarGeneralService" else "VehicleConditionMgr"
sb.appendLine("Katman5 ready : ${if (sVcmCallbackRegistered) "✓ ($katman5Path)" else "✗ ($katman5Path)"}")
sb.appendLine("Katman5 ign : ${carIgnitionName(sLastVcmIgnitionState)} ($sLastVcmIgnitionState)")
sb.appendLine("Layer5 ready : ${if (sVcmCallbackRegistered) "✓ ($layer5Path)" else "✗ ($layer5Path)"}")
sb.appendLine("Layer5 ign : ${carIgnitionName(sLastVcmIgnitionState)} ($sLastVcmIgnitionState)")
sb.appendLine()
if (gen == FirmwareInfo.Gen.SWI132) {
@@ -4013,7 +4016,7 @@ object EVHardware {
}
sb.appendLine()
// ── CarVehicleSettingClient (Katman4) ─────────────────────────
// ── CarVehicleSettingClient (Layer4) ─────────────────────────
sb.appendLine("── SWI132 CarVehicleSettingClient (sVsm=${if (sVsm != null) "✓" else "✗ null"}) ──")
fun vsmGet(method: String): Int = try {
(callVsm(method) as? Int) ?: -1
@@ -4177,7 +4180,7 @@ object EVHardware {
fun initAudio(context: Context) {
// The caradapter bind only concerns the A9. On the old SDK, loudness goes through
// SmartSoundManager (initialised in the Katman4 flow), so nothing to bind here.
// SmartSoundManager (initialised in the Layer4 flow), so nothing to bind here.
if (!isA9Sound()) return
if (sAudioHelper?.isBinderAlive == true) return
if (sAudioServiceConn != null) return
@@ -95,7 +95,7 @@ object GlassProbe {
glass: Glass = VehicleGlass,
stopped: () -> Boolean = { VehicleWriteGate.decideNow() == VehicleWriteGate.Decision.ALLOWED },
// [IgnitionState], not [CarIgnitionItem]: getCurrentIgnitionState() answers on the AAOS
// scale, and the two do not line up — RUN is 2 for Katman5, where 2 is OFF here. Read
// scale, and the two do not line up — RUN is 2 for Layer5, where 2 is OFF here. Read
// against the wrong one, a car with the ignition on refused the probe as if it were off.
ignitionRun: () -> Boolean = { EVHardware.getCurrentIgnitionState() == EVHardware.IgnitionState.ON },
sleep: (Long) -> Unit = { Thread.sleep(it) },
@@ -10,7 +10,7 @@ data class DrivingProfile(
val steeringHeat: Boolean = false,
val seatHeatLeft: Int = 0, // 0=off, 1, 2, 3
val seatHeatRight: Int = 0,
// ADAS SWI133 (Katman4) — default OFF for compatibility with existing profiles
// ADAS SWI133 (Layer4) — default OFF for compatibility with existing profiles
val overspeedAlarm: Boolean = false,
val speedLimitTone: Boolean = false,
val adasMode: Int = 0, // 0=Off, 1=Limiter, 2=Auto, 3=ACC, 4=ICA
@@ -471,7 +471,7 @@ object RuntimeInterfaceProbe {
*/
internal const val NO_ADAPTER_CLIENT =
"no CarAdapterClient on this firmware — sCarAdapter is assigned only by " +
"initKatman4Swi69, so this call failed before it reached a binder. ABSENT here is " +
"initLayer4Swi69, so this call failed before it reached a binder. ABSENT here is " +
"not a reading of the vehicle."
/**
+3 -2
View File
@@ -145,8 +145,9 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
`GlassProbeTest`). Comments, log texts and test names only: no code changed (checked in the
diff). Kept on purpose: the French translation `values-fr/strings.xml`, place names in test
data, the quoted French sentence in `ChargeStopPlan` and the voice phrase in `SaicNavGuidance`,
and "Katman" (Turkish for layer), which names the layers in function names and logs — now
explained at the top of `EVHardware.kt`. EVHardware, app tests and lint pass.
and "Katman" (Turkish for layer), which named the layers in function names and logs —
renamed to "Layer" right after (`whenLayer1Ready`, `initLayer4Swi69`, `Layer5: …` in the log,
and so on; nothing outside EVHardware used the names). EVHardware, app tests and lint pass.
- 2026-10-25 — README.md brought up to date: the tab screenshots retaken with 2.35.2 on the
emulator right after installing (`screenshots/nilsmg4-2.35.2-*.png`, the 2.18.1 ones removed),
"read-only" now names the drive mode exception, the upload frequency matches the app (1 min by