diff --git a/README.md b/README.md index c03e5a1..9b4ec7b 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,10 @@ will answer. > select that agent here. - **Reply to any sender** / **Allowed sender addresses** – restrict who Assist will answer. -- **Default recipient** – address used by the `notify.reticulum` entity. +- **Default recipient** – address the `notify.reticulum_send_message` entity + sends to (see Entities below). +- **Greeting** – optional message sent automatically the first time each sender + contacts Home Assistant (once per sender, reset on restart). Blank = disabled. - **Default delivery method** – `direct`, `opportunistic`, or `propagated`. - **Announce interval** – periodic re-announce (seconds; `0` disables). - **Propagation node** + **sync interval** – optional store-and-forward node so @@ -127,7 +130,7 @@ will answer. | `binary_sensor.reticulum_connected` | Whether the TCP interface is online | | `button.reticulum_announce` | Announce now | | `button.reticulum_sync_propagation_node` | Pull queued messages now | -| `notify.reticulum_message` | Send a message to the default recipient | +| `notify.reticulum_send_message` | `notify` entity that sends an LXMF message to the **default recipient** set in options — use it in automations/scripts (`action: notify.send_message`) or the Developer Tools; for arbitrary addresses use the `reticulum.send_message` service | The telemetry sensors read from the outbound interface. RX/TX bytes and bitrate work for the TCP link. **RSSI, SNR and link quality only carry data when the diff --git a/custom_components/reticulum/__init__.py b/custom_components/reticulum/__init__.py index decb0fb..8d2f606 100644 --- a/custom_components/reticulum/__init__.py +++ b/custom_components/reticulum/__init__.py @@ -80,6 +80,8 @@ class RuntimeData: # Maps a peer address -> the HA conversation_id we opened for it, so # each remote user keeps its own Assist conversation context. self.conversation_ids: dict[str, str] = {} + # Senders we've already greeted this session (first-contact greeting). + self.greeted: set[str] = set() async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -236,6 +238,12 @@ def _make_incoming_handler( _LOGGER.debug("Ignoring message from non-allowed sender %s", source) return + # First-contact greeting: send the configured greeting once per sender. + greeting = (options.get(CONF_GREETING) or "").strip() + if greeting and source not in runtime.greeted: + runtime.greeted.add(source) + await _reply(runtime.manager, source, greeting, "Home Assistant") + if not options.get(CONF_ENABLE_ASSIST, True): return diff --git a/custom_components/reticulum/manifest.json b/custom_components/reticulum/manifest.json index f9f408f..315f6c7 100644 --- a/custom_components/reticulum/manifest.json +++ b/custom_components/reticulum/manifest.json @@ -12,5 +12,5 @@ "loggers": ["RNS", "LXMF"], "requirements": ["rns>=0.9.0", "lxmf>=0.6.0"], "single_config_entry": true, - "version": "1.0.5" + "version": "1.0.6" } diff --git a/custom_components/reticulum/reticulum_client.py b/custom_components/reticulum/reticulum_client.py index 2e1726a..82d6304 100644 --- a/custom_components/reticulum/reticulum_client.py +++ b/custom_components/reticulum/reticulum_client.py @@ -362,9 +362,15 @@ class ReticulumManager: ) -> str: """Send an LXMF message. Returns the message hash (hex).""" dest_hash = self._normalise_hash(destination) - return await self.hass.async_add_executor_job( + message_hash = await self.hass.async_add_executor_job( self._send, dest_hash, content, title, method, fields ) + # Count at send time. The delivery callback only fires on a returned + # proof, which many peers/methods never send, so counting there would + # leave this stuck at 0 even for messages that were delivered fine. + self.state.messages_sent += 1 + self._push_state() + return message_hash def _send( self, @@ -535,10 +541,10 @@ class ReticulumManager: @callback def _on_outbound_delivered(self, message: Any) -> None: + # messages_sent is counted at send time (see async_send_message); this + # callback only confirms delivery, so it just emits the event. import RNS # noqa: PLC0415 - self.state.messages_sent += 1 - self._push_state() _fire_event( self.hass, "delivered", diff --git a/custom_components/reticulum/strings.json b/custom_components/reticulum/strings.json index fdb91fd..3a3ace4 100644 --- a/custom_components/reticulum/strings.json +++ b/custom_components/reticulum/strings.json @@ -37,7 +37,7 @@ "allow_all_senders": "Reply to any sender", "allowed_identities": "Allowed sender addresses", "default_recipient": "Default recipient address (for the notify entity)", - "greeting": "Greeting message", + "greeting": "First-contact greeting message", "delivery_method": "Default delivery method", "announce_interval": "Announce interval", "propagation_node": "Propagation node address", @@ -48,6 +48,7 @@ "enable_assist": "When enabled, incoming LXMF messages are answered by the Assist conversation agent and the reply is sent back over Reticulum.", "allowed_identities": "One address per line (or comma separated). Only used when 'Reply to any sender' is off.", "default_recipient": "16-byte hex LXMF address that the notify.reticulum entity sends to.", + "greeting": "Sent automatically the first time each sender messages Home Assistant (once per sender per restart). Leave blank to disable.", "announce_interval": "How often (seconds) to announce our address. 0 disables periodic announces.", "propagation_node": "Optional LXMF propagation node for store-and-forward delivery.", "sync_interval": "How often (seconds) to pull queued messages from the propagation node. 0 disables." @@ -79,7 +80,7 @@ "sync": { "name": "Sync propagation node" } }, "notify": { - "message": { "name": "Message" } + "message": { "name": "Send message" } } }, "selector": { diff --git a/custom_components/reticulum/translations/en.json b/custom_components/reticulum/translations/en.json index fdb91fd..3a3ace4 100644 --- a/custom_components/reticulum/translations/en.json +++ b/custom_components/reticulum/translations/en.json @@ -37,7 +37,7 @@ "allow_all_senders": "Reply to any sender", "allowed_identities": "Allowed sender addresses", "default_recipient": "Default recipient address (for the notify entity)", - "greeting": "Greeting message", + "greeting": "First-contact greeting message", "delivery_method": "Default delivery method", "announce_interval": "Announce interval", "propagation_node": "Propagation node address", @@ -48,6 +48,7 @@ "enable_assist": "When enabled, incoming LXMF messages are answered by the Assist conversation agent and the reply is sent back over Reticulum.", "allowed_identities": "One address per line (or comma separated). Only used when 'Reply to any sender' is off.", "default_recipient": "16-byte hex LXMF address that the notify.reticulum entity sends to.", + "greeting": "Sent automatically the first time each sender messages Home Assistant (once per sender per restart). Leave blank to disable.", "announce_interval": "How often (seconds) to announce our address. 0 disables periodic announces.", "propagation_node": "Optional LXMF propagation node for store-and-forward delivery.", "sync_interval": "How often (seconds) to pull queued messages from the propagation node. 0 disables." @@ -79,7 +80,7 @@ "sync": { "name": "Sync propagation node" } }, "notify": { - "message": { "name": "Message" } + "message": { "name": "Send message" } } }, "selector": { diff --git a/custom_components/reticulum/translations/ru.json b/custom_components/reticulum/translations/ru.json index 826fadf..ae6b3e0 100644 --- a/custom_components/reticulum/translations/ru.json +++ b/custom_components/reticulum/translations/ru.json @@ -37,7 +37,7 @@ "allow_all_senders": "Отвечать любому отправителю", "allowed_identities": "Разрешённые адреса отправителей", "default_recipient": "Адрес получателя по умолчанию (для сущности notify)", - "greeting": "Приветственное сообщение", + "greeting": "Приветствие при первом обращении", "delivery_method": "Способ доставки по умолчанию", "announce_interval": "Интервал анонсов", "propagation_node": "Адрес узла ретрансляции (propagation node)", @@ -48,6 +48,7 @@ "enable_assist": "Если включено, входящие сообщения LXMF обрабатываются агентом Assist, а ответ отправляется обратно через Reticulum.", "allowed_identities": "По одному адресу в строке (или через запятую). Используется только если «Отвечать любому отправителю» выключено.", "default_recipient": "16-байтовый hex-адрес LXMF, на который отправляет сущность notify.reticulum.", + "greeting": "Отправляется автоматически при первом сообщении от каждого отправителя (один раз на отправителя до перезапуска). Оставьте пустым, чтобы отключить.", "announce_interval": "Как часто (в секундах) анонсировать наш адрес. 0 отключает периодические анонсы.", "propagation_node": "Необязательный узел ретрансляции LXMF для доставки по принципу store-and-forward.", "sync_interval": "Как часто (в секундах) забирать сообщения из очереди узла ретрансляции. 0 отключает." @@ -79,7 +80,7 @@ "sync": { "name": "Синхронизировать узел ретрансляции" } }, "notify": { - "message": { "name": "Сообщение" } + "message": { "name": "Отправить сообщение" } } }, "selector": {