From 600937ad46eae130037491c5f1acc5e20fa4b4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o=20Gon=C3=A7alves?= <5808343+bgoncal@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:06:14 +0100 Subject: [PATCH] Cache config/entity_registry/list_for_display (#3268) ## Summary So we can use the "decimal places" value to display sensors widget correctly CC: @Penait1 ## Screenshots ## Link to pull request in Documentation repository Documentation: home-assistant/companion.home-assistant# ## Any other notes --- HomeAssistant.xcodeproj/project.pbxproj | 8 ++++ .../Extensions/GRDB+Initialization.swift | 30 +++++++++++++ .../Shared/EntityRegistryListForDisplay.swift | 33 +++++++++++++++ .../PeriodicAppEntitiesModelUpdater.swift | 42 +++++++++++++++++++ Sources/Shared/HATypedRequest+App.swift | 6 +++ 5 files changed, 119 insertions(+) create mode 100644 Sources/Shared/EntityRegistryListForDisplay.swift diff --git a/HomeAssistant.xcodeproj/project.pbxproj b/HomeAssistant.xcodeproj/project.pbxproj index c1e6a9dbe..13837ffdd 100644 --- a/HomeAssistant.xcodeproj/project.pbxproj +++ b/HomeAssistant.xcodeproj/project.pbxproj @@ -577,6 +577,9 @@ 422E25EE2C80019D00256D87 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 420B100B2B1D204400D383D8 /* Assets.xcassets */; }; 422E626C2CDCF00A00987BD0 /* AreaProvider.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422E626B2CDCF00A00987BD0 /* AreaProvider.test.swift */; }; 422F951F2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */; }; + 42333ADB2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; + 42333ADC2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; + 42333ADD2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; 4235075D2CDB756800A19902 /* HAServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4235075C2CDB756800A19902 /* HAServices.swift */; }; 4235075E2CDB756800A19902 /* HAServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4235075C2CDB756800A19902 /* HAServices.swift */; }; 4239D1832C4FFCCE003497FC /* WatchUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */; }; @@ -1857,6 +1860,7 @@ 422E25EC2C7FF28900256D87 /* ControlScriptsValueProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlScriptsValueProvider.swift; sourceTree = ""; }; 422E626B2CDCF00A00987BD0 /* AreaProvider.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AreaProvider.test.swift; sourceTree = ""; }; 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAApplicationShortcutItem.swift; sourceTree = ""; }; + 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityRegistryListForDisplay.swift; sourceTree = ""; }; 4235075C2CDB756800A19902 /* HAServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAServices.swift; sourceTree = ""; }; 4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchUserDefaults.swift; sourceTree = ""; }; 423F44EF2C17238200766A99 /* ChatBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBubbleView.swift; sourceTree = ""; }; @@ -4932,6 +4936,7 @@ 4278CB822D01F09400CFAAC9 /* HAGesture.swift */, 424D2D0F2C89DACE00C610F1 /* HAAppEntity.swift */, 42BB4C362CD26490003E47FD /* HATypedRequest+App.swift */, + 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */, 42F1DA6F2B4EE2E8002729BC /* HAAreaResponse.swift */, 426D9C722C9C582F00F278AF /* ControlEntityProvider.swift */, 42A47D4A2C9AEF10003C597D /* DataWidgetsUpdater.swift */, @@ -6981,6 +6986,7 @@ 1185DFAE271FF53800ED7D9A /* OnboardingAuthStepConfig.swift in Sources */, 425573CE2B5574F100145217 /* CarPlayAreasViewModel.swift in Sources */, 42BA1BC92C8864C200A2FC36 /* OpenPageAppIntent.swift in Sources */, + 42333ADD2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */, B661FB7A226C197900E541DD /* OnboardingManualURLViewController.swift in Sources */, 119A827C252A3C4700D7000D /* NFCNDEFPayload+Additions.swift in Sources */, 42AC94A52CF872520050A62C /* TileCardStyleModifier.swift in Sources */, @@ -7207,6 +7213,7 @@ 113D29DF24946EDA0014067C /* CLLocationManager+OneShotLocation.swift in Sources */, 11CFD78227364F450082D557 /* Identifier.swift in Sources */, 11AF4D17249C8083006C74C0 /* With.swift in Sources */, + 42333ADB2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */, 11B38EF7275C54A300205C7B /* UpdateSensorsIntentHandler.swift in Sources */, 1141182B24AFA10900E6525C /* WebhookResponseHandler.swift in Sources */, 426266462C11B02C0081A818 /* InteractiveImmediateMessages.swift in Sources */, @@ -7453,6 +7460,7 @@ 4251AAC12C6CE9C4004CCC9D /* WatchConfig.swift in Sources */, D03D893620E0AEFA00D4F28D /* Environment.swift in Sources */, D0EEF2CE214D8AE200D1D360 /* RealmZone.swift in Sources */, + 42333ADC2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */, 11C65CC0249838EB00D07FC7 /* StreamCameraResponse.swift in Sources */, B6A258452232485300ADD202 /* Alamofire+EncryptedResponses.swift in Sources */, 1182620424F9C453000795C6 /* HACoreMediaObjectSystem.swift in Sources */, diff --git a/Sources/Shared/Common/Extensions/GRDB+Initialization.swift b/Sources/Shared/Common/Extensions/GRDB+Initialization.swift index 802a61159..aa040c460 100644 --- a/Sources/Shared/Common/Extensions/GRDB+Initialization.swift +++ b/Sources/Shared/Common/Extensions/GRDB+Initialization.swift @@ -7,6 +7,7 @@ public enum GRDBDatabaseTable: String { case assistPipelines case carPlayConfig case clientEvent + case appEntityRegistryListForDisplay } public enum DatabaseTables { @@ -47,6 +48,13 @@ public enum DatabaseTables { case jsonPayload case date } + + public enum AppEntityRegistryListForDisplay: String { + case id + case serverId + case entityId + case registry + } } public extension DatabaseQueue { @@ -85,6 +93,8 @@ public extension DatabaseQueue { var shouldCreateWatchConfig = false var shouldCreateCarPlayConfig = false var shouldCreateClientEvent = false + var shouldCreateAppEntityRegistryListForDisplay = false + do { try database.read { db in shouldCreateHAppEntity = try !db.tableExists(GRDBDatabaseTable.HAAppEntity.rawValue) @@ -92,6 +102,8 @@ public extension DatabaseQueue { shouldCreateCarPlayConfig = try !db.tableExists(GRDBDatabaseTable.carPlayConfig.rawValue) shouldCreateAssistPipelines = try !db.tableExists(GRDBDatabaseTable.assistPipelines.rawValue) shouldCreateClientEvent = try !db.tableExists(GRDBDatabaseTable.clientEvent.rawValue) + shouldCreateAppEntityRegistryListForDisplay = try !db + .tableExists(GRDBDatabaseTable.appEntityRegistryListForDisplay.rawValue) } } catch { let errorMessage = "Failed to check if GRDB tables exist, error: \(error.localizedDescription)" @@ -180,5 +192,23 @@ public extension DatabaseQueue { Current.Log.error(errorMessage) } } + + // AppEntityRegistryListForDisplay + if shouldCreateAppEntityRegistryListForDisplay { + do { + try database.write { db in + try db.create(table: GRDBDatabaseTable.appEntityRegistryListForDisplay.rawValue) { t in + t.primaryKey(DatabaseTables.AppEntityRegistryListForDisplay.id.rawValue, .text).notNull() + t.column(DatabaseTables.AppEntityRegistryListForDisplay.serverId.rawValue, .text).notNull() + t.column(DatabaseTables.AppEntityRegistryListForDisplay.entityId.rawValue, .text).notNull() + t.column(DatabaseTables.AppEntityRegistryListForDisplay.registry.rawValue, .jsonText).notNull() + } + } + } catch { + let errorMessage = + "Failed to create AppEntityRegistryListForDisplay GRDB table, error: \(error.localizedDescription)" + Current.Log.error(errorMessage) + } + } } } diff --git a/Sources/Shared/EntityRegistryListForDisplay.swift b/Sources/Shared/EntityRegistryListForDisplay.swift new file mode 100644 index 000000000..682368b75 --- /dev/null +++ b/Sources/Shared/EntityRegistryListForDisplay.swift @@ -0,0 +1,33 @@ +import Foundation +import GRDB +import HAKit + +public struct EntityRegistryListForDisplay: HADataDecodable { + public let entityCategories: [String: String] + public let entities: [Entity] + + public init(data: HAData) throws { + self.entityCategories = try data.decode("entity_categories") + self.entities = try data.decode("entities") + } + + public struct Entity: HADataDecodable, Codable, FetchableRecord, PersistableRecord { + public let entityId: String + public let entityCategory: Int? + public let decimalPlaces: Int? + + public init(data: HAData) throws { + self.entityId = try data.decode("ei") + self.entityCategory = try? data.decode("ec") + self.decimalPlaces = try? data.decode("dp") + } + } +} + +public struct AppEntityRegistryListForDisplay: Codable, FetchableRecord, PersistableRecord { + /// serverId-entityId + let id: String + let serverId: String + let entityId: String + let registry: EntityRegistryListForDisplay.Entity +} diff --git a/Sources/Shared/Environment/PeriodicAppEntitiesModelUpdater.swift b/Sources/Shared/Environment/PeriodicAppEntitiesModelUpdater.swift index 5ba309320..8f58e3166 100644 --- a/Sources/Shared/Environment/PeriodicAppEntitiesModelUpdater.swift +++ b/Sources/Shared/Environment/PeriodicAppEntitiesModelUpdater.swift @@ -1,4 +1,5 @@ import Foundation +import GRDB import HAKit public protocol PeriodicAppEntitiesModelUpdaterProtocol { @@ -26,6 +27,8 @@ final class PeriodicAppEntitiesModelUpdater: PeriodicAppEntitiesModelUpdaterProt cancelOnGoingRequests() Current.servers.all.forEach { server in guard server.info.connection.activeURL() != nil else { return } + + // Cache entities let requestToken = Current.api(for: server)?.connection.send( HATypedRequest<[HAEntity]>.fetchStates(), completion: { result in @@ -38,6 +41,45 @@ final class PeriodicAppEntitiesModelUpdater: PeriodicAppEntitiesModelUpdaterProt } ) requestTokens.append(requestToken) + + // Cache entities registry list for display + let requestToken2 = Current.api(for: server)?.connection.send( + HATypedRequest.fetchEntityRegistryListForDisplay(), + completion: { [weak self] result in + switch result { + case let .success(response): + self?.saveEntityRegistryListForDisplay(response, serverId: server.identifier.rawValue) + case let .failure(error): + Current.Log.error("Failed to fetch states: \(error)") + } + } + ) + requestTokens.append(requestToken2) + } + } + + private func saveEntityRegistryListForDisplay(_ response: EntityRegistryListForDisplay, serverId: String) { + let entitiesListForDisplay = response.entities.filter({ $0.decimalPlaces != nil || $0.entityCategory != nil }) + .map { registry in + AppEntityRegistryListForDisplay( + id: ServerEntity.uniqueId(serverId: serverId, entityId: registry.entityId), + serverId: serverId, + entityId: registry.entityId, + registry: registry + ) + } + do { + try Current.database.write { db in + try AppEntityRegistryListForDisplay + .filter(Column(DatabaseTables.AppEntityRegistryListForDisplay.serverId.rawValue) == serverId) + .deleteAll(db) + for record in entitiesListForDisplay { + try record.save(db) + } + } + } catch { + Current.Log + .error("Failed to save EntityRegistryListForDisplay in database, error: \(error.localizedDescription)") } } diff --git a/Sources/Shared/HATypedRequest+App.swift b/Sources/Shared/HATypedRequest+App.swift index b75a98e69..3bbfd3986 100644 --- a/Sources/Shared/HATypedRequest+App.swift +++ b/Sources/Shared/HATypedRequest+App.swift @@ -114,4 +114,10 @@ public extension HATypedRequest { type: .rest(.get, "states") )) } + + static func fetchEntityRegistryListForDisplay() -> HATypedRequest { + HATypedRequest(request: .init( + type: .webSocket("config/entity_registry/list_for_display") + )) + } }