A failed DTLS handshake on the very first poll was being swallowed, so
the config entry loaded with no entities at all and stayed that way until
the user reloaded that device by hand (issue #254).
_poll_once() connects when there is no session yet, so connect()'s
handshake timeout reaches _async_update_data as a TimeoutError -- the
same type a slow blockwise transfer raises mid-session.
_defer_reconnect_for() only knew the mid-session meaning and deferred it,
making _async_update_data return flatten([], {}) == {} instead of
raising. DataUpdateCoordinator counts any non-raising return as success,
so async_config_entry_first_refresh saw a healthy first refresh and
skipped ConfigEntryNotReady, and setup forwarded the platforms with
`bound` still empty. Platforms enumerate `bound` once and have no dynamic
add-listener, so a later cycle repopulating it added nothing: every
restored entity sat unavailable until a manual reload.
Gate the deferral on self._discovered. Before the first discovery a poll
failure now takes the normal path -- one reconnect attempt, then
UpdateFailed -> ConfigEntryNotReady -- so HA retries on its own backoff
until the handshake goes through.
Two related fixes in the same failure path:
- Close the DTLS session on EVENT_HOMEASSISTANT_STOP, not only on entry
unload. HA does not unload entries on a Core restart, so async_close()
never ran and the previous run's association was left orphaned on the
appliance -- which is what makes the next run's handshake time out in
the first place. The fixed source port still covers the unclean-exit
case where no close_notify can be sent.
- Close the session when first refresh fails. _poll_once deliberately
leaves it open on a TimeoutError, so a failed setup abandoned a bound
UDP socket on a port that is fixed per device by design, and each HA
retry bound another socket to that same port.