Now that mbillow/localthings#8's protocol fixes are merged upstream and smartthings-local 0.1.0 is on PyPI, drop the vendored ocf/coap_dtls.py + ocf/observe_refresh.py transport (dead code, never instantiated) and the duplicated ocf_root_ca.pem in favor of the real package. manifest.json and requirements-dev.txt now pin smartthings-local>=0.1.0 instead of the unmerged git branch. README rewritten from scratch — it still described the old standalone MQTT-bridge (samsung_appliance/, main.py, docker-compose bridge) that this repo moved off of; it now documents the real architecture: a native HA custom component with config-flow-driven cert minting and a per-device-type capability registry, with the protocol layer split out to smartthings-local.
Local Things
A native Home Assistant custom integration for local control of newer-generation Samsung connected appliances. No cloud round-trip, no MQTT broker, no separate bridge process — add a device through HA's normal Settings → Devices & Services flow and it talks CoAP-over-DTLS straight to the appliance on your LAN.
Where things live
This project split into two repos partway through development:
smartthings-local(PyPI package) — the reusable protocol layer: DTLS session handling, CoAP wire encoding, Block2 reads, bounded retry/retransmit, inter-request rate limiting, cert-chain validation. No HA dependency; usable from any Python project.- This repo — the Home Assistant integration built on top of it: config flow, a per-device-type capability registry, the polling coordinator, and all the HA entity classes.
custom_components/localthings/manifest.jsonpulls insmartthings-localfrom PyPI like any other HA integration dependency.
What you get
- Auto-detected device type. Add a host + your CA credentials in the UI; the integration reads the appliance's
oneUiVersionand picks the matching capability registry (dryer, oven, dishwasher, refrigerator) itself. No per-model descriptor to write for a new unit of a type that's already supported. - One-time credential setup, then it's just "add integration." The first device you add asks for the AC14K_M CA cert + key (see Part 2). Every device after that reuses the same stored CA credentials — the config flow mints a fresh per-device leaf cert automatically and only asks for the host IP.
- Standard HA entities, not a hand-rolled dashboard:
sensor,binary_sensor,switch,number,select,button, andtimeplatforms, all backed by a singleDataUpdateCoordinator-driven poller per device. - Capability registry, not a monolith. Each appliance family's resources (power, kids lock, remote control, alarms, energy/water meters, cycle state, per-family settings) are declared once as
Capabilityobjects keyed on the stable OCF resourcehref, verified against live device dumps. - Stale-state resilience. A failed poll doesn't blank out entities — the coordinator falls back to the last-known state rather than flapping devices to
unavailable. - Your state stays on your LAN. HA ↔ appliance is a direct DTLS session; Samsung's cloud sees nothing from this integration. (The appliance still maintains its own connection to Samsung — that's appliance firmware behavior, not something this integration does or can prevent.)
Supported appliance types
| Type | Registry | Notable capabilities |
|---|---|---|
| Dryer | by_type/dryer.py |
Power, kids lock, remote control, alarms, energy meter, operational state, door LED, sound mode, dryer settings/course, job-beginning status, diagnosis, firmware-update sensor |
| Oven | by_type/oven.py |
Power, kids lock, remote control, alarms, cavity state, setpoint, mode, operational state, door, connectivity, firmware-update sensor |
| Dishwasher | by_type/dishwasher.py |
Power, kids lock, remote control, alarms, energy + water meters, water filter, operational state, cycle options/settings, door LED, sound mode/volume, firmware-update sensor |
| Refrigerator | by_type/refrigerator.py |
Power, kids lock, remote control, alarms, energy meter, water filter, status lock, door alert, icemaker (nighttime + generic per-compartment), flex zone, refrigeration mode, autofill, welcome/cabinet lighting, Sabbath mode, beverage zone, plus pattern-matched per-compartment temperature/setpoint/icemaker/door capabilities for multi-cavity fridges, firmware-update sensor |
Other Tizen RT / DAWIT-family appliances almost certainly speak the same protocol underneath (the auth path and CoAP primitives are shared across the fleet) — adding a new type is a new by_type/<name>.py registry file, not a protocol reverse-engineering project. See Adding a new appliance type below.
Part 1 — Is your appliance compatible?
# UDP scan for DTLS-CoAP ports
nmap -Pn -sU -p 49152-49160 "$APPLIANCE_IP"
49154/udpor49155/udpopen|filtered with a DTLS handshake responding → newer firmware (Tizen RT 3.x, DAWIT 3.0+). This is what the integration talks to. The config flow probes both ports automatically — you don't need to know which one your device uses.- Only
8888/tcpopen (token-based HTTPS) → older firmware (~2018–2022). Not supported here.
Part 2 — One-time: get the AC14K_M CA credentials
The integration authenticates with a client cert chained to AC14K_M, an intermediate CA that's been public for years and remains in current firmware trust stores. Every Samsung Tizen/RT-OCF appliance's factory ACL grants the identity in that chain full CRUDN access, so the same CA can mint a working cert for any appliance on your LAN — HA does the per-device minting itself once you give it the CA.
pip install -r requirements-bootstrap.txt
python setup_cert.py
This fetches the AC14K_M CA cert + key + upstream chain from a public mirror and writes them to ./certs/ac14k_m.pem and ./certs/ac14k_m.key. Nothing device-specific happens at this step — no IP, no handshake needed. (Pass --test with TARGET_IP=<appliance-ip> set if you want to sanity-check connectivity against a real device before touching HA at all.)
If the live fetch fails, the script prints an inline workaround: supply AC14K_M_CERT_BUNDLE=/path/to/cert.pem, or point BRAYSTORM_URL=<mirror> at an alternate source.
Why this works
- Every Samsung Tizen/RT-OCF appliance has a factory-baked ACE in
/oic/sec/aclgranting the AC14K_M-chained identityperm=31onhref=*. - TizenRT iotivity derives the peer ID via
memmem(subject_dn, "uuid:")— RDN-agnostic, so a cert with the UUID in any RDN authenticates the same way. - You don't need the original keyholder's private key — the config flow mints its own key and has
AC14K_Msign the leaf. Different key, same identity, same access.
Part 3 — Add the integration in Home Assistant
- Copy
custom_components/localthings/into your HA config'scustom_components/directory (or install via a custom HACS repository — nohacs.jsonis checked in yet, so add this repo as a custom integration repository in HACS if you use it that way). - Restart HA.
- Settings → Devices & Services → Add Integration → Local Things.
- First device: paste the appliance's IP, plus the contents of
certs/ac14k_m.pemandcerts/ac14k_m.keyfrom Part 2. - The flow fetches the current UUID from Samsung's cloud gateway, mints a leaf cert signed by your CA, probes ports
49154/49155, and confirms the device answers/device/0. On success it creates the config entry and detects the device type automatically. - Every subsequent device only asks for the host IP — the stored CA credentials are reused to mint that device's leaf cert.
Entities appear under one HA device per appliance, named Samsung Appliance (<ip>) initially (rename freely — the config entry is keyed on the device's serial, not the name).
Development
Docker Compose dev environment
docker compose up -d
docker compose logs -f
Runs the official home-assistant/home-assistant:stable image with network_mode: host (required — DTLS is UDP and won't traverse Docker's bridge NAT to reach LAN appliances) and custom_components/localthings/ bind-mounted read-only into ha_config/custom_components/. Bump custom_components.localthings to debug in ha_config/configuration.yaml for verbose protocol logging.
Tests
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/pip install pytest-homeassistant-custom-component homeassistant
.venv/bin/pytest tests/ -q
requirements-dev.txt pins smartthings-local the same way manifest.json does, so tests exercise the real published protocol layer rather than a vendored copy.
Repo layout
custom_components/localthings/
manifest.json Requirements (incl. the smartthings-local PyPI dep), version, domain
__init__.py async_setup_entry / async_unload_entry
config_flow.py UUID fetch, leaf cert minting, port probing, config entry creation
coordinator.py DataUpdateCoordinator: polling, stale-state fallback, write dispatch
const.py Domain, config keys, probe ports
entity.py Base entity wiring capability registry -> HA entity
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/
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,
dishwasher, fridge, laundry, operational)
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
setup_cert.py One-shot AC14K_M CA bundle fetcher (Part 2)
requirements-bootstrap.txt Deps for setup_cert.py only
requirements-dev.txt Test deps, including the smartthings-local package
docker-compose.yml / ha_config/ Local HA dev environment
Adding a new appliance type
- Capture the appliance's
/device/0response to see what resources/fields it exposes — an authenticatedDtlsCoapSessionfromsmartthings_local.protocol.dtls_sessionGET is enough;local-tools/probe_device.pywraps this. - Reuse existing
Capabilityobjects fromocf/registry/capabilities/wherever the resource matches one already declared (mostcommon.pycapabilities — 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. - Create
ocf/registry/by_type/<name>.pywith aDeviceRegistry(name=..., capabilities=_build([...])). Usepattern_capabilitiesinstead ofcapabilitiesfor any resource whosehrefisn't fixed (e.g. per-compartment fridge resources) — seerefrigerator.pyfor the pattern. - Register it in
_REGISTRY_BY_KEYinocf/registry/by_type/__init__.py, keyed on the lowercased, space/hyphen-to-underscore-converted suffix of the device'soneUiVersionstring (see_type_key()in that file for the exact transform). - Add golden-file coverage in
tests/against a captured/device/0dump for the new type.
No config-flow or coordinator changes needed — device-type detection and entity wiring are fully driven by the registry.
Known DTLS behavior
Samsung's RT-OCF DTLS stack occasionally closes sessions actively, usually right after a Block2 GET or in the seconds after a POST. Retry/retransmit bounds and inter-request pacing live in the smartthings-local protocol layer (tuned against measured per-firmware request-rate ceilings); reconnect-with-backoff and stale-state fallback live in this repo's coordinator.py. From HA's perspective a brief reconnect looks like an entity holding its last value for one poll cycle rather than going unavailable.
If reconnects become persistent (e.g. more than a handful per minute) something's actually wrong — check the appliance's Wi-Fi link first, then look for a competing DTLS client on the LAN (Samsung's RT-OCF DTLS allows only one active session per peer).
Contributing
Patches welcome — especially:
- New
by_type/registries for appliance families not yet covered (washer, AC, microwave, etc.) on the same Tizen RT 3.x firmware family. - Confirmation/refutation on additional models within an already-supported type.
- Protocol-level fixes belong upstream in
smartthings-local; HA-side fixes (entities, config flow, coordinator, registry) belong here.
If you submit a PR, please don't include real device UUIDs, MACs, serials, IPs, or CA private key material — use the placeholders from the config-flow form.