Skip to content

Commit

Permalink
Add ability for ClimateControl Card to cycle through entity states (#…
Browse files Browse the repository at this point in the history
…4142)

* Add ability for ClimateControl Card to cycle through entity states

* Fix ktlint issue

* Set `currentMode` after sending request for cycling through modes

* Display thermostat always as "on"

* Use "toggle" string from widget_tap_action_toggle

* Use hashMap to save states of entities as we're in a singleton

* Include support for colliding entityIds when using multi server within ClimateControl

* Add comment why ToggleRangeTemplate is always set to checked
  • Loading branch information
StopMotionCuber authored Feb 22, 2024
1 parent f006127 commit 45e0ca9
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.util.STATE_UNAVAILABLE
import java.net.URL
import java.util.concurrent.TimeUnit
Expand All @@ -32,11 +31,10 @@ object CameraControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
val image = if (baseUrl != null && (entity.attributes["entity_picture"] as? String)?.isNotBlank() == true) {
getThumbnail(baseUrl + entity.attributes["entity_picture"] as String)
val image = if (info.baseUrl != null && (entity.attributes["entity_picture"] as? String)?.isNotBlank() == true) {
getThumbnail(info.baseUrl + entity.attributes["entity_picture"] as String)
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import android.content.Context
import android.os.Build
import android.service.controls.Control
import android.service.controls.DeviceTypes
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.ControlAction
import android.service.controls.actions.FloatAction
import android.service.controls.actions.ModeAction
import android.service.controls.templates.RangeTemplate
import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
import androidx.annotation.RequiresApi
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object ClimateControl : HaControl {
private data class ClimateState(val currentMode: String, val supportedModes: ArrayList<String>)

private const val SUPPORT_TARGET_TEMPERATURE = 1
private const val SUPPORT_TARGET_TEMPERATURE_RANGE = 2
private val temperatureControlModes = mapOf(
Expand All @@ -31,13 +34,13 @@ object ClimateControl : HaControl {
"heat_cool" to TemperatureControlTemplate.FLAG_MODE_HEAT_COOL,
"off" to TemperatureControlTemplate.FLAG_MODE_OFF
)
private val climateStates = HashMap<String, ClimateState>()

override fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
val minValue = (entity.attributes["min_temp"] as? Number)?.toFloat() ?: 0f
val maxValue = (entity.attributes["max_temp"] as? Number)?.toFloat() ?: 100f
Expand All @@ -60,22 +63,32 @@ object ClimateControl : HaControl {
}
val temperatureFormatSize = if (temperatureStepSize < 1f) "1" else "0"
val rangeTemplate = RangeTemplate(
entity.entityId,
info.systemId,
minValue,
maxValue,
currentValue,
temperatureStepSize,
"%.${temperatureFormatSize}f $temperatureUnit"
)
if (entityShouldBePresentedAsThermostat(entity)) {
val state = ClimateState(entity.state, ArrayList())
val toggleRangeTemplate = ToggleRangeTemplate(
info.systemId + "_range",
// Set checked to true to always show the temperature indicator, regardless of climate mode
true,
context.getString(commonR.string.widget_tap_action_toggle),
rangeTemplate
)
var modesFlag = 0
(entity.attributes["hvac_modes"] as? List<String>)?.forEach {
modesFlag = modesFlag or temperatureControlModeFlags[it]!!
state.supportedModes.add(it)
}
this.climateStates[info.systemId] = state
control.setControlTemplate(
TemperatureControlTemplate(
entity.entityId,
rangeTemplate,
info.systemId,
toggleRangeTemplate,
temperatureControlModes[entity.state]!!,
temperatureControlModes[entity.state]!!,
modesFlag
Expand All @@ -102,13 +115,18 @@ object ClimateControl : HaControl {
integrationRepository: IntegrationRepository,
action: ControlAction
): Boolean {
val entityStr: String = if (action.templateId.split(".").size > 2) {
action.templateId.split(".", limit = 2)[1]
} else {
action.templateId
}
return when (action) {
is FloatAction -> {
integrationRepository.callService(
action.templateId.split(".")[0],
entityStr.split(".")[0],
"set_temperature",
hashMapOf(
"entity_id" to action.templateId,
"entity_id" to entityStr,
"temperature" to (action as? FloatAction)?.newValue.toString()
)
)
Expand All @@ -119,7 +137,7 @@ object ClimateControl : HaControl {
action.templateId.split(".")[0],
"set_hvac_mode",
hashMapOf(
"entity_id" to action.templateId,
"entity_id" to entityStr,
"hvac_mode" to (
temperatureControlModes.entries.find {
it.value == ((action as? ModeAction)?.newMode ?: -1)
Expand All @@ -129,6 +147,23 @@ object ClimateControl : HaControl {
)
true
}
is BooleanAction -> {
if (this.climateStates[action.templateId] == null) {
return false
}
val supportedModes = this.climateStates[action.templateId]!!.supportedModes
val currentMode = this.climateStates[action.templateId]!!.currentMode
val nextMode = (supportedModes.indexOf(currentMode) + 1) % supportedModes.count()
integrationRepository.callService(
entityStr.split(".")[0],
"set_hvac_mode",
hashMapOf(
"entity_id" to entityStr,
"hvac_mode" to supportedModes[nextMode]
)
)
true
}
else -> {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.getCoverPosition
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object CoverControl : HaControl {
Expand All @@ -26,8 +25,7 @@ object CoverControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
val position = entity.getCoverPosition()
control.setControlTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.util.capitalize
import java.util.Locale

Expand All @@ -21,8 +20,7 @@ object DefaultButtonControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
control.setStatusText("")
control.setControlTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.annotation.RequiresApi
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import kotlinx.coroutines.runBlocking

@RequiresApi(Build.VERSION_CODES.R)
Expand All @@ -20,8 +19,7 @@ object DefaultSliderControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
control.setStatusText("")
control.setControlTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.util.capitalize
import java.util.Locale

Expand All @@ -24,8 +23,7 @@ object DefaultSwitchControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
control.setControlTemplate(
ToggleTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@ import io.homeassistant.companion.android.common.data.integration.IntegrationRep
import io.homeassistant.companion.android.common.data.integration.getFanSpeed
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.integration.supportsFanSetSpeed
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object FanControl : HaControl {
override fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
if (entity.supportsFanSetSpeed()) {
val position = entity.getFanSpeed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.integration.friendlyState
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.webview.WebViewActivity

@RequiresApi(Build.VERSION_CODES.R)
Expand Down Expand Up @@ -77,15 +76,14 @@ interface HaControl {
}
}

return provideControlFeatures(context, control, entity, info.area, info.baseUrl).build()
return provideControlFeatures(context, control, entity, info).build()
}

fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder

fun getDeviceType(entity: Entity<Map<String, Any>>): Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.annotation.RequiresApi
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.util.capitalize
import java.util.Locale

Expand All @@ -20,8 +19,7 @@ object HaFailedControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
control.setStatus(if (entity.state == "notfound") Control.STATUS_NOT_FOUND else Control.STATUS_ERROR)
control.setStatusText("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@ import io.homeassistant.companion.android.common.data.integration.IntegrationRep
import io.homeassistant.companion.android.common.data.integration.getLightBrightness
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.integration.supportsLightBrightness
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object LightControl : HaControl {
override fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
val position = entity.getLightBrightness()
control.setControlTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object LockControl : HaControl {
override fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
control.setControlTemplate(
ToggleTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.integration.isActive
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse

@RequiresApi(Build.VERSION_CODES.R)
object VacuumControl : HaControl {
Expand All @@ -24,8 +23,7 @@ object VacuumControl : HaControl {
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?,
baseUrl: String?
info: HaControlInfo
): Control.StatefulBuilder {
entitySupportedFeatures = entity.attributes["supported_features"] as Int
control.setControlTemplate(
Expand Down

0 comments on commit 45e0ca9

Please sign in to comment.