From 1f910d83ebc99dcd4a1ab64f610aa0e50e01fbb8 Mon Sep 17 00:00:00 2001 From: tukks Date: Thu, 5 Mar 2026 21:42:07 +0100 Subject: [PATCH 1/6] Refactor OnyxSdkLightsController to improve handling of brightness types and streamline SDK bridging Move ONYX_GO7 to use the SDK --- .../koreader/launcher/device/LightsFactory.kt | 4 +- .../device/lights/OnyxSdkLightsController.kt | 318 +++++++++++++----- 2 files changed, 229 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt index f5d081438..dd868b785 100644 --- a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt +++ b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt @@ -17,7 +17,6 @@ object LightsFactory { DeviceInfo.Id.ONYX_GALILEO2, DeviceInfo.Id.ONYX_GO_COLOR7, DeviceInfo.Id.ONYX_GO6, - DeviceInfo.Id.ONYX_GO7, DeviceInfo.Id.ONYX_GO7GEN2, DeviceInfo.Id.ONYX_NOTE_AIR_3C, DeviceInfo.Id.ONYX_NOTE_AIR_4C, @@ -82,7 +81,8 @@ object LightsFactory { DeviceInfo.Id.ONYX_POKE4LITE, DeviceInfo.Id.ONYX_TAB_ULTRA, DeviceInfo.Id.STORYTEL_READER2, - -> { + DeviceInfo.Id.ONYX_GO7, + -> { logController("Onyx/Sdk") OnyxSdkLightsController() } diff --git a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt index dbe6d545c..40a54876e 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt @@ -1,150 +1,286 @@ package org.koreader.launcher.device.lights import android.app.Activity +import android.content.Context import android.util.Log import org.koreader.launcher.device.LightsInterface -import android.content.Context -import java.lang.Class.forName import java.lang.reflect.Method +// ─── Constants mirroring FrontLightController / BaseBrightnessProvider ──────── + +private const val LIGHT_TYPE_FL = 1 // FLBrightnessProvider — single channel +private const val LIGHT_TYPE_CTM_WARM = 2 // WarmBrightnessProvider — warm channel +private const val LIGHT_TYPE_CTM_COLD = 3 // ColdBrightnessProvider — cold channel +private const val LIGHT_TYPE_CTM_ALL = 4 // open/close the whole CTM unit +private const val LIGHT_TYPE_TEMP = 6 // CTMTemperatureProvider — CTM warmth param +private const val LIGHT_TYPE_CTM_BR = 7 // CTMBrightnessProvider — CTM brightness param + +// Mirrors BrightnessType enum +enum class OnyxBrightnessType { FL, WARM_AND_COLD, CTM, NONE } + +// ─── Controller ─────────────────────────────────────────────────────────────── + class OnyxSdkLightsController : LightsInterface { + companion object { private const val TAG = "Lights" - private const val BRIGHTNESS_MAX = 255 - private const val WARMTH_MAX = 255 private const val MIN = 0 + private const val FALLBACK_MAX = 255 } - override fun getPlatform(): String { - return "onyx-sdk-lights" - } - - override fun hasFallback(): Boolean { - return false - } + override fun getPlatform(): String = "onyx-sdk-lights" + override fun hasFallback(): Boolean = false + override fun needsPermission(): Boolean = false + override fun hasStandaloneWarmth(): Boolean = false override fun hasWarmth(): Boolean { - return true - } + return when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD, + OnyxBrightnessType.CTM -> true - override fun needsPermission(): Boolean { - return false + else -> !OnyxDevice.isInitialized // optimistic before init + } } + // ── Read ────────────────────────────────────────────────────────────────── + override fun getBrightness(activity: Activity): Int { - return FrontLight.getCold(activity) + OnyxDevice.init(activity) + return when (OnyxDevice.brightnessType) { + OnyxBrightnessType.FL -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_FL) ?: 0 + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_COLD) ?: 0 + OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_BR) ?: 0 + OnyxBrightnessType.NONE -> 0 + } } override fun getWarmth(activity: Activity): Int { - return FrontLight.getWarm(activity) + OnyxDevice.init(activity) + return when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_WARM) ?: 0 + OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_TEMP) ?: 0 + else -> 0 + } } + // ── Write ───────────────────────────────────────────────────────────────── + override fun setBrightness(activity: Activity, brightness: Int) { - if (brightness < MIN || brightness > BRIGHTNESS_MAX) { - Log.w(TAG, "brightness value of of range: $brightness") + OnyxDevice.init(activity) + val max = getMaxBrightness() + if (brightness < MIN || brightness > max) { + Log.w(TAG, "brightness out of range: $brightness (max=$max)") return } - Log.v(TAG, "Setting brightness to $brightness") - FrontLight.setCold(brightness, activity) + Log.v(TAG, "setBrightness=$brightness type=${OnyxDevice.brightnessType}") + when (OnyxDevice.brightnessType) { + OnyxBrightnessType.FL -> + OnyxDevice.setLight(activity, LIGHT_TYPE_FL, brightness) + + OnyxBrightnessType.WARM_AND_COLD -> + OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_COLD, brightness) + + OnyxBrightnessType.CTM -> + OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_BR, brightness) + + OnyxBrightnessType.NONE -> Unit + } } override fun setWarmth(activity: Activity, warmth: Int) { - if (warmth < MIN || warmth > WARMTH_MAX) { - Log.w(TAG, "warmth value of of range: $warmth") + OnyxDevice.init(activity) + val max = getMaxWarmth() + if (warmth < MIN || warmth > max) { + Log.w(TAG, "warmth out of range: $warmth (max=$max)") return } - Log.v(TAG, "Setting warmth to $warmth") - FrontLight.setWarm(warmth, activity) - } + Log.v(TAG, "setWarmth=$warmth type=${OnyxDevice.brightnessType}") + when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD -> + OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_WARM, warmth) - override fun getMinWarmth(): Int { - return MIN - } + OnyxBrightnessType.CTM -> + OnyxDevice.setLight(activity, LIGHT_TYPE_TEMP, warmth) - override fun getMaxWarmth(): Int { - return WARMTH_MAX + else -> Unit + } } - override fun getMinBrightness(): Int { - return MIN - } + // ── Range ───────────────────────────────────────────────────────────────── - override fun getMaxBrightness(): Int { - return BRIGHTNESS_MAX - } + override fun getMinBrightness(): Int = MIN + override fun getMinWarmth(): Int = MIN - override fun enableFrontlightSwitch(activity: Activity): Int { - return 1 + override fun getMaxBrightness(): Int = when (OnyxDevice.brightnessType) { + OnyxBrightnessType.FL -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_FL) ?: FALLBACK_MAX + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_COLD) ?: FALLBACK_MAX + OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_BR) ?: FALLBACK_MAX + OnyxBrightnessType.NONE -> FALLBACK_MAX } - override fun hasStandaloneWarmth(): Boolean { - return false + override fun getMaxWarmth(): Int = when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_WARM) ?: FALLBACK_MAX + OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_TEMP) ?: FALLBACK_MAX + else -> FALLBACK_MAX } + + override fun enableFrontlightSwitch(activity: Activity): Int = 1 } -object FrontLight { - private const val TAG = "lights" +// ─── Low-level SDK bridge ───────────────────────────────────────────────────── - private val flController: Class<*>? = try { - forName("android.onyx.hardware.DeviceController") - } catch (e: Exception) { - Log.w(TAG, "$e") - null - } +object OnyxDevice { + private const val TAG = "OnyxDevice" - private val setWarmBrightness: Method? = try { - flController!!.getMethod("setWarmLightDeviceValue", Context::class.java, Integer.TYPE) + private val controller: Class<*>? = try { + Class.forName("android.onyx.hardware.DeviceController") } catch (e: Exception) { - Log.w(TAG, "$e") - null + Log.w(TAG, "DeviceController not found: $e"); null } - private val setColdBrightness: Method? = try { - flController!!.getMethod("setColdLightDeviceValue", Context::class.java, Integer.TYPE) - } catch (e: Exception) { - Log.w(TAG, "$e") - null + + // Integer getLightValue(int type) + private val mGetLightValue: Method? = method("getLightValue", Integer.TYPE) + ?: method("getLightValues", Integer.TYPE) + + // Integer getMaxLightValue(int type) + private val mGetMaxLightValue: Method? = method("getMaxLightValue", Integer.TYPE) + ?: method("getMaxLightValues", Integer.TYPE) + + // boolean setLightValue(int type, int value) + private val mSetLightValue: Method? = method("setLightValue", Integer.TYPE, Integer.TYPE) + ?: method("setLightValues", Integer.TYPE, Integer.TYPE) + + // boolean openFrontLight(int type) + private val mOpenFrontLight: Method? = method("openFrontLight", Integer.TYPE) + + // boolean closeFrontLight(int type) + private val mCloseFrontLight: Method? = method("closeFrontLight", Integer.TYPE) + + // boolean isLightOn(int type) + private val mIsLightOn: Method? = method("isLightOn", Context::class.java, Integer.TYPE) + ?: method("isLightOn", Integer.TYPE) + + // boolean hasFLBrightness(Context) + private val mHasFLBrightness: Method? = method("hasFLBrightness", Context::class.java) + + // boolean hasCTMBrightness(Context) + private val mHasCTMBrightness: Method? = method("hasCTMBrightness", Context::class.java) + + // boolean checkCTM() + private val mCheckCTM: Method? = method("checkCTM") + + var brightnessType: OnyxBrightnessType = OnyxBrightnessType.NONE + private set + + var isInitialized = false + private set + + /** + * Call once from Activity.onCreate. + * Mirrors BrightnessController.initProviderMap() priority order: + * checkCTM() → CTM (open/close via type 4, read/write via types 6+7) + * hasCTMBrightness → WARM_AND_COLD (types 2+3) + * hasFLBrightness → FL (type 1) + */ + fun init(context: Context) { + // Allow re-initialization if we are currently in a state without warmth, + // to handle cases where detection might be state-dependent (like "OFF" vs "Custom" mode). + if (isInitialized && brightnessType != OnyxBrightnessType.NONE && brightnessType != OnyxBrightnessType.FL) return + + val checkCTM = mCheckCTM?.invoke(controller) as? Boolean ?: false + val hasFL = mHasFLBrightness?.invoke(controller, context) as? Boolean ?: false + val hasCTM = mHasCTMBrightness?.invoke(controller, context) as? Boolean ?: false + + // Check actual hardware channel availability as fallback/confirmation + val maxCTM = getMaxLightValue(LIGHT_TYPE_TEMP) ?: 0 + val maxWarm = getMaxLightValue(LIGHT_TYPE_CTM_WARM) ?: 0 + + val oldType = brightnessType + brightnessType = when { + checkCTM || maxCTM > 0 -> OnyxBrightnessType.CTM + hasCTM || maxWarm > 0 -> OnyxBrightnessType.WARM_AND_COLD + hasFL -> OnyxBrightnessType.FL + else -> OnyxBrightnessType.NONE + } + + if (brightnessType != OnyxBrightnessType.NONE) { + isInitialized = true + } + + if (oldType != brightnessType) { + Log.d( + TAG, + "Detection: checkCTM=$checkCTM hasCTM=$hasCTM hasFL=$hasFL maxCTM=$maxCTM maxWarm=$maxWarm → $brightnessType" + ) + } } - private val getCoolWarmBrightness: Method? = try { - flController!!.getMethod("getBrightnessConfig", Context::class.java, Integer.TYPE) - } catch (e: Exception) { - Log.w(TAG, "$e") - null + // ── Reads ───────────────────────────────────────────────────────────────── + + fun getLightValue(context: Context, type: Int): Int? = mGetLightValue?.invoke(controller, type) as? Int + fun getMaxLightValue(type: Int): Int? { + val max = mGetMaxLightValue?.invoke(controller, type) as? Number + return if (max == null || max.toInt() == 0) null else max.toInt() } - private const val BRIGHTNESS_CONFIG_WARM_IDX: Int = 2 - private const val BRIGHTNESS_CONFIG_COLD_IDX: Int = 3 - - fun getWarm(context: Context?): Int { - return try { - getCoolWarmBrightness!!.invoke(flController!!, context, BRIGHTNESS_CONFIG_WARM_IDX) as Int - } catch (e: Exception) { - e.printStackTrace() - 0 + + fun isLightOn(context: Context, type: Int): Boolean = mIsLightOn?.let { method -> + if (method.parameterTypes.size == 2) { + method.invoke(controller, context, type) + } else { + method.invoke(controller, type) } - } + } as? Boolean ?: false - fun getCold(context: Context?): Int { - return try { - getCoolWarmBrightness!!.invoke(flController!!, context, BRIGHTNESS_CONFIG_COLD_IDX) as Int - } catch (e: Exception) { - e.printStackTrace() - 0 + // ── Write ───────────────────────────────────────────────────────────────── + + fun setLight(context: Context, type: Int, value: Int): Boolean { + // Map logical parameter type → physical channel type for open/close. + // For CTM, type 4 (ALL) controls the master switch. + // For WARM_AND_COLD, channels 2 and 3 are independent. + val channelType = when (type) { + LIGHT_TYPE_CTM_BR, + LIGHT_TYPE_TEMP -> LIGHT_TYPE_CTM_ALL // CTM: always open/close the whole unit + else -> type // FL / WARM / COLD: direct channel } - } - fun setWarm(value: Int, context: Context?) { - try { - setWarmBrightness!!.invoke(flController!!, context, value) - } catch (e: Exception) { - e.printStackTrace() + return if (value == 0) { + // Only close the master switch if it's CTM brightness (7) or FL brightness (1). + // Closing warmth channel (2 or 6) shouldn't turn off all lights. + val shouldClose = when (type) { + LIGHT_TYPE_FL, + LIGHT_TYPE_CTM_BR, + LIGHT_TYPE_CTM_COLD, + LIGHT_TYPE_CTM_WARM -> true + + else -> false + } + + if (shouldClose) { + val ok = mCloseFrontLight?.invoke(controller, channelType) as? Boolean ?: false + Log.v(TAG, "closeFrontLight(channelType=$channelType) → $ok") + ok + } else { + // For warmth, just set value to 0 + val ok = mSetLightValue?.invoke(controller, type, 0) as? Boolean ?: false + Log.v(TAG, "setLightValue(type=$type, value=0) → $ok") + ok + } + } else { + if (!isLightOn(context, channelType)) { + val opened = mOpenFrontLight?.invoke(controller, channelType) as? Boolean ?: false + Log.v(TAG, "openFrontLight(channelType=$channelType) → $opened") + } + val ok = mSetLightValue?.invoke(controller, type, value) as? Boolean ?: false + Log.v(TAG, "setLightValue(type=$type, value=$value) → $ok") + ok } } - fun setCold(value: Int, context: Context?) { - try { - setColdBrightness!!.invoke(flController!!, context, value) - } catch (e: Exception) { - e.printStackTrace() - } + // ── Helpers ─────────────────────────────────────────────────────────────── + + private fun method(name: String, vararg params: Class<*>): Method? = try { + controller?.getMethod(name, *params) + } catch (e: Exception) { + Log.w(TAG, "Method '$name' not found: $e"); null } } From 7a6eb5de906ee9350c04c1d3176e0367bf3ff0f9 Mon Sep 17 00:00:00 2001 From: tukks Date: Thu, 5 Mar 2026 23:00:17 +0100 Subject: [PATCH 2/6] fix build warning --- .../device/lights/OnyxSdkLightsController.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt index 40a54876e..59b3b36de 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt @@ -47,9 +47,9 @@ class OnyxSdkLightsController : LightsInterface { override fun getBrightness(activity: Activity): Int { OnyxDevice.init(activity) return when (OnyxDevice.brightnessType) { - OnyxBrightnessType.FL -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_FL) ?: 0 - OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_COLD) ?: 0 - OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_BR) ?: 0 + OnyxBrightnessType.FL -> OnyxDevice.getLightValue(LIGHT_TYPE_FL) ?: 0 + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(LIGHT_TYPE_CTM_COLD) ?: 0 + OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(LIGHT_TYPE_CTM_BR) ?: 0 OnyxBrightnessType.NONE -> 0 } } @@ -57,8 +57,8 @@ class OnyxSdkLightsController : LightsInterface { override fun getWarmth(activity: Activity): Int { OnyxDevice.init(activity) return when (OnyxDevice.brightnessType) { - OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_CTM_WARM) ?: 0 - OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(activity, LIGHT_TYPE_TEMP) ?: 0 + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getLightValue(LIGHT_TYPE_CTM_WARM) ?: 0 + OnyxBrightnessType.CTM -> OnyxDevice.getLightValue(LIGHT_TYPE_TEMP) ?: 0 else -> 0 } } @@ -217,7 +217,7 @@ object OnyxDevice { // ── Reads ───────────────────────────────────────────────────────────────── - fun getLightValue(context: Context, type: Int): Int? = mGetLightValue?.invoke(controller, type) as? Int + fun getLightValue(type: Int): Int? = mGetLightValue?.invoke(controller, type) as? Int fun getMaxLightValue(type: Int): Int? { val max = mGetMaxLightValue?.invoke(controller, type) as? Number return if (max == null || max.toInt() == 0) null else max.toInt() From 8be9ad48a59b53aeef80a4473af3ee8af6d235f0 Mon Sep 17 00:00:00 2001 From: tukks Date: Fri, 6 Mar 2026 12:35:17 +0100 Subject: [PATCH 3/6] Refactor OnyxSdkLightsController: extract brightness detection logic, streamline light control, and encapsulate SDK bridging --- .../device/lights/OnyxSdkLightsController.kt | 381 +++++++++++------- 1 file changed, 233 insertions(+), 148 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt index 59b3b36de..37dfbd007 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt @@ -8,14 +8,13 @@ import java.lang.reflect.Method // ─── Constants mirroring FrontLightController / BaseBrightnessProvider ──────── -private const val LIGHT_TYPE_FL = 1 // FLBrightnessProvider — single channel -private const val LIGHT_TYPE_CTM_WARM = 2 // WarmBrightnessProvider — warm channel -private const val LIGHT_TYPE_CTM_COLD = 3 // ColdBrightnessProvider — cold channel -private const val LIGHT_TYPE_CTM_ALL = 4 // open/close the whole CTM unit -private const val LIGHT_TYPE_TEMP = 6 // CTMTemperatureProvider — CTM warmth param -private const val LIGHT_TYPE_CTM_BR = 7 // CTMBrightnessProvider — CTM brightness param - -// Mirrors BrightnessType enum +private const val LIGHT_TYPE_FL = 1 +private const val LIGHT_TYPE_CTM_WARM = 2 +private const val LIGHT_TYPE_CTM_COLD = 3 +private const val LIGHT_TYPE_CTM_ALL = 4 +private const val LIGHT_TYPE_TEMP = 6 +private const val LIGHT_TYPE_CTM_BR = 7 + enum class OnyxBrightnessType { FL, WARM_AND_COLD, CTM, NONE } // ─── Controller ─────────────────────────────────────────────────────────────── @@ -33,13 +32,11 @@ class OnyxSdkLightsController : LightsInterface { override fun needsPermission(): Boolean = false override fun hasStandaloneWarmth(): Boolean = false - override fun hasWarmth(): Boolean { - return when (OnyxDevice.brightnessType) { - OnyxBrightnessType.WARM_AND_COLD, - OnyxBrightnessType.CTM -> true + override fun hasWarmth(): Boolean = when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD, + OnyxBrightnessType.CTM -> true - else -> !OnyxDevice.isInitialized // optimistic before init - } + else -> !OnyxDevice.isInitialized // optimistic before init } // ── Read ────────────────────────────────────────────────────────────────── @@ -68,42 +65,34 @@ class OnyxSdkLightsController : LightsInterface { override fun setBrightness(activity: Activity, brightness: Int) { OnyxDevice.init(activity) val max = getMaxBrightness() - if (brightness < MIN || brightness > max) { + if (brightness !in MIN..max) { Log.w(TAG, "brightness out of range: $brightness (max=$max)") return } Log.v(TAG, "setBrightness=$brightness type=${OnyxDevice.brightnessType}") - when (OnyxDevice.brightnessType) { - OnyxBrightnessType.FL -> - OnyxDevice.setLight(activity, LIGHT_TYPE_FL, brightness) - - OnyxBrightnessType.WARM_AND_COLD -> - OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_COLD, brightness) - - OnyxBrightnessType.CTM -> - OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_BR, brightness) - - OnyxBrightnessType.NONE -> Unit + val lightType = when (OnyxDevice.brightnessType) { + OnyxBrightnessType.FL -> LIGHT_TYPE_FL + OnyxBrightnessType.WARM_AND_COLD -> LIGHT_TYPE_CTM_COLD + OnyxBrightnessType.CTM -> LIGHT_TYPE_CTM_BR + OnyxBrightnessType.NONE -> return } + OnyxDevice.setLight(activity, lightType, brightness) } override fun setWarmth(activity: Activity, warmth: Int) { OnyxDevice.init(activity) val max = getMaxWarmth() - if (warmth < MIN || warmth > max) { + if (warmth !in MIN..max) { Log.w(TAG, "warmth out of range: $warmth (max=$max)") return } Log.v(TAG, "setWarmth=$warmth type=${OnyxDevice.brightnessType}") - when (OnyxDevice.brightnessType) { - OnyxBrightnessType.WARM_AND_COLD -> - OnyxDevice.setLight(activity, LIGHT_TYPE_CTM_WARM, warmth) - - OnyxBrightnessType.CTM -> - OnyxDevice.setLight(activity, LIGHT_TYPE_TEMP, warmth) - - else -> Unit + val lightType = when (OnyxDevice.brightnessType) { + OnyxBrightnessType.WARM_AND_COLD -> LIGHT_TYPE_CTM_WARM + OnyxBrightnessType.CTM -> LIGHT_TYPE_TEMP + else -> return } + OnyxDevice.setLight(activity, lightType, warmth) } // ── Range ───────────────────────────────────────────────────────────────── @@ -112,63 +101,204 @@ class OnyxSdkLightsController : LightsInterface { override fun getMinWarmth(): Int = MIN override fun getMaxBrightness(): Int = when (OnyxDevice.brightnessType) { - OnyxBrightnessType.FL -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_FL) ?: FALLBACK_MAX - OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_COLD) ?: FALLBACK_MAX - OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_BR) ?: FALLBACK_MAX - OnyxBrightnessType.NONE -> FALLBACK_MAX - } + OnyxBrightnessType.FL -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_FL) + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_COLD) + OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_BR) + OnyxBrightnessType.NONE -> null + } ?: FALLBACK_MAX override fun getMaxWarmth(): Int = when (OnyxDevice.brightnessType) { - OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_WARM) ?: FALLBACK_MAX - OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_TEMP) ?: FALLBACK_MAX - else -> FALLBACK_MAX - } + OnyxBrightnessType.WARM_AND_COLD -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_CTM_WARM) + OnyxBrightnessType.CTM -> OnyxDevice.getMaxLightValue(LIGHT_TYPE_TEMP) + else -> null + } ?: FALLBACK_MAX override fun enableFrontlightSwitch(activity: Activity): Int = 1 } -// ─── Low-level SDK bridge ───────────────────────────────────────────────────── +// ─── Brightness type detector ───────────────────────────────────────────────── + +/** + * Encapsulates the detection heuristics for which brightness channels this + * device supports. Extracted from OnyxDevice.init() to keep each class focused. + * + * Priority (mirrors BrightnessController.initProviderMap()): + * checkCTM() / maxCTM > 0 → CTM (types 6 + 7) + * hasCTMBrightness / maxWarm > 0 → WARM_AND_COLD (types 2 + 3) + * hasFLBrightness → FL (type 1) + */ +internal object BrightnessDetector { -object OnyxDevice { private const val TAG = "OnyxDevice" + /** + * Returns the detected [OnyxBrightnessType], or `null` when detection + * results in [OnyxBrightnessType.NONE] (i.e. nothing was found). + */ + fun detect( + context: Context, + bridge: OnyxReflectionBridge, + ): OnyxBrightnessType { + val checkCTM = bridge.checkCTM() + val hasFL = bridge.hasFLBrightness(context) + val hasCTM = bridge.hasCTMBrightness(context) + val maxCTM = bridge.getMaxLightValue(LIGHT_TYPE_TEMP) ?: 0 + val maxWarm = bridge.getMaxLightValue(LIGHT_TYPE_CTM_WARM) ?: 0 + + val type = when { + checkCTM || maxCTM > 0 -> OnyxBrightnessType.CTM + hasCTM || maxWarm > 0 -> OnyxBrightnessType.WARM_AND_COLD + hasFL -> OnyxBrightnessType.FL + else -> OnyxBrightnessType.NONE + } + + Log.d( + TAG, + "Detection: checkCTM=$checkCTM hasCTM=$hasCTM hasFL=$hasFL " + + "maxCTM=$maxCTM maxWarm=$maxWarm → $type" + ) + return type + } + + /** + * Returns true when [current] is a preliminary result that warrants + * re-running detection (e.g. first call, or device reported no warmth + * support yet but that can be state-dependent). + */ + fun shouldRedetect(current: OnyxBrightnessType): Boolean = + current == OnyxBrightnessType.NONE || current == OnyxBrightnessType.FL +} + +// ─── Front-light open/close logic ──────────────────────────────────────────── + +/** + * Decides which physical channel to open/close and whether a value-zero write + * should close the channel or just zero it. + * + * Extracted from OnyxDevice.setLight() to keep that function focused on + * dispatching rather than policy. + */ +internal object FrontLightSwitch { + + /** + * Maps a logical [lightType] to the physical channel used for open/close. + * CTM brightness and temperature share the master CTM switch (type 4); + * all other channels map to themselves. + */ + fun channelFor(lightType: Int): Int = when (lightType) { + LIGHT_TYPE_CTM_BR, + LIGHT_TYPE_TEMP -> LIGHT_TYPE_CTM_ALL + + else -> lightType + } + + /** + * Returns true when setting [lightType] to zero should close the front + * light, rather than simply writing value 0 to the parameter. + * + * Warmth parameters (TEMP / CTM_WARM) only control colour balance and + * must not turn off the whole panel. + */ + fun shouldCloseOnZero(lightType: Int): Boolean = when (lightType) { + LIGHT_TYPE_FL, + LIGHT_TYPE_CTM_BR, + LIGHT_TYPE_CTM_COLD, + LIGHT_TYPE_CTM_WARM -> true + + else -> false + } +} + +// ─── Pure reflection bridge ─────────────────────────────────────────────────── + +/** + * Thin wrapper around `android.onyx.hardware.DeviceController` that exposes + * only typed Kotlin calls. Contains no state beyond the cached [Method] + * references; all business logic lives in [OnyxDevice], [BrightnessDetector], + * and [FrontLightSwitch]. + */ +internal class OnyxReflectionBridge { + + companion object { + private const val TAG = "OnyxDevice" + } + private val controller: Class<*>? = try { Class.forName("android.onyx.hardware.DeviceController") } catch (e: Exception) { Log.w(TAG, "DeviceController not found: $e"); null } - // Integer getLightValue(int type) - private val mGetLightValue: Method? = method("getLightValue", Integer.TYPE) - ?: method("getLightValues", Integer.TYPE) + private val mGetLightValue: Method? = + method("getLightValue", Integer.TYPE) ?: method("getLightValues", Integer.TYPE) - // Integer getMaxLightValue(int type) - private val mGetMaxLightValue: Method? = method("getMaxLightValue", Integer.TYPE) - ?: method("getMaxLightValues", Integer.TYPE) + private val mGetMaxLightValue: Method? = + method("getMaxLightValue", Integer.TYPE) ?: method("getMaxLightValues", Integer.TYPE) - // boolean setLightValue(int type, int value) - private val mSetLightValue: Method? = method("setLightValue", Integer.TYPE, Integer.TYPE) - ?: method("setLightValues", Integer.TYPE, Integer.TYPE) + private val mSetLightValue: Method? = + method("setLightValue", Integer.TYPE, Integer.TYPE) + ?: method("setLightValues", Integer.TYPE, Integer.TYPE) - // boolean openFrontLight(int type) private val mOpenFrontLight: Method? = method("openFrontLight", Integer.TYPE) - - // boolean closeFrontLight(int type) private val mCloseFrontLight: Method? = method("closeFrontLight", Integer.TYPE) - // boolean isLightOn(int type) - private val mIsLightOn: Method? = method("isLightOn", Context::class.java, Integer.TYPE) - ?: method("isLightOn", Integer.TYPE) + private val mIsLightOn: Method? = + method("isLightOn", Context::class.java, Integer.TYPE) + ?: method("isLightOn", Integer.TYPE) - // boolean hasFLBrightness(Context) private val mHasFLBrightness: Method? = method("hasFLBrightness", Context::class.java) - - // boolean hasCTMBrightness(Context) private val mHasCTMBrightness: Method? = method("hasCTMBrightness", Context::class.java) - - // boolean checkCTM() private val mCheckCTM: Method? = method("checkCTM") + // ── Capability queries ──────────────────────────────────────────────────── + + fun checkCTM(): Boolean = mCheckCTM?.invoke(controller) as? Boolean ?: false + fun hasFLBrightness(ctx: Context): Boolean = mHasFLBrightness?.invoke(controller, ctx) as? Boolean ?: false + fun hasCTMBrightness(ctx: Context): Boolean = mHasCTMBrightness?.invoke(controller, ctx) as? Boolean ?: false + + // ── Reads ───────────────────────────────────────────────────────────────── + + fun getLightValue(type: Int): Int? = + mGetLightValue?.invoke(controller, type) as? Int + + fun getMaxLightValue(type: Int): Int? { + val v = mGetMaxLightValue?.invoke(controller, type) as? Number + return v?.toInt()?.takeIf { it != 0 } + } + + fun isLightOn(context: Context, type: Int): Boolean = mIsLightOn?.let { m -> + if (m.parameterTypes.size == 2) m.invoke(controller, context, type) + else m.invoke(controller, type) + } as? Boolean ?: false + + // ── Writes ──────────────────────────────────────────────────────────────── + + fun openFrontLight(type: Int): Boolean = + mOpenFrontLight?.invoke(controller, type) as? Boolean ?: false + + fun closeFrontLight(type: Int): Boolean = + mCloseFrontLight?.invoke(controller, type) as? Boolean ?: false + + fun setLightValue(type: Int, value: Int): Boolean = + mSetLightValue?.invoke(controller, type, value) as? Boolean ?: false + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private fun method(name: String, vararg params: Class<*>): Method? = try { + controller?.getMethod(name, *params) + } catch (e: Exception) { + Log.w(TAG, "Method '$name' not found: $e"); null + } +} + +// ─── Stateful device facade ─────────────────────────────────────────────────── + +object OnyxDevice { + + private const val TAG = "OnyxDevice" + + private val bridge = OnyxReflectionBridge() + var brightnessType: OnyxBrightnessType = OnyxBrightnessType.NONE private set @@ -176,111 +306,66 @@ object OnyxDevice { private set /** - * Call once from Activity.onCreate. - * Mirrors BrightnessController.initProviderMap() priority order: - * checkCTM() → CTM (open/close via type 4, read/write via types 6+7) - * hasCTMBrightness → WARM_AND_COLD (types 2+3) - * hasFLBrightness → FL (type 1) + * Initialises brightness-type detection. Re-runs detection if the current + * result is considered preliminary (see [BrightnessDetector.shouldRedetect]). */ fun init(context: Context) { - // Allow re-initialization if we are currently in a state without warmth, - // to handle cases where detection might be state-dependent (like "OFF" vs "Custom" mode). - if (isInitialized && brightnessType != OnyxBrightnessType.NONE && brightnessType != OnyxBrightnessType.FL) return - - val checkCTM = mCheckCTM?.invoke(controller) as? Boolean ?: false - val hasFL = mHasFLBrightness?.invoke(controller, context) as? Boolean ?: false - val hasCTM = mHasCTMBrightness?.invoke(controller, context) as? Boolean ?: false - - // Check actual hardware channel availability as fallback/confirmation - val maxCTM = getMaxLightValue(LIGHT_TYPE_TEMP) ?: 0 - val maxWarm = getMaxLightValue(LIGHT_TYPE_CTM_WARM) ?: 0 + if (isInitialized && !BrightnessDetector.shouldRedetect(brightnessType)) return - val oldType = brightnessType - brightnessType = when { - checkCTM || maxCTM > 0 -> OnyxBrightnessType.CTM - hasCTM || maxWarm > 0 -> OnyxBrightnessType.WARM_AND_COLD - hasFL -> OnyxBrightnessType.FL - else -> OnyxBrightnessType.NONE + val detected = BrightnessDetector.detect(context, bridge) + if (detected != brightnessType) { + brightnessType = detected } - if (brightnessType != OnyxBrightnessType.NONE) { isInitialized = true } - - if (oldType != brightnessType) { - Log.d( - TAG, - "Detection: checkCTM=$checkCTM hasCTM=$hasCTM hasFL=$hasFL maxCTM=$maxCTM maxWarm=$maxWarm → $brightnessType" - ) - } } - // ── Reads ───────────────────────────────────────────────────────────────── + // ── Reads (delegated to bridge) ─────────────────────────────────────────── - fun getLightValue(type: Int): Int? = mGetLightValue?.invoke(controller, type) as? Int - fun getMaxLightValue(type: Int): Int? { - val max = mGetMaxLightValue?.invoke(controller, type) as? Number - return if (max == null || max.toInt() == 0) null else max.toInt() - } + fun getLightValue(type: Int): Int? = bridge.getLightValue(type) + fun getMaxLightValue(type: Int): Int? = bridge.getMaxLightValue(type) - fun isLightOn(context: Context, type: Int): Boolean = mIsLightOn?.let { method -> - if (method.parameterTypes.size == 2) { - method.invoke(controller, context, type) - } else { - method.invoke(controller, type) - } - } as? Boolean ?: false + fun isLightOn(context: Context, type: Int): Boolean = + bridge.isLightOn(context, type) // ── Write ───────────────────────────────────────────────────────────────── + /** + * Sets a light [type] to [value], opening or closing the physical channel + * as required. Channel-to-switch mapping and close-on-zero policy are + * resolved by [FrontLightSwitch]. + */ fun setLight(context: Context, type: Int, value: Int): Boolean { - // Map logical parameter type → physical channel type for open/close. - // For CTM, type 4 (ALL) controls the master switch. - // For WARM_AND_COLD, channels 2 and 3 are independent. - val channelType = when (type) { - LIGHT_TYPE_CTM_BR, - LIGHT_TYPE_TEMP -> LIGHT_TYPE_CTM_ALL // CTM: always open/close the whole unit - else -> type // FL / WARM / COLD: direct channel - } + val channel = FrontLightSwitch.channelFor(type) return if (value == 0) { - // Only close the master switch if it's CTM brightness (7) or FL brightness (1). - // Closing warmth channel (2 or 6) shouldn't turn off all lights. - val shouldClose = when (type) { - LIGHT_TYPE_FL, - LIGHT_TYPE_CTM_BR, - LIGHT_TYPE_CTM_COLD, - LIGHT_TYPE_CTM_WARM -> true - - else -> false - } - - if (shouldClose) { - val ok = mCloseFrontLight?.invoke(controller, channelType) as? Boolean ?: false - Log.v(TAG, "closeFrontLight(channelType=$channelType) → $ok") - ok - } else { - // For warmth, just set value to 0 - val ok = mSetLightValue?.invoke(controller, type, 0) as? Boolean ?: false - Log.v(TAG, "setLightValue(type=$type, value=0) → $ok") - ok - } + setLightOff(type, channel) } else { - if (!isLightOn(context, channelType)) { - val opened = mOpenFrontLight?.invoke(controller, channelType) as? Boolean ?: false - Log.v(TAG, "openFrontLight(channelType=$channelType) → $opened") - } - val ok = mSetLightValue?.invoke(controller, type, value) as? Boolean ?: false + ensureLightOn(context, channel) + val ok = bridge.setLightValue(type, value) Log.v(TAG, "setLightValue(type=$type, value=$value) → $ok") ok } } - // ── Helpers ─────────────────────────────────────────────────────────────── + // ── Private helpers ─────────────────────────────────────────────────────── - private fun method(name: String, vararg params: Class<*>): Method? = try { - controller?.getMethod(name, *params) - } catch (e: Exception) { - Log.w(TAG, "Method '$name' not found: $e"); null + private fun setLightOff(type: Int, channel: Int): Boolean { + if (FrontLightSwitch.shouldCloseOnZero(type)) { + bridge.closeFrontLight(channel) + Log.v(TAG, "closeFrontLight(channel=$channel)") + } + + val ok = bridge.setLightValue(type, 0) + Log.v(TAG, "setLightValue(type=$type, value=0) → $ok") + return ok + } + + private fun ensureLightOn(context: Context, channel: Int) { + if (!bridge.isLightOn(context, channel)) { + val ok = bridge.openFrontLight(channel) + Log.v(TAG, "openFrontLight(channel=$channel) → $ok") + } } } From ff6afcbb243992f5125106c042320e82d180ebaf Mon Sep 17 00:00:00 2001 From: tukks Date: Fri, 6 Mar 2026 13:40:49 +0100 Subject: [PATCH 4/6] Simplify OnyxSdkLightsController by always enabling warmth and removing redundant brightness detection logic --- .../device/lights/OnyxSdkLightsController.kt | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt index 37dfbd007..5fabb5ff9 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt @@ -32,12 +32,7 @@ class OnyxSdkLightsController : LightsInterface { override fun needsPermission(): Boolean = false override fun hasStandaloneWarmth(): Boolean = false - override fun hasWarmth(): Boolean = when (OnyxDevice.brightnessType) { - OnyxBrightnessType.WARM_AND_COLD, - OnyxBrightnessType.CTM -> true - - else -> !OnyxDevice.isInitialized // optimistic before init - } + override fun hasWarmth(): Boolean = true // ── Read ────────────────────────────────────────────────────────────────── @@ -123,9 +118,9 @@ class OnyxSdkLightsController : LightsInterface { * device supports. Extracted from OnyxDevice.init() to keep each class focused. * * Priority (mirrors BrightnessController.initProviderMap()): - * checkCTM() / maxCTM > 0 → CTM (types 6 + 7) + * checkCTM() / maxCTM > 0 → CTM (types 6 + 7) + * hasFLBrightness → FL (type 1) * hasCTMBrightness / maxWarm > 0 → WARM_AND_COLD (types 2 + 3) - * hasFLBrightness → FL (type 1) */ internal object BrightnessDetector { @@ -147,8 +142,8 @@ internal object BrightnessDetector { val type = when { checkCTM || maxCTM > 0 -> OnyxBrightnessType.CTM - hasCTM || maxWarm > 0 -> OnyxBrightnessType.WARM_AND_COLD hasFL -> OnyxBrightnessType.FL + hasCTM || maxWarm > 0 -> OnyxBrightnessType.WARM_AND_COLD else -> OnyxBrightnessType.NONE } @@ -159,14 +154,6 @@ internal object BrightnessDetector { ) return type } - - /** - * Returns true when [current] is a preliminary result that warrants - * re-running detection (e.g. first call, or device reported no warmth - * support yet but that can be state-dependent). - */ - fun shouldRedetect(current: OnyxBrightnessType): Boolean = - current == OnyxBrightnessType.NONE || current == OnyxBrightnessType.FL } // ─── Front-light open/close logic ──────────────────────────────────────────── @@ -302,23 +289,14 @@ object OnyxDevice { var brightnessType: OnyxBrightnessType = OnyxBrightnessType.NONE private set - var isInitialized = false - private set - /** - * Initialises brightness-type detection. Re-runs detection if the current - * result is considered preliminary (see [BrightnessDetector.shouldRedetect]). + * Initialises brightness-type detection. */ fun init(context: Context) { - if (isInitialized && !BrightnessDetector.shouldRedetect(brightnessType)) return - val detected = BrightnessDetector.detect(context, bridge) if (detected != brightnessType) { brightnessType = detected } - if (brightnessType != OnyxBrightnessType.NONE) { - isInitialized = true - } } // ── Reads (delegated to bridge) ─────────────────────────────────────────── From 18112d9ddb19b78cdb823fcc3ee9b9e8b3d9729c Mon Sep 17 00:00:00 2001 From: tukks Date: Fri, 6 Mar 2026 16:26:43 +0100 Subject: [PATCH 5/6] Convert verbose log to debug log instead --- .../device/lights/OnyxSdkLightsController.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt index 5fabb5ff9..0f184e6bb 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/OnyxSdkLightsController.kt @@ -64,7 +64,7 @@ class OnyxSdkLightsController : LightsInterface { Log.w(TAG, "brightness out of range: $brightness (max=$max)") return } - Log.v(TAG, "setBrightness=$brightness type=${OnyxDevice.brightnessType}") + Log.d(TAG, "setBrightness=$brightness type=${OnyxDevice.brightnessType}") val lightType = when (OnyxDevice.brightnessType) { OnyxBrightnessType.FL -> LIGHT_TYPE_FL OnyxBrightnessType.WARM_AND_COLD -> LIGHT_TYPE_CTM_COLD @@ -81,7 +81,7 @@ class OnyxSdkLightsController : LightsInterface { Log.w(TAG, "warmth out of range: $warmth (max=$max)") return } - Log.v(TAG, "setWarmth=$warmth type=${OnyxDevice.brightnessType}") + Log.d(TAG, "setWarmth=$warmth type=${OnyxDevice.brightnessType}") val lightType = when (OnyxDevice.brightnessType) { OnyxBrightnessType.WARM_AND_COLD -> LIGHT_TYPE_CTM_WARM OnyxBrightnessType.CTM -> LIGHT_TYPE_TEMP @@ -322,7 +322,7 @@ object OnyxDevice { } else { ensureLightOn(context, channel) val ok = bridge.setLightValue(type, value) - Log.v(TAG, "setLightValue(type=$type, value=$value) → $ok") + Log.d(TAG, "setLightValue(type=$type, value=$value) → $ok") ok } } @@ -332,18 +332,18 @@ object OnyxDevice { private fun setLightOff(type: Int, channel: Int): Boolean { if (FrontLightSwitch.shouldCloseOnZero(type)) { bridge.closeFrontLight(channel) - Log.v(TAG, "closeFrontLight(channel=$channel)") + Log.d(TAG, "closeFrontLight(channel=$channel)") } val ok = bridge.setLightValue(type, 0) - Log.v(TAG, "setLightValue(type=$type, value=0) → $ok") + Log.d(TAG, "setLightValue(type=$type, value=0) → $ok") return ok } private fun ensureLightOn(context: Context, channel: Int) { if (!bridge.isLightOn(context, channel)) { val ok = bridge.openFrontLight(channel) - Log.v(TAG, "openFrontLight(channel=$channel) → $ok") + Log.d(TAG, "openFrontLight(channel=$channel) → $ok") } } } From e06a63254d3aa03bc8d5d22e8b5ef77ea29d3b1f Mon Sep 17 00:00:00 2001 From: Frans de Jonge Date: Mon, 1 Jun 2026 22:31:26 +0200 Subject: [PATCH 6/6] Update app/src/main/java/org/koreader/launcher/device/LightsFactory.kt --- app/src/main/java/org/koreader/launcher/device/LightsFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt index dd868b785..e7762b5e2 100644 --- a/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt +++ b/app/src/main/java/org/koreader/launcher/device/LightsFactory.kt @@ -82,7 +82,7 @@ object LightsFactory { DeviceInfo.Id.ONYX_TAB_ULTRA, DeviceInfo.Id.STORYTEL_READER2, DeviceInfo.Id.ONYX_GO7, - -> { + -> { logController("Onyx/Sdk") OnyxSdkLightsController() }