Fix a race condition Sensor Update (#907)

* Fix a race condition between a sensor updating and sending the data to Home Assistant.

* ktlint

* UI enhancements + force update if re-enabled.
This commit is contained in:
Justin Bassett 2020-09-08 23:09:13 -04:00 committed by GitHub
parent 01d33067e7
commit 2c990e2c7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 54 additions and 22 deletions

View file

@ -64,7 +64,8 @@ abstract class AppDatabase : RoomDatabase() {
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9
MIGRATION_8_9,
MIGRATION_9_10
)
.build()
}
@ -168,5 +169,30 @@ abstract class AppDatabase : RoomDatabase() {
database.execSQL("ALTER TABLE `sensors` ADD `state_changed` INTEGER NOT NULL DEFAULT ''")
}
}
private val MIGRATION_9_10 = object : Migration(9, 10) {
override fun migrate(database: SupportSQLiteDatabase) {
val cursor = database.query("SELECT * FROM sensors")
val sensors = mutableListOf<ContentValues>()
while (cursor.moveToNext()) {
sensors.add(ContentValues().also {
it.put("id", cursor.getString(cursor.getColumnIndex("id")))
it.put("enabled", cursor.getInt(cursor.getColumnIndex("enabled")))
it.put("registered", cursor.getInt(cursor.getColumnIndex("registered")))
it.put("state", "")
it.put("last_sent_state", "")
it.put("state_type", "")
it.put("type", "")
it.put("icon", "")
it.put("name", "")
})
}
cursor.close()
database.execSQL("DROP TABLE IF EXISTS `sensors`")
database.execSQL("CREATE TABLE IF NOT EXISTS `sensors` (`id` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `registered` INTEGER NOT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT NOT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, PRIMARY KEY(`id`))")
sensors.forEach {
database.insert("sensors", OnConflictStrategy.REPLACE, it)
}
}
}
}
}

View file

@ -13,10 +13,10 @@ data class Sensor(
var enabled: Boolean,
@ColumnInfo(name = "registered")
var registered: Boolean,
@ColumnInfo(name = "state_changed")
var stateChanged: Boolean,
@ColumnInfo(name = "state")
var state: String,
@ColumnInfo(name = "last_sent_state")
var lastSentState: String = "",
@ColumnInfo(name = "state_type")
var stateType: String = "",
@ColumnInfo(name = "type")

View file

@ -28,4 +28,7 @@ interface SensorDao {
@Query("DELETE FROM sensor_attributes WHERE sensor_id = :sensorId")
fun clearAttributes(sensorId: String)
@Query("UPDATE sensors SET last_sent_state = :state WHERE id = :sensorId")
fun updateLastSendState(sensorId: String, state: String)
}

View file

@ -251,7 +251,6 @@ class MobileAppIntegrationFragment : Fragment(), MobileAppIntegrationView {
uniqueId,
isChecked,
false,
true,
""
)
sensorDao.add(sensor)

View file

@ -69,7 +69,6 @@ class SensorDetailFragment(
}
updateSensorEntity(isEnabled)
this@SensorDetailFragment.refreshSensorData()
return@setOnPreferenceChangeListener true
}
@ -115,22 +114,24 @@ class SensorDetailFragment(
}
}
findPreference<Preference>("device_class")?.let {
if (sensorData.deviceClass == null)
it.isVisible = false
else
if (sensorData.enabled && sensorData.deviceClass != null) {
it.summary = sensorData.deviceClass
it.isVisible = true
} else {
it.isVisible = false
}
}
findPreference<Preference>("icon")?.let {
if (sensorData.icon == "")
it.isVisible = false
else
if (sensorData.enabled && sensorData.icon != "") {
it.summary = sensorData.icon
it.isVisible = true
} else {
it.isVisible = false
}
}
findPreference<PreferenceCategory>("attributes")?.let {
if (attributes.isNullOrEmpty())
it.isVisible = false
else {
if (sensorData.enabled && !attributes.isNullOrEmpty()) {
attributes.forEach { attribute ->
val key = "attribute_${attribute.name}"
val pref = findPreference(key) ?: Preference(requireContext())
@ -143,7 +144,9 @@ class SensorDetailFragment(
if (!it.contains(pref))
it.addPreference(pref)
}
}
it.isVisible = true
} else
it.isVisible = false
}
}
@ -153,9 +156,10 @@ class SensorDetailFragment(
var sensorEntity = sensorDao.get(basicSensor.id)
if (sensorEntity != null) {
sensorEntity.enabled = isEnabled
sensorEntity.lastSentState = ""
sensorDao.update(sensorEntity)
} else {
sensorEntity = Sensor(basicSensor.id, isEnabled, false, true, "")
sensorEntity = Sensor(basicSensor.id, isEnabled, false, "")
sensorDao.add(sensorEntity)
}
refreshSensorData()

View file

@ -4,7 +4,6 @@ import android.content.Context
import android.content.pm.PackageManager
import android.os.Process.myPid
import android.os.Process.myUid
import android.util.Log
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.sensor.Attribute
@ -40,7 +39,7 @@ interface SensorManager {
// If we haven't created the entity yet do so and default to enabled if required
if (sensor == null) {
sensor = Sensor(sensorId, permission && enabledByDefault, false, true, "")
sensor = Sensor(sensorId, permission && enabledByDefault, false, "")
sensorDao.add(sensor)
}
@ -68,8 +67,6 @@ interface SensorManager {
) {
val sensorDao = AppDatabase.getInstance(context).sensorDao()
val sensor = sensorDao.get(basicSensor.id) ?: return
Log.d("SensorManager", "Old sensor state ${sensor.state} compared to new state $state for ${sensor.name}")
sensor.stateChanged = sensor.stateChanged || (sensor.state != state.toString())
sensor.id = basicSensor.id
sensor.state = state.toString()
sensor.stateType = when (state) {

View file

@ -131,7 +131,7 @@ class SensorReceiver : BroadcastReceiver() {
Log.e(TAG, "Issue registering sensor: ${reg.uniqueId}", e)
}
}
if (sensor?.enabled == true && fullSensor != null && sensor?.registered && sensor?.stateChanged) {
if (sensor?.enabled == true && sensor.registered && sensor.state != sensor.lastSentState) {
enabledRegistrations.add(fullSensor.toSensorRegistration())
}
}
@ -141,6 +141,9 @@ class SensorReceiver : BroadcastReceiver() {
var success = false
try {
success = integrationUseCase.updateSensors(enabledRegistrations.toTypedArray())
enabledRegistrations.forEach {
sensorDao.updateLastSendState(it.uniqueId, it.state.toString())
}
} catch (e: Exception) {
Log.e(TAG, "Exception while updating sensors.", e)
}
@ -151,7 +154,7 @@ class SensorReceiver : BroadcastReceiver() {
val sensor = sensorDao.get(it.uniqueId)
if (sensor != null) {
sensor.registered = false
sensor.stateChanged = false
sensor.lastSentState = ""
sensorDao.update(sensor)
}
}