mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-05-24 08:05:16 +00:00
6744eb136d
- Fix use-after-free crash on hangup: set _call_state=IDLE before deleting _lxst_audio, preventing pump_call_tx() (runs without LVGL lock) from accessing freed memory - Replace single-slot _call_signal_pending with 8-element ring buffer queue to prevent signal loss when CONNECTING+ESTABLISHED arrive in rapid succession - Extract TX pump into pump_call_tx() called right after reticulum->loop() for low-latency audio TX without LVGL lock dependency (was buried at step 10) - Tune ES7210 mic gain to 21dB (was 15dB) to improve Codec2 input level without ADC clipping that occurred at 24dB - I2S capture: use APLL for accurate 8kHz clock, direct 8kHz sampling (no more 16→8kHz decimation), DMA 16x64 for encode burst headroom - Reduce Reticulum log verbosity to LOG_INFO (was LOG_TRACE) - BLE: add ble_hs_sched_reset() tiered recovery before reboot on desync, widen supervision timeout to 4.0s for WiFi coexistence - Add UDP multicast log broadcasting and OTA flash support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
336 lines
10 KiB
C++
336 lines
10 KiB
C++
// Copyright (c) 2024 microReticulum contributors
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#ifndef UI_LXMF_UIMANAGER_H
|
|
#define UI_LXMF_UIMANAGER_H
|
|
|
|
#ifdef ARDUINO
|
|
#include <Arduino.h>
|
|
#include <lvgl.h>
|
|
#include <functional>
|
|
#include "ConversationListScreen.h"
|
|
#include "ChatScreen.h"
|
|
#include "ComposeScreen.h"
|
|
#include "AnnounceListScreen.h"
|
|
#include "StatusScreen.h"
|
|
#include "QRScreen.h"
|
|
#include "SettingsScreen.h"
|
|
#include "PropagationNodesScreen.h"
|
|
#include "CallScreen.h"
|
|
#include "LXMF/LXMRouter.h"
|
|
#include "LXMF/PropagationNodeManager.h"
|
|
#include "LXMF/MessageStore.h"
|
|
#include "Reticulum.h"
|
|
#include "Link.h"
|
|
|
|
class LXSTAudio;
|
|
|
|
namespace UI {
|
|
namespace LXMF {
|
|
|
|
/**
|
|
* UI Manager
|
|
*
|
|
* Manages all LXMF UI screens and coordinates between:
|
|
* - UI screens (ConversationList, Chat, Compose, Call)
|
|
* - LXMF router (message sending/receiving)
|
|
* - Message store (persistence)
|
|
* - Reticulum (network layer)
|
|
* - LXST voice calls (audio pipeline + Reticulum Links)
|
|
*
|
|
* Responsibilities:
|
|
* - Screen navigation
|
|
* - Message delivery callbacks
|
|
* - UI updates on message events
|
|
* - Integration with LXMF router
|
|
* - Voice call state machine
|
|
*/
|
|
class UIManager {
|
|
public:
|
|
/**
|
|
* Create UI manager
|
|
* @param reticulum Reticulum instance
|
|
* @param router LXMF router instance
|
|
* @param store Message store instance
|
|
*/
|
|
UIManager(RNS::Reticulum& reticulum, ::LXMF::LXMRouter& router, ::LXMF::MessageStore& store);
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
~UIManager();
|
|
|
|
/**
|
|
* Initialize UI and show conversation list
|
|
* @return true if initialization successful
|
|
*/
|
|
bool init();
|
|
|
|
/**
|
|
* Update UI (call periodically from main loop)
|
|
* Processes pending LXMF messages, updates UI, pumps voice call
|
|
*/
|
|
void update();
|
|
|
|
/**
|
|
* Pump TX audio without LVGL lock — call from main loop for low-latency TX.
|
|
* Safe to call on every loop iteration; no-ops when not in a call.
|
|
*/
|
|
void pump_call_tx();
|
|
|
|
/**
|
|
* Show conversation list screen
|
|
*/
|
|
void show_conversation_list();
|
|
|
|
/**
|
|
* Show chat screen for a specific peer
|
|
* @param peer_hash Peer destination hash
|
|
*/
|
|
void show_chat(const RNS::Bytes& peer_hash);
|
|
|
|
/**
|
|
* Show compose new message screen
|
|
*/
|
|
void show_compose();
|
|
|
|
/**
|
|
* Show announce list screen
|
|
*/
|
|
void show_announces();
|
|
|
|
/**
|
|
* Show status screen
|
|
*/
|
|
void show_status();
|
|
|
|
/**
|
|
* Show settings screen
|
|
*/
|
|
void show_settings();
|
|
|
|
/**
|
|
* Show propagation nodes screen
|
|
*/
|
|
void show_propagation_nodes();
|
|
|
|
/**
|
|
* Set propagation node manager
|
|
* @param manager Propagation node manager instance
|
|
*/
|
|
void set_propagation_node_manager(::LXMF::PropagationNodeManager* manager);
|
|
|
|
/**
|
|
* Set LoRa interface for RSSI display
|
|
* @param iface LoRa interface
|
|
*/
|
|
void set_lora_interface(RNS::Interface* iface);
|
|
|
|
/**
|
|
* Set BLE interface for connection count display
|
|
* @param iface BLE interface
|
|
*/
|
|
void set_ble_interface(RNS::Interface* iface);
|
|
|
|
/**
|
|
* Set GPS for satellite count display
|
|
* @param gps TinyGPSPlus instance
|
|
*/
|
|
void set_gps(TinyGPSPlus* gps);
|
|
|
|
/**
|
|
* Get settings screen for external configuration
|
|
*/
|
|
SettingsScreen* get_settings_screen() { return _settings_screen; }
|
|
|
|
/**
|
|
* Get status screen for external updates (e.g., BLE peer info)
|
|
*/
|
|
StatusScreen* get_status_screen() { return _status_screen; }
|
|
|
|
/**
|
|
* Update RNS connection status displayed on status screen
|
|
* @param connected Whether connected to RNS server
|
|
* @param server_name Server hostname (optional)
|
|
*/
|
|
void set_rns_status(bool connected, const String& server_name = "");
|
|
|
|
/**
|
|
* Announce LXST voice call destination
|
|
* Called periodically from main loop
|
|
*/
|
|
void announce_lxst();
|
|
|
|
/**
|
|
* Handle incoming LXMF message
|
|
* Called by LXMF router delivery callback
|
|
* @param message Received message
|
|
*/
|
|
void on_message_received(::LXMF::LXMessage& message);
|
|
|
|
/**
|
|
* Handle message delivery confirmation
|
|
* @param message Message that was delivered
|
|
*/
|
|
void on_message_delivered(::LXMF::LXMessage& message);
|
|
|
|
/**
|
|
* Handle message delivery failure
|
|
* @param message Message that failed to deliver
|
|
*/
|
|
void on_message_failed(::LXMF::LXMessage& message);
|
|
|
|
private:
|
|
enum Screen {
|
|
SCREEN_CONVERSATION_LIST,
|
|
SCREEN_CHAT,
|
|
SCREEN_COMPOSE,
|
|
SCREEN_ANNOUNCES,
|
|
SCREEN_STATUS,
|
|
SCREEN_QR,
|
|
SCREEN_SETTINGS,
|
|
SCREEN_PROPAGATION_NODES,
|
|
SCREEN_CALL
|
|
};
|
|
|
|
RNS::Reticulum& _reticulum;
|
|
::LXMF::LXMRouter& _router;
|
|
::LXMF::MessageStore& _store;
|
|
RNS::Destination _lxst_destination;
|
|
|
|
Screen _current_screen;
|
|
RNS::Bytes _current_peer_hash;
|
|
|
|
ConversationListScreen* _conversation_list_screen;
|
|
ChatScreen* _chat_screen;
|
|
ComposeScreen* _compose_screen;
|
|
AnnounceListScreen* _announce_list_screen;
|
|
StatusScreen* _status_screen;
|
|
QRScreen* _qr_screen;
|
|
SettingsScreen* _settings_screen;
|
|
PropagationNodesScreen* _propagation_nodes_screen;
|
|
CallScreen* _call_screen;
|
|
|
|
::LXMF::PropagationNodeManager* _propagation_manager;
|
|
RNS::Interface* _ble_interface;
|
|
|
|
bool _initialized;
|
|
|
|
// Screen navigation handlers
|
|
void on_conversation_selected(const RNS::Bytes& peer_hash);
|
|
void on_new_message();
|
|
void on_back_to_conversation_list();
|
|
void on_send_message_from_chat(const String& content);
|
|
void on_call_from_chat();
|
|
void on_send_message_from_compose(const RNS::Bytes& dest_hash, const String& message);
|
|
void on_cancel_compose();
|
|
void on_announce_selected(const RNS::Bytes& dest_hash);
|
|
void on_back_from_announces();
|
|
void on_back_from_status();
|
|
void on_share_from_status();
|
|
void on_back_from_qr();
|
|
void on_back_from_settings();
|
|
void on_back_from_propagation_nodes();
|
|
void on_propagation_node_selected(const RNS::Bytes& node_hash);
|
|
void on_propagation_auto_select_changed(bool enabled);
|
|
void on_propagation_sync();
|
|
|
|
// LXMF message handling
|
|
void send_message(const RNS::Bytes& dest_hash, const String& content);
|
|
|
|
// UI updates
|
|
void refresh_current_screen();
|
|
|
|
// ── LXST Voice Call ──
|
|
|
|
// LXST signalling byte constants (matches Python LXST / LXST-kt)
|
|
static constexpr uint8_t LXST_STATUS_BUSY = 0x00;
|
|
static constexpr uint8_t LXST_STATUS_REJECTED = 0x01;
|
|
static constexpr uint8_t LXST_STATUS_CALLING = 0x02;
|
|
static constexpr uint8_t LXST_STATUS_AVAILABLE = 0x03;
|
|
static constexpr uint8_t LXST_STATUS_RINGING = 0x04;
|
|
static constexpr uint8_t LXST_STATUS_CONNECTING = 0x05;
|
|
static constexpr uint8_t LXST_STATUS_ESTABLISHED = 0x06;
|
|
|
|
// LXST codec type bytes (match LXST Codecs/__init__.py)
|
|
static constexpr uint8_t LXST_CODEC_CODEC2 = 0x02;
|
|
|
|
// LXST profile negotiation
|
|
static constexpr int LXST_PREFERRED_PROFILE = 0xFF;
|
|
static constexpr int LXST_PROFILE_VLBW = 0x20; // Codec2 1600bps
|
|
static constexpr int LXST_PROFILE_LBW = 0x30; // Codec2 3200bps
|
|
|
|
enum class CallState {
|
|
IDLE,
|
|
PATH_REQUESTING, // Outgoing: waiting for path to resolve
|
|
LINK_ESTABLISHING, // Outgoing: waiting for Link to come up
|
|
WAIT_AVAILABLE, // Outgoing: link up, waiting for STATUS_AVAILABLE
|
|
WAIT_RINGING, // Outgoing: sent identify, waiting for STATUS_RINGING
|
|
RINGING, // Outgoing: remote is ringing
|
|
INCOMING_RINGING, // Incoming: waiting for user to answer/reject
|
|
CONNECTING, // Both: opening audio pipelines
|
|
ACTIVE, // Both: voice flowing
|
|
};
|
|
|
|
CallState _call_state;
|
|
RNS::Bytes _call_peer_hash;
|
|
RNS::Bytes _call_dest_hash; // LXST destination hash (for deferred link creation)
|
|
RNS::Link _call_link;
|
|
LXSTAudio* _lxst_audio;
|
|
uint32_t _call_start_ms; // millis() when call became ACTIVE
|
|
uint32_t _call_timeout_ms; // millis() deadline for current wait state
|
|
bool _call_muted;
|
|
volatile bool _call_answer_pending; // Set by LVGL task, consumed by main loop
|
|
volatile bool _call_link_closed_pending; // Set by link callback, consumed by call_update
|
|
// Signal queue: written by Reticulum thread, consumed by call_update under LVGL lock
|
|
static constexpr int SIGNAL_QUEUE_SIZE = 8;
|
|
volatile uint8_t _call_signal_queue[SIGNAL_QUEUE_SIZE];
|
|
volatile uint8_t _call_signal_write; // Next write index (Reticulum thread)
|
|
volatile uint8_t _call_signal_read; // Next read index (main thread)
|
|
uint32_t _call_audio_rx_count; // Count of received audio frames (for diagnostics)
|
|
uint32_t _call_audio_tx_count; // Count of sent audio frames (for diagnostics)
|
|
|
|
// Singleton instance pointer for static Link callbacks
|
|
static UIManager* s_call_instance;
|
|
|
|
// Voice call methods
|
|
void call_initiate(const RNS::Bytes& peer_hash);
|
|
void call_hangup();
|
|
void call_set_mute(bool muted);
|
|
void call_update(); // Called from update() — pumps audio packets + state machine
|
|
|
|
// Process a received signalling byte (runs under LVGL lock in call_update)
|
|
void call_process_signal(uint8_t signal);
|
|
|
|
// Send a signalling byte over the call link
|
|
void call_send_signal(int signal);
|
|
|
|
// Send batched audio frames over the call link (10 sub-frames per batch)
|
|
void call_send_audio_batch(const uint8_t* batch_data, int batch_len, int batch_count, int total_frames);
|
|
|
|
// Process a single received audio frame (codec_header + data)
|
|
void call_rx_audio_frame(const uint8_t* frame, size_t frame_len);
|
|
|
|
// Handle received packet on call link (queues signals for call_update)
|
|
void call_on_packet(const RNS::Bytes& data);
|
|
|
|
// Transition to call ended and schedule return to chat
|
|
void call_ended();
|
|
|
|
// Incoming call callbacks (LXST IN destination)
|
|
static void on_lxst_link_established(RNS::Link& link);
|
|
static void on_lxst_caller_identified(const RNS::Link& link, const RNS::Identity& identity);
|
|
void call_answer();
|
|
|
|
// Static Link callbacks (delegate to s_call_instance)
|
|
static void on_call_link_established(RNS::Link& link);
|
|
static void on_call_link_closed(RNS::Link& link);
|
|
static void on_call_link_packet(const RNS::Bytes& plaintext, const RNS::Packet& packet);
|
|
};
|
|
|
|
} // namespace LXMF
|
|
} // namespace UI
|
|
|
|
#endif // ARDUINO
|
|
#endif // UI_LXMF_UIMANAGER_H
|