ucapi-framework 1.9.0b3__tar.gz → 1.9.0b4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/PKG-INFO +1 -1
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/pyproject.toml +1 -1
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/driver.py +12 -6
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entity.py +27 -16
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/PKG-INFO +1 -1
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/README.md +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/setup.cfg +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_button_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_climate_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_config.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_cover_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_device.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_discovery.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_driver.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_factory_pattern.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_helpers.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_ir_emitter_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_light_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_media_player_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_migration.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_remote_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_select_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_sensor_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_setup.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_switch_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/tests/test_voice_assistant_entity.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/__init__.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/config.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/device.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/discovery.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/__init__.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/button.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/climate.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/cover.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/ir_emitter.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/light.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/media_player.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/remote.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/select.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/sensor.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/switch.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/voice_assistant.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/helpers.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/migration.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/setup.py +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/SOURCES.txt +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/dependency_links.txt +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/requires.txt +0 -0
- {ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/top_level.txt +0 -0
|
@@ -594,8 +594,8 @@ class BaseIntegrationDriver(Generic[DeviceT, ConfigT]):
|
|
|
594
594
|
cast(FrameworkEntity, configured_entity) if has_update else None
|
|
595
595
|
)
|
|
596
596
|
|
|
597
|
-
#
|
|
598
|
-
#
|
|
597
|
+
# Coordinator pattern: entity owns its state via sync_state() + subscribe_to_device().
|
|
598
|
+
# Call sync_state() directly and skip all legacy attribute-routing paths.
|
|
599
599
|
if (
|
|
600
600
|
framework_entity
|
|
601
601
|
and type(framework_entity).sync_state is not FrameworkEntity.sync_state
|
|
@@ -1216,9 +1216,10 @@ class BaseIntegrationDriver(Generic[DeviceT, ConfigT]):
|
|
|
1216
1216
|
"""
|
|
1217
1217
|
Handle device connection.
|
|
1218
1218
|
|
|
1219
|
-
Sets integration device state to CONNECTED and refreshes
|
|
1220
|
-
|
|
1221
|
-
|
|
1219
|
+
Sets integration device state to CONNECTED and refreshes state for legacy-pattern
|
|
1220
|
+
entities. Coordinator-pattern entities (those with ``sync_state()`` overridden)
|
|
1221
|
+
are skipped here — they receive state via ``push_update()`` emitted by the
|
|
1222
|
+
device's own ``connect()`` implementation, avoiding a redundant double-sync.
|
|
1222
1223
|
|
|
1223
1224
|
:param device_id: Device identifier
|
|
1224
1225
|
"""
|
|
@@ -1230,8 +1231,13 @@ class BaseIntegrationDriver(Generic[DeviceT, ConfigT]):
|
|
|
1230
1231
|
|
|
1231
1232
|
await self.api.set_device_state(ucapi.DeviceStates.CONNECTED)
|
|
1232
1233
|
|
|
1233
|
-
# Refresh entity states now that device is connected and has populated attributes
|
|
1234
1234
|
for entity_id in self.get_entity_ids_for_device(device_id):
|
|
1235
|
+
configured_entity = self.api.configured_entities.get(entity_id)
|
|
1236
|
+
if isinstance(configured_entity, FrameworkEntity) and (
|
|
1237
|
+
type(configured_entity).sync_state is not FrameworkEntity.sync_state
|
|
1238
|
+
):
|
|
1239
|
+
# Coordinator pattern: entity syncs via push_update() from device.connect()
|
|
1240
|
+
continue
|
|
1235
1241
|
await self.refresh_entity_state(entity_id)
|
|
1236
1242
|
|
|
1237
1243
|
async def on_device_disconnected(self, device_id: str) -> None:
|
|
@@ -173,6 +173,10 @@ class Entity(ABC):
|
|
|
173
173
|
:param update: dictionary containing the updated properties.
|
|
174
174
|
:param force: if True, update attributes even if they haven't changed.
|
|
175
175
|
"""
|
|
176
|
+
# Skip entirely if this entity is not configured on the Remote.
|
|
177
|
+
if not self._api.configured_entities.contains(self._framework_entity_id):
|
|
178
|
+
return
|
|
179
|
+
|
|
176
180
|
# Strip None values - they represent attributes that have never been set
|
|
177
181
|
# and should not be sent to the Remote. Empty strings and False are valid.
|
|
178
182
|
update = {k: v for k, v in update.items() if v is not None}
|
|
@@ -289,8 +293,9 @@ class Entity(ABC):
|
|
|
289
293
|
def on_device_disconnected(self):
|
|
290
294
|
self.set_unavailable()
|
|
291
295
|
"""
|
|
292
|
-
|
|
293
|
-
|
|
296
|
+
# All ucapi States enums share the same UNAVAILABLE string value,
|
|
297
|
+
# so media_player.States.UNAVAILABLE works as a proxy for all entity types.
|
|
298
|
+
self.update({media_player.Attributes.STATE: media_player.States.UNAVAILABLE})
|
|
294
299
|
|
|
295
300
|
def subscribe_to_device(self, device: "BaseDeviceInterface") -> None:
|
|
296
301
|
"""
|
|
@@ -317,11 +322,10 @@ class Entity(ABC):
|
|
|
317
322
|
self.subscribe_to_device(device)
|
|
318
323
|
|
|
319
324
|
async def sync_state(self) -> None:
|
|
320
|
-
self.
|
|
321
|
-
self._device.state
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
self.update(self.attributes)
|
|
325
|
+
self.update({
|
|
326
|
+
light.Attributes.STATE: self.map_entity_states(self._device.state),
|
|
327
|
+
light.Attributes.BRIGHTNESS: self._device.brightness,
|
|
328
|
+
})
|
|
325
329
|
"""
|
|
326
330
|
from .device import DeviceEvents # local import to avoid circular dependency
|
|
327
331
|
|
|
@@ -335,9 +339,9 @@ class Entity(ABC):
|
|
|
335
339
|
"""
|
|
336
340
|
Sync entity state from device to Remote.
|
|
337
341
|
|
|
338
|
-
Override this method to read current values from ``self._device
|
|
339
|
-
|
|
340
|
-
|
|
342
|
+
Override this method to read current values from ``self._device`` and call
|
|
343
|
+
``self.update()`` with a **fresh dict or dataclass** — do not mutate
|
|
344
|
+
``self.attributes`` directly, as that would defeat change-filtering.
|
|
341
345
|
|
|
342
346
|
The framework calls this method automatically in two situations:
|
|
343
347
|
|
|
@@ -351,14 +355,21 @@ class Entity(ABC):
|
|
|
351
355
|
manages its own state (i.e. the developer is not using the driver's
|
|
352
356
|
default ``on_device_update`` attribute-routing logic).
|
|
353
357
|
|
|
354
|
-
Example::
|
|
358
|
+
Example with dict::
|
|
355
359
|
|
|
356
360
|
async def sync_state(self) -> None:
|
|
357
|
-
self.
|
|
358
|
-
self._device.state
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
self.update({
|
|
362
|
+
light.Attributes.STATE: self.map_entity_states(self._device.state),
|
|
363
|
+
light.Attributes.BRIGHTNESS: self._device.brightness,
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
Example with dataclass (recommended)::
|
|
367
|
+
|
|
368
|
+
async def sync_state(self) -> None:
|
|
369
|
+
self.update(LightAttributes(
|
|
370
|
+
STATE=self.map_entity_states(self._device.state),
|
|
371
|
+
BRIGHTNESS=self._device.brightness,
|
|
372
|
+
))
|
|
362
373
|
"""
|
|
363
374
|
# No-op by default. Subclasses override to pull from device and push to Remote.
|
|
364
375
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/media_player.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework/entities/voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ucapi_framework-1.9.0b3 → ucapi_framework-1.9.0b4}/ucapi_framework.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|