Add extensions to Entity for domain, lights features (#2322)

* Add extensions to Entity for domain, lights features

 - Add extension property for domain
 - Add extension functions for lights: support for setting brightness and color temperature

* Use domain extension property in more places
This commit is contained in:
Joris Pelgröm 2022-02-26 23:34:29 +01:00 committed by GitHub
parent ffeeb4a962
commit 72a598fdbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 90 additions and 61 deletions

View file

@ -27,6 +27,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mikepenz.iconics.IconicsDrawable
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.settings.wear.SettingsWearViewModel
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
import org.burnoutcrew.reorderable.draggedItem
@ -158,7 +159,7 @@ fun LoadWearFavoritesSettings(
modifier = Modifier.padding(top = 10.dp)
)
Text(
text = getDomainString(item.entityId.split('.')[0]),
text = getDomainString(item.domain),
fontSize = 11.sp
)
}

View file

@ -9,6 +9,7 @@ import android.service.controls.templates.StatelessTemplate
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 kotlinx.coroutines.runBlocking
import io.homeassistant.companion.android.common.R as commonR
@ -32,18 +33,18 @@ class DefaultButtonControl {
}
override fun getDeviceType(entity: Entity<Map<String, Any>>): Int =
when (entity.entityId.split(".")[0]) {
when (entity.domain) {
"scene", "script" -> DeviceTypes.TYPE_ROUTINE
else -> DeviceTypes.TYPE_UNKNOWN
}
override fun getDomainString(context: Context, entity: Entity<Map<String, Any>>): String =
when (entity.entityId.split(".")[0]) {
when (entity.domain) {
"button" -> context.getString(commonR.string.domain_button)
"input_button" -> context.getString(commonR.string.domain_input_button)
"scene" -> context.getString(commonR.string.domain_scene)
"script" -> context.getString(commonR.string.domain_script)
else -> entity.entityId.split(".")[0].capitalize()
else -> entity.domain.capitalize()
}
override fun performAction(

View file

@ -11,6 +11,7 @@ import android.service.controls.templates.ToggleTemplate
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 kotlinx.coroutines.runBlocking
import io.homeassistant.companion.android.common.R as commonR
@ -37,17 +38,17 @@ class DefaultSwitchControl {
}
override fun getDeviceType(entity: Entity<Map<String, Any>>): Int =
when (entity.entityId.split(".")[0]) {
when (entity.domain) {
"switch" -> DeviceTypes.TYPE_SWITCH
else -> DeviceTypes.TYPE_GENERIC_ON_OFF
}
override fun getDomainString(context: Context, entity: Entity<Map<String, Any>>): String =
when (entity.entityId.split(".")[0]) {
when (entity.domain) {
"automation" -> context.getString(commonR.string.domain_automation)
"input_boolean" -> context.getString(commonR.string.domain_input_boolean)
"switch" -> context.getString(commonR.string.domain_switch)
else -> entity.entityId.split(".")[0].capitalize()
else -> entity.domain.capitalize()
}
override fun performAction(

View file

@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi
import dagger.hilt.android.AndroidEntryPoint
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.WebSocketRepository
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
@ -78,8 +79,7 @@ class HaControlsProviderService : ControlsProviderService() {
entities
?.sortedWith(compareBy(nullsLast()) { areaForEntity[it.entityId]?.name })
?.mapNotNull {
val domain = it.entityId.split(".")[0]
domainToHaControl[domain]?.createControl(
domainToHaControl[it.domain]?.createControl(
applicationContext,
it as Entity<Map<String, Any>>,
areaForEntity[it.entityId]
@ -141,8 +141,7 @@ class HaControlsProviderService : ControlsProviderService() {
webSocketScope.launch {
entityFlow?.collect {
if (controlIds.contains(it.entityId)) {
val domain = it.entityId.split(".")[0]
val control = domainToHaControl[domain]?.createControl(
val control = domainToHaControl[it.domain]?.createControl(
applicationContext,
it as Entity<Map<String, Any>>,
RegistriesDataHandler.getAreaForEntity(it.entityId, areaRegistry, deviceRegistry, entityRegistry)
@ -214,8 +213,7 @@ class HaControlsProviderService : ControlsProviderService() {
entityRegistry: List<EntityRegistryResponse>?
) {
entities.forEach {
val domain = it.key.split(".")[0]
val control = domainToHaControl[domain]?.createControl(
val control = domainToHaControl[it.value.domain]?.createControl(
applicationContext,
it.value,
RegistriesDataHandler.getAreaForEntity(it.key, areaRegistry, deviceRegistry, entityRegistry)

View file

@ -9,6 +9,7 @@ import android.service.controls.templates.StatelessTemplate
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
@RequiresApi(Build.VERSION_CODES.R)
@ -34,7 +35,7 @@ class HaFailedControl {
DeviceTypes.TYPE_UNKNOWN
override fun getDomainString(context: Context, entity: Entity<Map<String, Any>>): String =
entity.entityId.split(".")[0].capitalize()
entity.domain.capitalize()
override fun performAction(
integrationRepository: IntegrationRepository,

View file

@ -14,6 +14,7 @@ import android.service.controls.templates.ToggleTemplate
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.supportsLightBrightness
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import kotlinx.coroutines.runBlocking
import io.homeassistant.companion.android.common.R as commonR
@ -21,19 +22,12 @@ import io.homeassistant.companion.android.common.R as commonR
@RequiresApi(Build.VERSION_CODES.R)
class LightControl {
companion object : HaControl {
private const val SUPPORT_BRIGHTNESS = 1
private val NO_BRIGHTNESS_SUPPORT = listOf("unknown", "onoff")
override fun provideControlFeatures(
context: Context,
control: Control.StatefulBuilder,
entity: Entity<Map<String, Any>>,
area: AreaRegistryResponse?
): Control.StatefulBuilder {
// On HA Core 2021.5 and later brightness detection has changed
// to simplify things in the app lets use both methods for now
val supportedColorModes = entity.attributes["supported_color_modes"] as? List<String>
val supportsBrightness = if (supportedColorModes == null) false else (supportedColorModes - NO_BRIGHTNESS_SUPPORT).isNotEmpty()
val minValue = 0f
val maxValue = 100f
var currentValue = (entity.attributes["brightness"] as? Number)?.toFloat()?.div(255f)?.times(100) ?: 0f
@ -42,7 +36,7 @@ class LightControl {
if (currentValue > maxValue)
currentValue = maxValue
control.setControlTemplate(
if (supportsBrightness || ((entity.attributes["supported_features"] as Int) and SUPPORT_BRIGHTNESS == SUPPORT_BRIGHTNESS))
if (entity.supportsLightBrightness())
ToggleRangeTemplate(
entity.entityId,
entity.state == "on",

View file

@ -17,6 +17,7 @@ import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.R
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.database.AppDatabase
import io.homeassistant.companion.android.database.qs.TileEntity
import kotlinx.coroutines.flow.Flow
@ -55,8 +56,7 @@ class ManageTilesViewModel @Inject constructor(
init {
viewModelScope.launch {
integrationUseCase.getEntities()?.forEach {
val split = it.entityId.split(".")
if (split[0] in ManageTilesFragment.validDomains)
if (it.domain in ManageTilesFragment.validDomains)
entities[it.entityId] = it
}
}

View file

@ -17,6 +17,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BaseActivity
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.database.AppDatabase
import io.homeassistant.companion.android.databinding.WidgetCameraConfigureBinding
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel
@ -128,11 +129,8 @@ class CameraWidgetConfigureActivity : BaseActivity() {
// Fetch entities
val fetchedEntities = integrationUseCase.getEntities()
fetchedEntities?.forEach {
val entityId = it.entityId
val domain = entityId.split(".")[0]
if (domain == "camera") {
entities[entityId] = it
if (it.domain == "camera") {
entities[it.entityId] = it
}
}
entityAdapter.addAll(entities.values)

View file

@ -17,6 +17,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BaseActivity
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.database.AppDatabase
import io.homeassistant.companion.android.databinding.WidgetMediaControlsConfigureBinding
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel
@ -133,11 +134,8 @@ class MediaPlayerControlsWidgetConfigureActivity : BaseActivity() {
// Fetch entities
val fetchedEntities = integrationUseCase.getEntities()
fetchedEntities?.forEach {
val entityId = it.entityId
val domain = entityId.split(".")[0]
if (domain == "media_player") {
entities[entityId] = it
if (it.domain == "media_player") {
entities[it.entityId] = it
}
}
entityAdapter.addAll(entities.values)

View file

@ -1,5 +1,6 @@
package io.homeassistant.companion.android.common.data.integration
import android.util.Log
import java.util.Calendar
data class Entity<T>(
@ -10,3 +11,46 @@ data class Entity<T>(
val lastUpdated: Calendar,
val context: Map<String, Any>?
)
object EntityExt {
const val TAG = "EntityExt"
const val LIGHT_MODE_COLOR_TEMP = "color_temp"
val LIGHT_MODE_NO_BRIGHTNESS_SUPPORT = listOf("unknown", "onoff")
const val LIGHT_SUPPORT_BRIGHTNESS_DEPR = 1
const val LIGHT_SUPPORT_COLOR_TEMP_DEPR = 2
}
val <T> Entity<T>.domain: String
get() = this.entityId.split(".")[0]
fun <T> Entity<T>.supportsLightBrightness(): Boolean {
return try {
if (domain != "light") return false
// On HA Core 2021.5 and later brightness detection has changed
// to simplify things in the app lets use both methods for now
val supportedColorModes = (attributes as Map<*, *>)["supported_color_modes"] as? List<String>
val supportsBrightness =
if (supportedColorModes == null) false else (supportedColorModes - EntityExt.LIGHT_MODE_NO_BRIGHTNESS_SUPPORT).isNotEmpty()
val supportedFeatures = attributes["supported_features"] as Int
supportsBrightness || (supportedFeatures and EntityExt.LIGHT_SUPPORT_BRIGHTNESS_DEPR == EntityExt.LIGHT_SUPPORT_BRIGHTNESS_DEPR)
} catch (e: Exception) {
Log.e(EntityExt.TAG, "Unable to get supportsLightBrightness", e)
false
}
}
fun <T> Entity<T>.supportsLightColorTemperature(): Boolean {
return try {
if (domain != "light") return false
val supportedColorModes = (attributes as Map<*, *>)["supported_color_modes"] as? List<String>
val supportsColorTemp = supportedColorModes?.contains(EntityExt.LIGHT_MODE_COLOR_TEMP) ?: false
val supportedFeatures = attributes["supported_features"] as Int
supportsColorTemp || (supportedFeatures and EntityExt.LIGHT_SUPPORT_COLOR_TEMP_DEPR == EntityExt.LIGHT_SUPPORT_COLOR_TEMP_DEPR)
} catch (e: Exception) {
Log.e(EntityExt.TAG, "Unable to get supportsLightColorTemperature", e)
false
}
}

View file

@ -11,6 +11,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
@ -132,7 +133,7 @@ class MainViewModel @Inject constructor(application: Application) : AndroidViewM
deviceRegistry = homePresenter.getDeviceRegistry()
entityRegistry = homePresenter.getEntityRegistry()
homePresenter.getEntities()?.forEach {
if (supportedDomains().contains(it.entityId.split(".")[0])) {
if (supportedDomains().contains(it.domain)) {
entities[it.entityId] = it
}
}
@ -153,7 +154,7 @@ class MainViewModel @Inject constructor(application: Application) : AndroidViewM
// Listen for updates
viewModelScope.launch {
homePresenter.getEntityUpdates()?.collect {
if (supportedDomains().contains(it.entityId.split(".")[0])) {
if (supportedDomains().contains(it.domain)) {
entities[it.entityId] = it
updateEntityDomains()
}
@ -191,7 +192,7 @@ class MainViewModel @Inject constructor(application: Application) : AndroidViewM
fun updateEntityDomains() {
val entitiesList = entities.values.toList().sortedBy { it.entityId }
val areasList = areaRegistry.orEmpty().sortedBy { it.name }
val domainsList = entitiesList.map { it.entityId.split(".")[0] }.distinct()
val domainsList = entitiesList.map { it.domain }.distinct()
// Create a list with all areas + their entities
areasList.forEach { area ->
@ -221,7 +222,7 @@ class MainViewModel @Inject constructor(application: Application) : AndroidViewM
// Create a list with all discovered domains + their entities
domainsList.forEach { domain ->
val entitiesInDomain = mutableStateListOf<Entity<*>>()
entitiesInDomain.addAll(entitiesList.filter { it.entityId.split(".")[0] == domain })
entitiesInDomain.addAll(entitiesList.filter { it.domain == domain })
entitiesByDomain[domain]?.let {
it.clear()
it.addAll(entitiesInDomain)

View file

@ -17,6 +17,7 @@ import androidx.wear.compose.material.Text
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.data.SimplifiedEntity
import io.homeassistant.companion.android.home.MainViewModel
import io.homeassistant.companion.android.theme.WearAppTheme
@ -85,7 +86,7 @@ private fun ChooseEntityChip(
val attributes = entityList[index].attributes as Map<*, *>
val iconBitmap = getIcon(
entityList[index] as Entity<Map<String, Any>>,
entityList[index].entityId.split(".")[0],
entityList[index].domain,
LocalContext.current
)
Chip(

View file

@ -23,16 +23,15 @@ import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.EntityExt
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.integration.supportsLightBrightness
import io.homeassistant.companion.android.common.data.integration.supportsLightColorTemperature
import io.homeassistant.companion.android.home.HomePresenterImpl
import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.util.getColorTemperature
import java.text.DateFormat
const val SUPPORT_BRIGHTNESS_DEPR = 1
val NO_BRIGHTNESS_SUPPORT = listOf("unknown", "onoff")
const val SUPPORT_COLOR_TEMP_DEPR = 2
const val SUPPORT_COLOR_TEMP = "color_temp"
@ExperimentalComposeUiApi
@Composable
fun DetailsPanelView(
@ -52,7 +51,7 @@ fun DetailsPanelView(
val friendlyName = attributes["friendly_name"].toString()
Text(friendlyName)
if (entity.entityId.split(".")[0] in HomePresenterImpl.toggleDomains) {
if (entity.domain in HomePresenterImpl.toggleDomains) {
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
ToggleButton(
checked = isChecked,
@ -67,24 +66,14 @@ fun DetailsPanelView(
}
}
if (entity.entityId.split('.')[0] == "light") {
// Brightness
// On HA Core 2021.5 and later brightness detection has changed
// to simplify things in the app lets use both methods for now
val supportedColorModes = attributes["supported_color_modes"] as? List<String>
val supportsBrightness =
if (supportedColorModes == null) false else (supportedColorModes - NO_BRIGHTNESS_SUPPORT).isNotEmpty()
if (supportsBrightness || ((attributes["supported_features"] as Int) and SUPPORT_BRIGHTNESS_DEPR == SUPPORT_BRIGHTNESS_DEPR)) {
if (entity.domain == "light") {
if (entity.supportsLightBrightness()) {
item {
BrightnessSlider(attributes, onBrightnessChanged)
}
}
// Color temp
val supportsColorTemp = supportedColorModes?.contains(SUPPORT_COLOR_TEMP) ?: false
val supportsColorTempComplete =
supportsColorTemp || ((attributes["supported_features"] as Int) and SUPPORT_COLOR_TEMP_DEPR == SUPPORT_COLOR_TEMP_DEPR)
if (supportsColorTempComplete && attributes["color_mode"] == SUPPORT_COLOR_TEMP) {
if (entity.supportsLightColorTemperature() && attributes["color_mode"] == EntityExt.LIGHT_MODE_COLOR_TEMP) {
item {
ColorTempSlider(attributes, onColorTempChanged)
}

View file

@ -19,6 +19,7 @@ import androidx.wear.compose.material.ToggleChipDefaults
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.home.HomePresenterImpl
import io.homeassistant.companion.android.theme.wearColorPalette
import io.homeassistant.companion.android.util.getIcon
@ -37,10 +38,10 @@ fun EntityUi(
val haptic = LocalHapticFeedback.current
val context = LocalContext.current
val attributes = entity.attributes as Map<*, *>
val iconBitmap = getIcon(entity as Entity<Map<String, Any>>, entity.entityId.split(".")[0], LocalContext.current)
val iconBitmap = getIcon(entity as Entity<Map<String, Any>>, entity.domain, LocalContext.current)
val friendlyName = attributes["friendly_name"].toString()
if (entity.entityId.split(".")[0] in HomePresenterImpl.toggleDomains) {
if (entity.domain in HomePresenterImpl.toggleDomains) {
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
ToggleChip(
checked = isChecked,

View file

@ -19,6 +19,7 @@ import androidx.wear.compose.material.rememberScalingLazyListState
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.home.MainViewModel
import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.theme.wearColorPalette
@ -90,7 +91,7 @@ private fun FavoriteToggleChip(
val attributes = entityList[index].attributes as Map<*, *>
val iconBitmap = getIcon(
entityList[index] as Entity<Map<String, Any>>,
entityList[index].entityId.split(".")[0],
entityList[index].domain,
LocalContext.current
)