Multiple identities via config subentries + regenerate + editable name
Refactor from one-entry-one-identity to a hub config entry (shared RNS stack) plus config subentries of type "identity", each an independent LXMF identity with its own LXMRouter, address, device and entities. This is required because LXMRouter allows only one delivery identity per instance. - ReticulumStack: owns the single process-wide RNS instance, peer discovery and interface telemetry (hub device). - IdentityManager: one per subentry, its own LXMRouter/identity/destination and delivery callback (identity device). Signal handlers suppressed and atexit handlers tamed for each router too. - Config subentry flow (add / reconfigure identity); a first identity is seeded on stack creation. Editable announce display name = subentry title. - Per-identity "Regenerate identity" button: new address + re-announce (clears the router's single delivery destination first). - Services and events gain an "identity" field to target/distinguish identities. - Per-identity assist bridge, greeting-when-assist-off, allow-list, notify, announce/sync buttons, message counters; hub-level connectivity/telemetry/peers. Verified against current HA config_entries/entity_platform/selector and RNS/LXMF sources. Docs and en/ru translations updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,44 +1,80 @@
|
||||
"""Base entity for the Reticulum integration."""
|
||||
"""Base entities for the Reticulum integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigSubentry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN, SIGNAL_STATE_UPDATED
|
||||
from .reticulum_client import ReticulumManager
|
||||
from .const import DOMAIN, identity_signal, stack_signal
|
||||
from .reticulum_client import IdentityManager, ReticulumStack
|
||||
|
||||
|
||||
class ReticulumEntity(Entity):
|
||||
"""Base class wiring entities to the manager state updates."""
|
||||
class ReticulumStackEntity(Entity):
|
||||
"""Base for hub (stack-level) entities."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, manager: ReticulumManager, entry_id: str) -> None:
|
||||
self._manager = manager
|
||||
self._entry_id = entry_id
|
||||
def __init__(self, stack: ReticulumStack, entry: ConfigEntry) -> None:
|
||||
self._stack = stack
|
||||
self._entry = entry
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, entry_id)},
|
||||
identifiers={(DOMAIN, entry.entry_id)},
|
||||
name="Reticulum",
|
||||
manufacturer="Reticulum Network Stack",
|
||||
model="LXMF Peer",
|
||||
sw_version=manager.state.lxmf_address or None,
|
||||
model="RNS stack",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
configuration_url="https://reticulum.network/",
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to state-update signals."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_STATE_UPDATED, self._handle_update
|
||||
self.hass, stack_signal(self._entry.entry_id), self._handle_update
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_update(self) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ReticulumIdentityEntity(Entity):
|
||||
"""Base for per-identity (subentry) entities."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
manager: IdentityManager,
|
||||
entry: ConfigEntry,
|
||||
subentry: ConfigSubentry,
|
||||
) -> None:
|
||||
self._manager = manager
|
||||
self._entry = entry
|
||||
self._subentry = subentry
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, subentry.subentry_id)},
|
||||
name=subentry.title,
|
||||
manufacturer="Reticulum Network Stack",
|
||||
model="LXMF identity",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
via_device=(DOMAIN, entry.entry_id),
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
identity_signal(self._subentry.subentry_id),
|
||||
self._handle_update,
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_update(self) -> None:
|
||||
# Decorated with @callback so the dispatcher runs it on the event loop
|
||||
# (a plain sync callback would be offloaded to an executor thread, and
|
||||
# async_write_ha_state is not thread-safe).
|
||||
self.async_write_ha_state()
|
||||
|
||||
Reference in New Issue
Block a user