Two fixes for the alarm_code diagnostic sensor:
1. _is_included now treats an empty rep ({}) from /device/0 as a stub
meaning the resource exists but hasn't been individually fetched yet.
Previously the field-presence check against {} returned False, leaving
the entity unregistered and showing Unavailable from the entity registry.
2. Replace _last_alarm_code (silently dropped all but the final alarm) with
_active_alarm_codes which joins all codes in the items list. Shows 'none'
when no alarms are active.
Samsung appliances silently drop CoAP requests when hit faster than their
firmware ceiling (~8–14 req/s measured; dishwasher unknown). Multi-block
Block2 fetches on LAN hit ~100 req/s instantaneously, causing the observed
burst timeouts on block 4 and block 10 of /device/0.
Adds rate_limit_rps (default 5 req/s / 200 ms) to DtlsCoapSession with a
pace() method that enforces the inter-block delay via _stop.wait() so session
teardown interrupts the sleep cleanly. Coordinator calls pace() between hrefs
in _poll_hrefs_blocking. Also adds device IP to all GET log lines for easier
per-device filtering.
_is_included now checks that an entity's field exists in the resource
rep before registering it, preventing phantom entities when optional
fields are absent on a shared resource. Explicit exists_fn still takes
priority for cases requiring custom logic. All platforms use the shared
helper; select.py's inline duplicate removed.
- Add strip_prefix_in_key to Capability so pattern caps can drop their
href_prefix segments from key derivation; temp caps use it so
/temperature/desired/cooler/0 → "Cooler Setpoint" and
/temperature/current/cooler/0 → "Cooler Temperature" instead of the
verbose "Temperature Desired Cooler Temp F"
- Change TEMP_CURRENT key to 'temperature' and TEMP_SETPOINT to 'setpoint'
to keep state keys distinct and names readable
- Number entities now render as sliders instead of text boxes
- Remove ICEMAKER_STATUS sensor (redundant with per-unit enabled switches)
- Remove Cabinet light level sensor and Cabinet light on binary sensor
(redundant with Cabinet light switch)
- Add button platform (button.py + PLATFORMS) so oven/washer Start/Pause/Stop
controls actually appear in HA
- Load DTLS cert chain from memory via _load_pem_chain(); eliminate all temp
file writes for key material in config_flow and coordinator
- Fix PEM boundary: ensure leaf and CA blocks are separated by a newline in
the stored fullchain regardless of whether the user pasted a trailing newline
- Dryer mode select: add static options tuple and value_fn to decode Course_XX
hex strings back to human names; was permanently empty before
- Remove callable branch from LocalThingsSelect (options_field covers dynamic
case; callable contract was broken — called with no args vs Callable[[dict]])
- Guard native_min/max_value overrides with hasattr + super() fallback to
prevent AttributeError when native_min=None and range_field returns nothing
- Add async_close() public method to coordinator; __init__ now calls it instead
of reaching in to _close_session directly
- Update tests: rename CONF_CERT_PEM/KEY_PEM → CA/LEAF variants, add
leaf_cert_pem/leaf_key_pem to mock_probe return, fix mock PEM constants
(no trailing newlines, matching what .strip() stores)
Config flow now accepts the AC14K_M CA cert+key, fetches the Samsung cloud
UUID from connect-v2.samsungiotcloud.com, mints a fresh RSA-2048 leaf cert
signed by the CA, and stores both the CA keypair and generated leaf for
reconnect. The DTLS context uses @SECLEVEL=0 in the cipher string — the only
channel that reaches cryptography's bundled OpenSSL — to permit the SHA-1
signed AC14K_M intermediate in Samsung's cert chain.
Entity model fixes:
- EntityCategory string values coerced to EntityCategory enum in entity.py
- SelectDesc gains options_field for live options from resource data; select
registration skips entities where exists_fn rejects the initial resource
- NumberDesc gains range_field for live min/max from resource data
- Temperature sensors/numbers now carry device_class and unit (°F)
- Icemaker state sensor and on binary_sensor removed (duplicates of switch)
- Ice type select gated on x.com.samsung.da.iceType.supported presence
- Sabbath mode icon updated to mdi:hands-pray
use_certificate_chain_file sends the full PEM chain including the
AC14K_M intermediate CA, which is SHA-1 signed. OpenSSL 3.x at
SECLEVEL=2 raises SSL_R_CA_MD_TOO_WEAK when it sees this cert — before
the handshake starts, before any verify callback fires.
Switch to use_certificate_file, which reads only the first PEM block
(the leaf cert, SHA-256 signed). Samsung's device already has AC14K_M
in its trust store and can verify the leaf without receiving the chain.
Also add verbose debug logging to _set_security_level so the logs show
exactly which API path ran and whether it succeeded, to aid diagnosis
if the error recurs after a library update.
The fullchain.pem we send as our client cert contains the AC14K_M
intermediate CA, which is SHA-1 signed. Samsung's OCF Root CA (trust
anchor) is also SHA-1 self-signed. OpenSSL 3.x SECLEVEL=2 rejects
SHA-1 in any certificate it touches — including our own outbound chain —
raising SSL_R_CA_MD_TOO_WEAK before the verify callback is reached.
Fix: call SSL_CTX_set_security_level(ctx, 1) before loading any certs.
Tries pyOpenSSL >= 24.0 ctx.set_security_level(), falls back to cffi,
then ctypes as a last resort. SECLEVEL=1 (80-bit minimum) allows SHA-1
while still enforcing meaningful key-strength constraints.
@SECLEVEL=1 in the cipher string does not reliably lower the security
level for DTLS_METHOD contexts across all pyOpenSSL/OpenSSL builds.
Instead, explicitly allow error 66 (X509_V_ERR_CA_MD_TOO_WEAK) in the
verify callback. This is the error OpenSSL emits when a CA certificate
uses SHA-1 — which Samsung's OCF Root CA does. All other verification
errors (chain trust, expiry, key usage, revocation) are still enforced.
OpenSSL 3.x defaults to SECLEVEL=2, which rejects SHA-1 digests in the
certificate chain. Samsung's OCF Root CA is SHA-1 signed, so peer
verification failed with "ca md too weak" on every connect attempt.
SECLEVEL=1 (80-bit minimum) allows SHA-1 in the CA chain while still
enforcing meaningful key-strength and cipher constraints. Not going back
to SECLEVEL=0 which removes all restrictions.
- translations/en.json: runtime translation file HA loads (strings.json
is tooling-only); includes data_description hints for host, cert, key
- config_flow.py: cert_pem and key_pem rendered as multiline text areas
via TextSelector so users can paste full PEM blocks; host stays single-line
- strings.json: expanded error messages and data_description to match
- entity.py: entities with name=None (pattern-cap doors, temps, icemmakers)
now derive a human-readable title from their state key instead of
displaying blank ("door_cooler_open1" → "Door Cooler Open 1")
Makes the integration fully self-contained for HACS distribution.
Users installing via HACS get everything in one directory; no external
samsung_appliance package needed.
All component imports updated to relative (.ocf.*); all test imports
updated to custom_components.localthings.ocf.*. ocf_root_ca.pem moves
with the package and resolves correctly via Path(__file__).parent.