Rename ocf/ to registry/, it's a capability registry now
The DTLS/CoAP transport code that made "ocf" an accurate name moved out to the smartthings-local package. What's left here (capability.py, entities.py, discovery.py, adapter.py, identity.py, capabilities/, by_type/, plus the /device/0 batch parser) is entirely the device capability registry, so name the package for what it does. Flattened the redundant ocf/registry/ nesting into a single top-level registry/ package and updated every import across the platform modules and test suite accordingly. Verified: full test suite (80/80) passes, and the Docker dev container reconnects to both live appliances and rediscovers their entities cleanly after the rename.
This commit is contained in:
@@ -110,17 +110,16 @@ custom_components/localthings/
|
||||
sensor.py / binary_sensor.py / switch.py / number.py / select.py / button.py / time.py
|
||||
One module per HA platform
|
||||
strings.json / translations/ Config-flow copy + entity state translations
|
||||
ocf/
|
||||
registry/
|
||||
batch.py /device/0 batch response parsing
|
||||
registry/
|
||||
capability.py Capability dataclass (href, entities, transforms)
|
||||
entities.py Per-platform entity descriptor dataclasses
|
||||
discovery.py Binds a device's live resources to registered capabilities
|
||||
adapter.py Flattens bound entities into HA-ready state
|
||||
identity.py Reads device identity (serial, oneUiVersion) for type detection
|
||||
capabilities/ Shared + per-family Capability definitions (common, dryer, oven,
|
||||
capability.py Capability dataclass (href, entities, transforms)
|
||||
entities.py Per-platform entity descriptor dataclasses
|
||||
discovery.py Binds a device's live resources to registered capabilities
|
||||
adapter.py Flattens bound entities into HA-ready state
|
||||
identity.py Reads device identity (serial, oneUiVersion) for type detection
|
||||
capabilities/ Shared + per-family Capability definitions (common, dryer, oven,
|
||||
dishwasher, fridge, laundry, operational)
|
||||
by_type/ One DeviceRegistry per appliance type, composed from capabilities/
|
||||
by_type/ One DeviceRegistry per appliance type, composed from capabilities/
|
||||
tests/ 80+ tests: registry composition, discovery, entity descriptors,
|
||||
golden-file regression against captured device dumps
|
||||
requirements-dev.txt Test deps, including the smartthings-local package
|
||||
@@ -132,9 +131,9 @@ docker-compose.yml / ha_config/ Local HA dev environment
|
||||
## Adding a new appliance type
|
||||
|
||||
1. Capture the appliance's `/device/0` response to see what resources/fields it exposes. An authenticated `DtlsCoapSession` from `smartthings_local.protocol.dtls_session` GET is enough; `local-tools/probe_device.py` wraps this.
|
||||
2. Reuse existing `Capability` objects from `ocf/registry/capabilities/` wherever the resource matches one already declared. Most `common.py` capabilities (power, kids lock, remote control, alarms, energy/water meters) are shared verbatim across families; add new ones only for resources unique to the new type.
|
||||
3. Create `ocf/registry/by_type/<name>.py` with a `DeviceRegistry(name=..., capabilities=_build([...]))`. Use `pattern_capabilities` instead of `capabilities` for any resource whose `href` isn't fixed (for example per-compartment fridge resources); see `refrigerator.py` for the pattern.
|
||||
4. Register it in `_REGISTRY_BY_KEY` in `ocf/registry/by_type/__init__.py`, keyed on the lowercased, space/hyphen-to-underscore-converted suffix of the device's `oneUiVersion` string (see `_type_key()` in that file for the exact transform).
|
||||
2. Reuse existing `Capability` objects from `registry/capabilities/` wherever the resource matches one already declared. Most `common.py` capabilities (power, kids lock, remote control, alarms, energy/water meters) are shared verbatim across families; add new ones only for resources unique to the new type.
|
||||
3. Create `registry/by_type/<name>.py` with a `DeviceRegistry(name=..., capabilities=_build([...]))`. Use `pattern_capabilities` instead of `capabilities` for any resource whose `href` isn't fixed (for example per-compartment fridge resources); see `refrigerator.py` for the pattern.
|
||||
4. Register it in `_REGISTRY_BY_KEY` in `registry/by_type/__init__.py`, keyed on the lowercased, space/hyphen-to-underscore-converted suffix of the device's `oneUiVersion` string (see `_type_key()` in that file for the exact transform).
|
||||
5. Add golden-file coverage in `tests/` against a captured `/device/0` dump for the new type.
|
||||
|
||||
No config-flow or coordinator changes are needed. Device-type detection and entity wiring are fully driven by the registry.
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import BinarySensorDesc
|
||||
from .registry.entities import BinarySensorDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import ButtonDesc
|
||||
from .registry.entities import ButtonDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -124,7 +124,7 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
|
||||
"""Fetch UUID, mint leaf cert, probe each port. Returns config entry data dict."""
|
||||
import cbor2
|
||||
from smartthings_local.protocol.dtls_session import DtlsCoapSession
|
||||
from .ocf.batch import parse_device0_batch
|
||||
from .registry.batch import parse_device0_batch
|
||||
|
||||
_LOGGER.debug("Fetching Samsung cloud UUID from %s", _SAMSUNG_CLOUD_HOST)
|
||||
try:
|
||||
|
||||
@@ -15,12 +15,12 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
||||
from smartthings_local.protocol.dtls_session import DtlsCoapSession
|
||||
|
||||
from .ocf.batch import parse_device0_batch
|
||||
from .ocf.registry.by_type import for_device
|
||||
from .ocf.registry.discovery import discover, BoundEntity
|
||||
from .ocf.registry import CAPABILITIES
|
||||
from .ocf.registry.adapter import flatten
|
||||
from .ocf.registry.identity import read_identity, DeviceIdentity
|
||||
from .registry.batch import parse_device0_batch
|
||||
from .registry.by_type import for_device
|
||||
from .registry.discovery import discover, BoundEntity
|
||||
from .registry import CAPABILITIES
|
||||
from .registry.adapter import flatten
|
||||
from .registry.identity import read_identity, DeviceIdentity
|
||||
|
||||
from .const import (
|
||||
DOMAIN, CONF_HOST, CONF_PORT, CONF_LEAF_CERT_PEM, CONF_LEAF_KEY_PEM,
|
||||
|
||||
@@ -7,8 +7,8 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.const import EntityCategory
|
||||
|
||||
from .ocf.registry.adapter import _key
|
||||
from .ocf.registry.discovery import BoundEntity
|
||||
from .registry.adapter import _key
|
||||
from .registry.discovery import BoundEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import NumberDesc
|
||||
from .registry.entities import NumberDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
"""Samsung appliance local-API library (custom_components/localthings/ocf).
|
||||
|
||||
CoAP/DTLS session management, OCF batch resource parsing, and a
|
||||
capability registry for discovering and reading Samsung appliance state."""
|
||||
__version__ = "2.0.0"
|
||||
@@ -1,5 +0,0 @@
|
||||
from .adapter import flatten, is_active, _key
|
||||
from .discovery import BoundEntity, discover
|
||||
from .registry import CAPABILITIES
|
||||
|
||||
__all__ = ['CAPABILITIES', 'discover', 'BoundEntity', 'flatten', 'is_active', '_key']
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Device capability registry for Samsung appliances.
|
||||
|
||||
Declares each appliance family's resources as Capability objects keyed on
|
||||
their stable OCF resource href, binds a connected device's live resources
|
||||
to the matching capabilities, and flattens the result into HA-ready entity
|
||||
state. The DTLS/CoAP transport itself lives in the smartthings-local
|
||||
package, not here.
|
||||
"""
|
||||
from .adapter import flatten, is_active, _key
|
||||
from .discovery import BoundEntity, discover
|
||||
from .registry import CAPABILITIES
|
||||
|
||||
__all__ = ['CAPABILITIES', 'discover', 'BoundEntity', 'flatten', 'is_active', '_key']
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import SelectDesc
|
||||
from .registry.entities import SelectDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import SensorDesc
|
||||
from .registry.entities import SensorDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import SwitchDesc
|
||||
from .registry.entities import SwitchDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .ocf.registry.entities import TimeDesc
|
||||
from .registry.entities import TimeDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ FIXTURES = Path(__file__).resolve().parent / 'fixtures'
|
||||
|
||||
|
||||
def _resources_from_dump(dump: dict) -> dict[str, dict]:
|
||||
from custom_components.localthings.ocf.batch import parse_device0_batch
|
||||
from custom_components.localthings.registry.batch import parse_device0_batch
|
||||
return parse_device0_batch(dump['device0'])
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ ENTRY_DATA = {
|
||||
|
||||
|
||||
def _load_fridge_resources() -> dict:
|
||||
from custom_components.localthings.ocf.batch import parse_device0_batch
|
||||
from custom_components.localthings.registry.batch import parse_device0_batch
|
||||
data = json.loads((FIXTURES / 'refrigerator_device.json').read_text())
|
||||
return parse_device0_batch(data['device0'])
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for samsung_appliance/registry/by_type."""
|
||||
import pytest
|
||||
from custom_components.localthings.ocf.registry.by_type import _type_key, for_device, DeviceRegistry
|
||||
from custom_components.localthings.registry.by_type import _type_key, for_device, DeviceRegistry
|
||||
|
||||
|
||||
class TestTypeKey:
|
||||
|
||||
@@ -4,10 +4,10 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from custom_components.localthings.ocf.registry.adapter import _key
|
||||
from custom_components.localthings.ocf.registry.by_type import dishwasher, refrigerator
|
||||
from custom_components.localthings.ocf.registry.capabilities import fridge
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.registry.adapter import _key
|
||||
from custom_components.localthings.registry.by_type import dishwasher, refrigerator
|
||||
from custom_components.localthings.registry.capabilities import fridge
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_resources
|
||||
|
||||
GOLDEN = Path(__file__).parent / 'fixtures' / 'golden'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from custom_components.localthings.ocf.registry.capability import Capability
|
||||
from custom_components.localthings.ocf.registry.entities import BinarySensorDesc
|
||||
from custom_components.localthings.registry.capability import Capability
|
||||
from custom_components.localthings.registry.entities import BinarySensorDesc
|
||||
|
||||
|
||||
def test_capability_defaults():
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from custom_components.localthings.ocf.registry.capabilities import common
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.registry.capabilities import common
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
|
||||
|
||||
def _reg():
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from custom_components.localthings.ocf.registry.capability import Capability
|
||||
from custom_components.localthings.ocf.registry.discovery import (
|
||||
from custom_components.localthings.registry.capability import Capability
|
||||
from custom_components.localthings.registry.discovery import (
|
||||
BoundEntity, discover, instance_suffix,
|
||||
)
|
||||
from custom_components.localthings.ocf.registry.entities import BinarySensorDesc
|
||||
from custom_components.localthings.registry.entities import BinarySensorDesc
|
||||
|
||||
LOCK = Capability(
|
||||
href='/kidslock/vs/0',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from tests.conftest import _load_device
|
||||
from custom_components.localthings.ocf.registry.by_type import for_device, _type_key
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.ocf.registry.adapter import flatten, is_active
|
||||
from custom_components.localthings.registry.by_type import for_device, _type_key
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.adapter import flatten, is_active
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,expected_type_key', [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from custom_components.localthings.ocf.registry.entities import (
|
||||
from custom_components.localthings.registry.entities import (
|
||||
SensorDesc, BinarySensorDesc, SelectDesc, SwitchDesc, ButtonDesc,
|
||||
NumberDesc, PLATFORM_OF,
|
||||
)
|
||||
|
||||
@@ -7,9 +7,9 @@ pattern capability that auto-derives distinct keys from the href segments.
|
||||
"""
|
||||
from tests.conftest import _load_resources
|
||||
|
||||
from custom_components.localthings.ocf.registry.adapter import _key
|
||||
from custom_components.localthings.ocf.registry.by_type import refrigerator
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.registry.adapter import _key
|
||||
from custom_components.localthings.registry.by_type import refrigerator
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
|
||||
|
||||
def test_two_doors_get_distinct_keys():
|
||||
|
||||
@@ -7,14 +7,14 @@ GOLDEN = Path(__file__).parent / 'fixtures' / 'golden'
|
||||
|
||||
|
||||
def _new_state_keys(name, resources):
|
||||
from custom_components.localthings.ocf.registry.by_type import for_device
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.ocf.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
otn = resources.get('/otninformation/vs/0', {})
|
||||
one_ui = otn.get('swVersionInfo', {}).get('oneUiVersion', '')
|
||||
reg = for_device(one_ui) if one_ui else None
|
||||
if reg is None:
|
||||
from custom_components.localthings.ocf.registry.registry import CAPABILITIES
|
||||
from custom_components.localthings.registry.registry import CAPABILITIES
|
||||
caps, pats = CAPABILITIES, []
|
||||
else:
|
||||
caps, pats = reg.capabilities, reg.pattern_capabilities
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import cbor2
|
||||
|
||||
from custom_components.localthings.ocf.registry.identity import DeviceIdentity, read_identity
|
||||
from custom_components.localthings.registry.identity import DeviceIdentity, read_identity
|
||||
|
||||
|
||||
class FakeSession:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Write-handler unit tests for laundry-family capabilities."""
|
||||
from custom_components.localthings.ocf.registry.capabilities import laundry
|
||||
from custom_components.localthings.ocf.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
from custom_components.localthings.registry.capabilities import laundry
|
||||
from custom_components.localthings.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
|
||||
|
||||
def test_start_button_writes_run():
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from custom_components.localthings.ocf.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
from custom_components.localthings.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
|
||||
|
||||
def test_machine_state_maps_samsung_to_ocf():
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Unit tests for oven-family capabilities."""
|
||||
from custom_components.localthings.ocf.registry.capabilities import oven
|
||||
from custom_components.localthings.ocf.registry.discovery import discover
|
||||
from custom_components.localthings.registry.capabilities import oven
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Tests for the CAPABILITIES registry."""
|
||||
from custom_components.localthings.ocf.registry.registry import CAPABILITIES
|
||||
from custom_components.localthings.registry.registry import CAPABILITIES
|
||||
|
||||
|
||||
def test_registry_is_keyed_by_href():
|
||||
|
||||
Reference in New Issue
Block a user