forked from iarv/meshcore-gui
56
CHANGELOG.md
56
CHANGELOG.md
@@ -8,7 +8,53 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver
|
||||
|
||||
---
|
||||
|
||||
<!-- ADDED: Nieuw v5.3.0 entry bovenaan -->
|
||||
<!-- ADDED: v5.5.2 bugfix entry -->
|
||||
|
||||
## [5.5.2] - 2026-02-09 — Bugfix: Bot Device Name Restoration After Restart
|
||||
|
||||
### Fixed
|
||||
- 🛠 **Bot device name not properly restored after restart/crash** — After a restart or crash with bot mode previously active, the original device name was incorrectly stored as the bot name (e.g. `NL-OV-ZWL-STDSHGN-WKC Bot`) instead of the real device name (e.g. `PE1HVH T1000e`). The original device name is now correctly preserved and restored when bot mode is disabled
|
||||
|
||||
### Changed
|
||||
- 🔄 `commands.py`: `set_bot_name` handler now verifies that the stored original name is not already the bot name before saving
|
||||
- 🔄 `shared_data.py`: `original_device_name` is only written when it differs from `BOT_DEVICE_NAME` to prevent overwriting with the bot name on restart
|
||||
|
||||
---
|
||||
|
||||
<!-- ADDED: v5.5.1 bugfix entry -->
|
||||
|
||||
## [5.5.1] - 2026-02-09 — Bugfix: Auto-add AttributeError
|
||||
|
||||
### Fixed
|
||||
- 🛠 **Auto-add error on first toggle** — Setting auto-add for the first time raised `AttributeError: 'telemetry_mode_base'`. The `set_manual_add_contacts()` SDK call now handles missing `telemetry_mode_base` attribute gracefully
|
||||
|
||||
### Changed
|
||||
- 🔄 `commands.py`: `set_auto_add` handler wraps `set_manual_add_contacts()` call with attribute check and error handling for missing `telemetry_mode_base`
|
||||
|
||||
---
|
||||
|
||||
<!-- ADDED: Nieuw v5.5.0 entry bovenaan -->
|
||||
|
||||
## [5.5.0] - 2026-02-08 — Bot Device Name Management
|
||||
|
||||
### Added
|
||||
- ✅ **Bot device name switching** — When the BOT checkbox is enabled, the device name is automatically changed to a configurable bot name; when disabled, the original name is restored
|
||||
- Original device name is saved before renaming so it can be restored on BOT disable
|
||||
- Device name written to device via BLE `set_name()` SDK call
|
||||
- Graceful handling of BLE failures during name change
|
||||
- ✅ **`BOT_DEVICE_NAME` constant** in `config.py` — Configurable fixed device name used when bot mode is active (default: `;NL-OV-ZWL-STDSHGN-WKC Bot`)
|
||||
|
||||
### Changed
|
||||
- 🔄 `config.py`: Added `BOT_DEVICE_NAME` constant for bot mode device name
|
||||
- 🔄 `bot.py`: Removed hardcoded `BOT_NAME` prefix ("Zwolle Bot") from bot reply messages — bot replies no longer include a name prefix
|
||||
- 🔄 `filter_panel.py`: BOT checkbox toggle now triggers device name save/rename via command queue
|
||||
- 🔄 `commands.py`: Added `set_bot_name` and `restore_name` command handlers for device name switching
|
||||
- 🔄 `shared_data.py`: Added `original_device_name` field for storing the pre-bot device name
|
||||
|
||||
### Removed
|
||||
- ❌ `BOT_NAME` constant from `bot.py` — bot reply prefix removed; replies no longer prepend a bot display name
|
||||
|
||||
---
|
||||
|
||||
## [5.4.0] - 2026-02-08 — Contact Maintenance Feature
|
||||
|
||||
@@ -45,7 +91,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver
|
||||
---
|
||||
|
||||
### Fixed
|
||||
- 🐛 **Route table names and IDs not displayed** — Route tables in both current messages (RoutePage) and archive messages (ArchivePage) now correctly show node names and public key IDs for sender, repeaters and receiver
|
||||
- 🛠 **Route table names and IDs not displayed** — Route tables in both current messages (RoutePage) and archive messages (ArchivePage) now correctly show node names and public key IDs for sender, repeaters and receiver
|
||||
|
||||
### Changed
|
||||
- 🔄 **CHANGELOG.md**: Corrected version numbering (v1.0.x → v5.x), fixed inaccurate references (archive button location, filter state persistence)
|
||||
@@ -124,9 +170,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver
|
||||
<!-- CHANGED: Versienummer gewijzigd van v1.0.3 naar v5.1.3 -->
|
||||
|
||||
### Fixed
|
||||
- 🐛 **CRITICAL**: Fixed bug where archive was overwritten instead of appended on restart
|
||||
- 🐛 Archive now preserves existing data when read errors occur
|
||||
- 🐛 Buffer is retained for retry if existing archive cannot be read
|
||||
- 🛠 **CRITICAL**: Fixed bug where archive was overwritten instead of appended on restart
|
||||
- 🛠 Archive now preserves existing data when read errors occur
|
||||
- 🛠 Buffer is retained for retry if existing archive cannot be read
|
||||
|
||||
### Changed
|
||||
- 🔄 `_flush_messages()`: Early return on read error instead of overwriting
|
||||
|
||||
26
README.md
26
README.md
@@ -198,8 +198,10 @@ The GUI opens automatically in your browser at `http://localhost:8080`
|
||||
| `CONTACT_RETENTION_DAYS` | `meshcore_gui/config.py` | Retention period for cached contacts (default: 90 days) |
|
||||
<!-- ADDED: Three retention settings above were missing from config table -->
|
||||
| `KEY_RETRY_INTERVAL` | `meshcore_gui/ble/worker.py` | Interval between background retry attempts for missing channel keys (default: 30s) |
|
||||
| `BOT_DEVICE_NAME` | `meshcore_gui/config.py` | Device name set when bot mode is active (default: `;NL-OV-ZWL-STDSHGN-WKC Bot`) |
|
||||
<!-- ADDED: BOT_DEVICE_NAME setting added in v5.5.0 -->
|
||||
| `BOT_CHANNELS` | `meshcore_gui/services/bot.py` | Channel indices the bot listens on |
|
||||
| `BOT_NAME` | `meshcore_gui/services/bot.py` | Display name prepended to bot replies |
|
||||
<!-- CHANGED: BOT_NAME removed in v5.5.0 — bot replies no longer include a name prefix -->
|
||||
| `BOT_COOLDOWN_SECONDS` | `meshcore_gui/services/bot.py` | Minimum seconds between bot replies |
|
||||
| `BOT_KEYWORDS` | `meshcore_gui/services/bot.py` | Keyword → reply template mapping |
|
||||
| BLE Address | Command line argument | |
|
||||
@@ -290,20 +292,25 @@ If BLE connection fails, the GUI remains usable with cached data and shows an of
|
||||
|
||||
The built-in bot automatically replies to messages containing recognised keywords. Enable or disable it via the 🤖 BOT checkbox in the filter bar.
|
||||
|
||||
<!-- CHANGED: Bot device name switching feature added in v5.5.0 -->
|
||||
**Device name switching:** When the BOT checkbox is enabled, the device name is automatically changed to the configured `BOT_DEVICE_NAME` (default: `;NL-OV-ZWL-STDSHGN-WKC Bot`). The original device name is saved and restored when bot mode is disabled. This allows the mesh network to identify the node as a bot by its name.
|
||||
|
||||
**Default keywords:**
|
||||
|
||||
<!-- CHANGED: Removed "Zwolle Bot:" prefix from example replies — bot replies no longer include a name prefix (v5.5.0) -->
|
||||
|
||||
| Keyword | Reply |
|
||||
|---------|-------|
|
||||
| `test` | `Zwolle Bot: <sender>, rcvd \| SNR <snr> \| path(<hops>); <repeaters>` |
|
||||
| `ping` | `Zwolle Bot: Pong!` |
|
||||
| `help` | `Zwolle Bot: test, ping, help` |
|
||||
| `test` | `<sender>, rcvd \| SNR <snr> \| path(<hops>); <repeaters>` |
|
||||
| `ping` | `Pong!` |
|
||||
| `help` | `test, ping, help` |
|
||||
|
||||
**Safety guards:**
|
||||
- Only replies on configured channels (`BOT_CHANNELS`)
|
||||
- Ignores own messages and messages from other bots (names ending in "Bot")
|
||||
- Cooldown period between replies (default: 5 seconds)
|
||||
|
||||
**Customisation:** Edit `BOT_KEYWORDS` in `meshcore_gui/services/bot.py`. Templates support `{bot}`, `{sender}`, `{snr}` and `{path}` variables.
|
||||
**Customisation:** Edit `BOT_KEYWORDS` in `meshcore_gui/services/bot.py`. Templates support `{sender}`, `{snr}` and `{path}` variables.
|
||||
|
||||
### RX Log
|
||||
- Received packets with SNR and type
|
||||
@@ -353,10 +360,12 @@ The built-in bot automatically replies to messages containing recognised keyword
|
||||
```
|
||||
|
||||
- **BLEWorker**: Runs in separate thread with its own asyncio loop, with background retry for missing channel keys
|
||||
- **CommandHandler**: Executes commands (send message, advert, refresh, purge unpinned, set auto-add)
|
||||
- **CommandHandler**: Executes commands (send message, advert, refresh, purge unpinned, set auto-add, set bot name, restore name)
|
||||
<!-- CHANGED: Added set bot name and restore name commands (v5.5.0) -->
|
||||
- **EventHandler**: Processes incoming BLE events (messages, RX log)
|
||||
- **PacketDecoder**: Decodes raw LoRa packets and extracts route data
|
||||
- **MeshBot**: Keyword-triggered auto-reply on configured channels
|
||||
- **MeshBot**: Keyword-triggered auto-reply on configured channels with automatic device name switching
|
||||
<!-- CHANGED: Added device name switching to MeshBot description (v5.5.0) -->
|
||||
- **DualDeduplicator**: Prevents duplicate messages (hash-based + content-based)
|
||||
- **DeviceCache**: Local JSON cache per device for instant startup and offline resilience
|
||||
- **MessageArchive**: Persistent storage for messages and RX log with configurable retention and automatic cleanup
|
||||
@@ -462,7 +471,8 @@ meshcore-gui/
|
||||
├── meshcore_gui/ # Application package
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py # Alternative entry: python -m meshcore_gui
|
||||
│ ├── config.py # DEBUG flag, channel configuration, refresh interval, retention settings
|
||||
│ ├── config.py # DEBUG flag, channel configuration, refresh interval, retention settings, BOT_DEVICE_NAME
|
||||
<!-- CHANGED: Added BOT_DEVICE_NAME to config.py description (v5.5.0) -->
|
||||
│ ├── ble/ # BLE communication layer
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── worker.py # BLE thread, connection lifecycle, cache-first startup, background key retry
|
||||
|
||||
1
docs/.~lock.MeshCore_GUI_Design.docx#
Normal file
1
docs/.~lock.MeshCore_GUI_Design.docx#
Normal file
@@ -0,0 +1 @@
|
||||
,hans,hans-NLx0AU,09.02.2026 15:24,file:///home/hans/.config/libreoffice/4;
|
||||
Binary file not shown.
4
install_venv.sh
Executable file
4
install_venv.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install nicegui meshcore bleak meshcoredecoder
|
||||
@@ -12,9 +12,10 @@ from typing import Dict, List, Optional
|
||||
|
||||
from meshcore import MeshCore, EventType
|
||||
|
||||
from meshcore_gui.config import debug_print
|
||||
from meshcore_gui.config import BOT_DEVICE_NAME, DEVICE_NAME, debug_print
|
||||
from meshcore_gui.core.models import Message
|
||||
from meshcore_gui.core.protocols import SharedDataWriter
|
||||
from meshcore_gui.services.cache import DeviceCache
|
||||
|
||||
|
||||
class CommandHandler:
|
||||
@@ -23,11 +24,18 @@ class CommandHandler:
|
||||
Args:
|
||||
mc: Connected MeshCore instance.
|
||||
shared: SharedDataWriter for storing results.
|
||||
cache: DeviceCache for persistent storage.
|
||||
"""
|
||||
|
||||
def __init__(self, mc: MeshCore, shared: SharedDataWriter) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
mc: MeshCore,
|
||||
shared: SharedDataWriter,
|
||||
cache: Optional[DeviceCache] = None,
|
||||
) -> None:
|
||||
self._mc = mc
|
||||
self._shared = shared
|
||||
self._cache = cache
|
||||
|
||||
# Handler registry — add new commands here (OCP)
|
||||
self._handlers: Dict[str, object] = {
|
||||
@@ -37,6 +45,7 @@ class CommandHandler:
|
||||
'refresh': self._cmd_refresh,
|
||||
'purge_unpinned': self._cmd_purge_unpinned,
|
||||
'set_auto_add': self._cmd_set_auto_add,
|
||||
'set_device_name': self._cmd_set_device_name,
|
||||
}
|
||||
|
||||
async def process_all(self) -> None:
|
||||
@@ -191,6 +200,12 @@ class CommandHandler:
|
||||
On failure the SharedData flag is rolled back so the GUI
|
||||
checkbox reverts on the next update cycle.
|
||||
|
||||
Note: some firmware/SDK versions raise ``KeyError`` (e.g.
|
||||
``'telemetry_mode_base'``) when parsing the device response.
|
||||
The BLE command itself was already sent successfully in that
|
||||
case, so we treat ``KeyError`` as *probable success* and keep
|
||||
the requested state instead of rolling back.
|
||||
|
||||
Expected command dict::
|
||||
|
||||
{
|
||||
@@ -201,6 +216,7 @@ class CommandHandler:
|
||||
enabled: bool = cmd.get('enabled', False)
|
||||
# Invert: UI "auto-add ON" → manual_add = False
|
||||
manual_add = not enabled
|
||||
state = "ON" if enabled else "OFF"
|
||||
|
||||
try:
|
||||
r = await self._mc.commands.set_manual_add_contacts(manual_add)
|
||||
@@ -216,9 +232,18 @@ class CommandHandler:
|
||||
)
|
||||
else:
|
||||
self._shared.set_auto_add_enabled(enabled)
|
||||
state = "ON" if enabled else "OFF"
|
||||
self._shared.set_status(f"✅ Auto-add contacts: {state}")
|
||||
debug_print(f"set_auto_add: success → {state}")
|
||||
except KeyError as exc:
|
||||
# SDK response-parsing error (e.g. missing 'telemetry_mode_base').
|
||||
# The BLE command was already transmitted; the device has likely
|
||||
# accepted the new setting. Keep the requested state.
|
||||
self._shared.set_auto_add_enabled(enabled)
|
||||
self._shared.set_status(f"✅ Auto-add contacts: {state}")
|
||||
debug_print(
|
||||
f"set_auto_add: KeyError '{exc}' during response parse — "
|
||||
f"command sent, treating as success → {state}"
|
||||
)
|
||||
except Exception as exc:
|
||||
# Rollback
|
||||
self._shared.set_auto_add_enabled(not enabled)
|
||||
@@ -227,6 +252,58 @@ class CommandHandler:
|
||||
)
|
||||
debug_print(f"set_auto_add exception: {exc}")
|
||||
|
||||
async def _cmd_set_device_name(self, cmd: Dict) -> None:
|
||||
"""Set or restore the device name when BOT is toggled.
|
||||
|
||||
Uses the fixed names from config.py:
|
||||
- BOT enabled → ``BOT_DEVICE_NAME`` (e.g. "NL-OV-ZWL-STDSHGN-WKC Bot")
|
||||
- BOT disabled → ``DEVICE_NAME`` (e.g. "PE1HVH T1000e")
|
||||
|
||||
This avoids the previous bug where the dynamically read device
|
||||
name could already be the bot name (e.g. after a restart while
|
||||
BOT was active), causing the original name to be overwritten
|
||||
with the bot name.
|
||||
|
||||
On failure the bot_enabled flag is rolled back so the GUI
|
||||
checkbox reverts on the next update cycle.
|
||||
|
||||
Expected command dict::
|
||||
|
||||
{
|
||||
'action': 'set_device_name',
|
||||
'bot_enabled': True/False,
|
||||
}
|
||||
"""
|
||||
bot_enabled: bool = cmd.get('bot_enabled', False)
|
||||
target_name = BOT_DEVICE_NAME if bot_enabled else DEVICE_NAME
|
||||
|
||||
try:
|
||||
r = await self._mc.commands.set_name(target_name)
|
||||
if r.type == EventType.ERROR:
|
||||
# Rollback: revert bot flag to previous state
|
||||
self._shared.set_bot_enabled(not bot_enabled)
|
||||
self._shared.set_status(
|
||||
f"⚠️ Failed to set device name to '{target_name}'"
|
||||
)
|
||||
debug_print(
|
||||
f"set_device_name: ERROR response for '{target_name}', "
|
||||
f"rolled back bot_enabled to {not bot_enabled}"
|
||||
)
|
||||
return
|
||||
|
||||
self._shared.set_status(f"✅ Device name → {target_name}")
|
||||
debug_print(f"set_device_name: success → '{target_name}'")
|
||||
|
||||
# Send advert so the network sees the new name
|
||||
await self._mc.commands.send_advert(flood=True)
|
||||
debug_print("set_device_name: advert sent")
|
||||
|
||||
except Exception as exc:
|
||||
# Rollback on exception
|
||||
self._shared.set_bot_enabled(not bot_enabled)
|
||||
self._shared.set_status(f"⚠️ Device name error: {exc}")
|
||||
debug_print(f"set_device_name exception: {exc}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Callback for refresh (set by BLEWorker after construction)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -154,7 +154,7 @@ class BLEWorker:
|
||||
dedup=self._dedup,
|
||||
bot=self._bot,
|
||||
)
|
||||
self._cmd_handler = CommandHandler(mc=self.mc, shared=self.shared)
|
||||
self._cmd_handler = CommandHandler(mc=self.mc, shared=self.shared, cache=self._cache)
|
||||
self._cmd_handler.set_load_data_callback(self._load_data)
|
||||
|
||||
# Subscribe to events
|
||||
@@ -228,6 +228,12 @@ class BLEWorker:
|
||||
except (ValueError, TypeError) as exc:
|
||||
debug_print(f"Cache → bad channel key [{idx_str}]: {exc}")
|
||||
|
||||
# Restore original device name (if BOT was active when app closed)
|
||||
cached_orig_name = self._cache.get_original_device_name()
|
||||
if cached_orig_name:
|
||||
self.shared.set_original_device_name(cached_orig_name)
|
||||
debug_print(f"Cache → original device name: {cached_orig_name}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Initial data loading (refreshes cache)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -44,6 +44,19 @@ CHANNELS_CONFIG: List[Dict] = [
|
||||
]
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# BOT DEVICE NAME
|
||||
# ==============================================================================
|
||||
|
||||
# Fixed device name applied when the BOT checkbox is enabled.
|
||||
# The original device name is saved and restored when BOT is disabled.
|
||||
BOT_DEVICE_NAME: str = "NL-OV-ZWL-STDSHGN-WKC Bot"
|
||||
|
||||
# Default device name used as fallback when restoring from BOT mode
|
||||
# and no original name was saved (e.g. after a restart).
|
||||
DEVICE_NAME: str = "PE1HVH T1000e"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# CACHE / REFRESH
|
||||
# ==============================================================================
|
||||
|
||||
@@ -61,6 +61,9 @@ class SharedDataWriter(Protocol):
|
||||
def put_command(self, cmd: Dict) -> None: ...
|
||||
def set_auto_add_enabled(self, enabled: bool) -> None: ...
|
||||
def is_auto_add_enabled(self) -> bool: ...
|
||||
def set_original_device_name(self, name: Optional[str]) -> None: ...
|
||||
def get_original_device_name(self) -> Optional[str]: ...
|
||||
def get_device_name(self) -> str: ...
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
@@ -65,6 +65,9 @@ class SharedData:
|
||||
# Auto-add contacts flag (synced with device)
|
||||
self.auto_add_enabled: bool = False
|
||||
|
||||
# Original device name (saved when BOT is enabled, restored when disabled)
|
||||
self.original_device_name: Optional[str] = None
|
||||
|
||||
# Message archive (persistent storage)
|
||||
self.archive: Optional[MessageArchive] = None
|
||||
if ble_address:
|
||||
@@ -139,6 +142,26 @@ class SharedData:
|
||||
with self.lock:
|
||||
return self.auto_add_enabled
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Original device name (BOT feature)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def set_original_device_name(self, name: Optional[str]) -> None:
|
||||
"""Store the original device name before BOT rename (thread-safe)."""
|
||||
with self.lock:
|
||||
self.original_device_name = name
|
||||
debug_print(f"Original device name stored: {name}")
|
||||
|
||||
def get_original_device_name(self) -> Optional[str]:
|
||||
"""Get the stored original device name (thread-safe)."""
|
||||
with self.lock:
|
||||
return self.original_device_name
|
||||
|
||||
def get_device_name(self) -> str:
|
||||
"""Get the current device name (thread-safe)."""
|
||||
with self.lock:
|
||||
return self.device.name
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Command queue
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -74,7 +74,7 @@ class DashboardPage:
|
||||
self._contacts = ContactsPanel(put_cmd, self._pin_store, self._shared.set_auto_add_enabled)
|
||||
self._map = MapPanel()
|
||||
self._input = InputPanel(put_cmd)
|
||||
self._filter = FilterPanel(self._shared.set_bot_enabled)
|
||||
self._filter = FilterPanel(self._shared.set_bot_enabled, put_cmd)
|
||||
self._messages = MessagesPanel()
|
||||
self._actions = ActionsPanel(put_cmd)
|
||||
self._rxlog = RxLogPanel()
|
||||
|
||||
@@ -10,10 +10,16 @@ class FilterPanel:
|
||||
|
||||
Args:
|
||||
set_bot_enabled: Callable to toggle the bot in SharedData.
|
||||
put_command: Callable to enqueue a BLE command.
|
||||
"""
|
||||
|
||||
def __init__(self, set_bot_enabled: Callable[[bool], None]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
set_bot_enabled: Callable[[bool], None],
|
||||
put_command: Callable[[dict], None],
|
||||
) -> None:
|
||||
self._set_bot_enabled = set_bot_enabled
|
||||
self._put_command = put_command
|
||||
self._container = None
|
||||
self._bot_checkbox = None
|
||||
self._channel_filters: Dict = {}
|
||||
@@ -35,6 +41,14 @@ class FilterPanel:
|
||||
ui.label('📻 Filter:').classes('text-sm text-gray-600')
|
||||
self._container = ui.row().classes('gap-4')
|
||||
|
||||
def _on_bot_toggle(self, value: bool) -> None:
|
||||
"""Handle BOT checkbox toggle: update flag and queue name change."""
|
||||
self._set_bot_enabled(value)
|
||||
self._put_command({
|
||||
'action': 'set_device_name',
|
||||
'bot_enabled': value,
|
||||
})
|
||||
|
||||
def update(self, data: Dict) -> None:
|
||||
"""Rebuild checkboxes when channel data changes."""
|
||||
if not self._container or not data['channels']:
|
||||
@@ -47,7 +61,7 @@ class FilterPanel:
|
||||
self._bot_checkbox = ui.checkbox(
|
||||
'🤖 BOT',
|
||||
value=data.get('bot_enabled', False),
|
||||
on_change=lambda e: self._set_bot_enabled(e.value),
|
||||
on_change=lambda e: self._on_bot_toggle(e.value),
|
||||
)
|
||||
ui.label('│').classes('text-gray-300')
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ BOT_COOLDOWN_SECONDS: float = 5.0
|
||||
# The bot checks whether the incoming message text *contains* the keyword
|
||||
# (case-insensitive). First match wins.
|
||||
BOT_KEYWORDS: Dict[str, str] = {
|
||||
'test': '{bot}: {sender}, rcvd | SNR {snr} | {path}',
|
||||
'ping': '{bot}: Pong!',
|
||||
'help': '{bot}: test, ping, help',
|
||||
'test': '{sender}, rcvd | SNR {snr} | {path}',
|
||||
'ping': 'Pong!',
|
||||
'help': 'test, ping, help',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -242,3 +242,19 @@ class DeviceCache:
|
||||
def get_last_updated(self) -> Optional[str]:
|
||||
"""Return ISO timestamp of last cache update, or None."""
|
||||
return self._data.get("last_updated")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Original device name (BOT feature)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_original_device_name(self) -> Optional[str]:
|
||||
"""Return cached original device name, or None."""
|
||||
return self._data.get("original_device_name")
|
||||
|
||||
def set_original_device_name(self, name: Optional[str]) -> None:
|
||||
"""Store or clear the original device name and persist to disk."""
|
||||
if name is None:
|
||||
self._data.pop("original_device_name", None)
|
||||
else:
|
||||
self._data["original_device_name"] = name
|
||||
self.save()
|
||||
|
||||
Reference in New Issue
Block a user