diff --git a/app/videonative/src/main/cpp/AudioDecoder.cpp b/app/videonative/src/main/cpp/AudioDecoder.cpp index 668c948..db2ec30 100644 --- a/app/videonative/src/main/cpp/AudioDecoder.cpp +++ b/app/videonative/src/main/cpp/AudioDecoder.cpp @@ -18,7 +18,7 @@ AudioDecoder::AudioDecoder() AudioDecoder::~AudioDecoder() { stopAudioProcessing(); - delete pOpusDecoder; + opus_decoder_destroy(pOpusDecoder); AAudioStream_requestStop(m_stream); AAudioStream_close(m_stream); } diff --git a/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.cpp b/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.cpp new file mode 100644 index 0000000..3478a9a --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.cpp @@ -0,0 +1,281 @@ +#include "AdaptiveLinkController.hpp" +#include "DeviceManager.hpp" +#include "WfbLogger.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#undef TAG +#define TAG "AdaptiveLinkController" + +AdaptiveLinkController::AdaptiveLinkController(const WfbConfiguration& config, + std::shared_ptr device_manager, + FecChangeController& fec_controller) + : config_(config) + , device_manager_(device_manager) + , fec_controller_(fec_controller) + , enabled_(config.adaptive_link.enabled_by_default) + , tx_power_(config.adaptive_link.default_tx_power) { + + WFB_LOGF_INFO(ADAPTIVE, "AdaptiveLinkController initialized: enabled=%d, tx_power=%d", + enabled_.load(), tx_power_.load()); +} + +AdaptiveLinkController::~AdaptiveLinkController() { + stop(); +} + +bool AdaptiveLinkController::start(int device_fd) { + WFB_LOG_CONTEXT(ADAPTIVE, "AdaptiveLinkController::start"); + + if (running_.load()) { + WFB_LOG_WARN(ADAPTIVE, "Controller already running"); + return false; + } + + if (!enabled_.load()) { + WFB_LOG_DEBUG(ADAPTIVE, "Controller disabled, not starting"); + return false; + } + + current_device_fd_ = device_fd; + should_stop_ = false; + running_ = true; + + // Set initial TX power on device + auto device = device_manager_->getDevice(device_fd); + if (device) { + device->setTxPower(tx_power_.load()); + } + + // Start monitoring thread + monitoring_thread_ = std::make_unique(&AdaptiveLinkController::linkQualityLoop, this); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Started adaptive link monitoring for fd=%d", device_fd); + return true; +} + +void AdaptiveLinkController::stop() { + if (!running_.load()) { + return; + } + + should_stop_ = true; + running_ = false; + + if (monitoring_thread_ && monitoring_thread_->joinable()) { + monitoring_thread_->join(); + monitoring_thread_.reset(); + } + + current_device_fd_ = -1; + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Stopped adaptive link monitoring"); +} + +void AdaptiveLinkController::setEnabled(bool enabled) { + bool was_enabled = enabled_.exchange(enabled); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "setEnabled(%d): was_enabled=%d, current_fd=%d", + enabled, was_enabled, current_device_fd_.load()); + + if (enabled && !was_enabled && current_device_fd_ != -1) { + // Enabling - restart if we have a device + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Enabling adaptive link, restarting..."); + stop(); + start(current_device_fd_); + } else if (!enabled && was_enabled) { + // Disabling - stop monitoring + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Disabling adaptive link, stopping..."); + stop(); + } + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Adaptive link %s", enabled ? "enabled" : "disabled"); +} + +void AdaptiveLinkController::setTxPower(int power) { + int old_power = tx_power_.exchange(power); + + if (old_power == power) { + return; // No change + } + + // Update TX power on all devices + if (device_manager_) { + device_manager_->forEachDevice([power](int device_fd, std::shared_ptr device) { + if (device && device->isValid()) { + device->setTxPower(power); + WFB_LOGF_DEBUG(ADAPTIVE, "Updated TX power to %d for device fd=%d", power, device_fd); + } + }); + } + + // If adaptive mode is enabled and running, restart to apply new power + if (enabled_.load() && running_.load()) { + int current_fd = current_device_fd_.load(); + stop(); + if (current_fd != -1) { + start(current_fd); + } + } + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "TX power updated to %d", power); +} + +void AdaptiveLinkController::linkQualityLoop() { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Link quality monitoring thread started"); + + // Wait a bit before starting monitoring + std::this_thread::sleep_for(std::chrono::seconds(1)); + + struct sockaddr_in server_addr; + int sockfd = createSocket(server_addr); + if (sockfd < 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to create UDP socket"); + running_ = false; + return; + } + + while (!should_stop_.load()) { + try { + auto quality = SignalQualityCalculator::get_instance().calculate_signal_quality(); + + #if defined(ANDROID_DEBUG_RSSI) || true + __android_log_print(ANDROID_LOG_WARN, TAG, "Signal quality: %d", quality.quality); + #endif + + // Map quality to configured range + quality.quality = static_cast(mapRange(quality.quality, + config_.adaptive_link.signal_quality_min, + config_.adaptive_link.signal_quality_max, + config_.adaptive_link.mapped_quality_min, + config_.adaptive_link.mapped_quality_max)); + + // Update FEC settings based on quality + updateFecSettings(quality); + + // Send quality update to ground station + sendQualityUpdate(quality, sockfd, server_addr); + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in link quality loop: %s", e.what()); + } + + // Sleep for configured interval + std::this_thread::sleep_for(config_.adaptive_link.update_interval); + } + + close(sockfd); + running_ = false; + should_stop_ = false; + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Link quality monitoring thread stopped"); +} + +void AdaptiveLinkController::sendQualityUpdate(const SignalQualityCalculator::SignalQuality& quality, + int sockfd, + const struct sockaddr_in& server_addr) { + uint32_t len; + char message[100]; + time_t currentEpoch = time(nullptr); + + /** + * Message format: + * :::::::::: + * + * gs_time: ground station clock + * link_score: 1000-2000 sent twice (already including any penalty) + * fec: instantaneous fec_rec (only used by old fec_rec_pntly now disabled by default) + * lost: instantaneous lost (not used) + * rssi_dB: best antenna rssi (for OSD) + * snr_dB: best antenna snr_dB (for OSD) + * num_ants: number of gs antennas (for OSD) + * noise_penalty: penalty deducted from score due to noise (for OSD) + * fec_change: int from 0 to 5 : how much to alter fec based on noise + * optional idr_request_code: 4 char unique code to request 1 keyframe + */ + + snprintf(message + sizeof(len), + sizeof(message) - sizeof(len), + "%ld:%d:%d:%d:%d:%d:%f:0:-1:%d:%s\n", + static_cast(currentEpoch), + quality.quality, + quality.quality, + quality.recovered_last_second, + quality.lost_last_second, + quality.quality, + quality.snr, + fec_controller_.value(), + quality.idr_code.c_str()); + + len = strlen(message + sizeof(len)); + len = htonl(len); + memcpy(message, &len, sizeof(len)); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Sending message: %s", message + 4); + + ssize_t sent = sendto(sockfd, + message, + strlen(message + sizeof(len)) + sizeof(len), + 0, + (struct sockaddr *)&server_addr, + sizeof(server_addr)); + + if (sent < 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to send quality message: %s", strerror(errno)); + } +} + +int AdaptiveLinkController::createSocket(struct sockaddr_in& server_addr) { + const char* ip = config_.adaptive_link.target_ip.c_str(); + int port = config_.network_ports.ADAPTIVE_LINK_PORT; + + // Create UDP socket + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Socket creation failed: %s", strerror(errno)); + return -1; + } + + // Set socket options + int opt = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // Configure server address + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Invalid IP address: %s", ip); + close(sockfd); + return -1; + } + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Created UDP socket for %s:%d", ip, port); + return sockfd; +} + +void AdaptiveLinkController::updateFecSettings(const SignalQualityCalculator::SignalQuality& quality) { + // Use the configuration FEC threshold values to adjust FEC + if (quality.lost_last_second > config_.fec.lost_to_5) { + fec_controller_.bump(5); // Bump to FEC 5 + } else if (quality.recovered_last_second > config_.fec.recovered_to_4) { + fec_controller_.bump(4); // Bump to FEC 4 + } else if (quality.recovered_last_second > config_.fec.recovered_to_3) { + fec_controller_.bump(3); // Bump to FEC 3 + } else if (quality.recovered_last_second > config_.fec.recovered_to_2) { + fec_controller_.bump(2); // Bump to FEC 2 + } else if (quality.recovered_last_second > config_.fec.recovered_to_1) { + fec_controller_.bump(1); // Bump to FEC 1 + } +} + +double AdaptiveLinkController::mapRange(double value, double inputMin, double inputMax, + double outputMin, double outputMax) { + return outputMin + ((value - inputMin) * (outputMax - outputMin) / (inputMax - inputMin)); +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.hpp b/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.hpp new file mode 100644 index 0000000..b2b4bff --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/AdaptiveLinkController.hpp @@ -0,0 +1,143 @@ +#ifndef ADAPTIVE_LINK_CONTROLLER_HPP +#define ADAPTIVE_LINK_CONTROLLER_HPP + +#include "WfbConfiguration.hpp" +#include "FecChangeController.h" +#include "SignalQualityCalculator.h" + +#include +#include +#include +#include +#include + +// Forward declarations +class DeviceManager; + +/** + * @brief Manages adaptive link quality monitoring and control + * + * This class extracts the adaptive link functionality from WfbngLink to provide + * a dedicated component for managing: + * - Signal quality monitoring + * - FEC adaptation based on link quality + * - TX power control + * - UDP communication with ground station + */ +class AdaptiveLinkController { +public: + /** + * @brief Constructs the controller with configuration + * @param config Configuration containing adaptive link settings + * @param device_manager Reference to device manager for power control + * @param fec_controller Reference to FEC controller for adaptation + */ + explicit AdaptiveLinkController(const WfbConfiguration& config, + std::shared_ptr device_manager, + FecChangeController& fec_controller); + + /** + * @brief Destructor - ensures clean shutdown + */ + ~AdaptiveLinkController(); + + /** + * @brief Starts the adaptive link quality monitoring + * @param device_fd File descriptor of the device to monitor + * @return true if started successfully, false otherwise + */ + bool start(int device_fd); + + /** + * @brief Stops the adaptive link monitoring + */ + void stop(); + + /** + * @brief Enables or disables adaptive link functionality + * @param enabled true to enable, false to disable + */ + void setEnabled(bool enabled); + + /** + * @brief Sets the transmission power + * @param power TX power value + */ + void setTxPower(int power); + + /** + * @brief Checks if adaptive link is currently enabled + * @return true if enabled, false otherwise + */ + bool isEnabled() const { return enabled_.load(); } + + /** + * @brief Checks if adaptive link is currently running + * @return true if running, false otherwise + */ + bool isRunning() const { return running_.load(); } + + /** + * @brief Gets the current TX power setting + * @return Current TX power value + */ + int getTxPower() const { return tx_power_.load(); } + +private: + /** + * @brief Main loop for link quality monitoring + * Runs in a separate thread to continuously monitor and adapt link quality + */ + void linkQualityLoop(); + + /** + * @brief Sends quality update message to ground station + * @param quality Signal quality data to send + * @param sockfd UDP socket file descriptor + * @param server_addr Server address structure + */ + void sendQualityUpdate(const SignalQualityCalculator::SignalQuality& quality, int sockfd, + const struct sockaddr_in& server_addr); + + /** + * @brief Creates and configures UDP socket for communication + * @param server_addr Output parameter for server address + * @return Socket file descriptor, or -1 on error + */ + int createSocket(struct sockaddr_in& server_addr); + + /** + * @brief Updates FEC settings based on signal quality + * @param quality Current signal quality measurements + */ + void updateFecSettings(const SignalQualityCalculator::SignalQuality& quality); + + /** + * @brief Maps signal quality to configured range + * @param value Input value to map + * @param inputMin Input range minimum + * @param inputMax Input range maximum + * @param outputMin Output range minimum + * @param outputMax Output range maximum + * @return Mapped value + */ + double mapRange(double value, double inputMin, double inputMax, + double outputMin, double outputMax); + + // Configuration + WfbConfiguration config_; + std::shared_ptr device_manager_; + FecChangeController& fec_controller_; + + // Thread control + std::atomic enabled_{true}; + std::atomic running_{false}; + std::atomic should_stop_{false}; + std::unique_ptr monitoring_thread_; + + // Settings + std::atomic tx_power_{30}; + std::atomic current_device_fd_{-1}; +}; + +#endif // ADAPTIVE_LINK_CONTROLLER_HPP \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/AggregatorManager.cpp b/app/wfbngrtl8812/src/main/cpp/AggregatorManager.cpp new file mode 100644 index 0000000..9f8879f --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/AggregatorManager.cpp @@ -0,0 +1,293 @@ +#include "AggregatorManager.hpp" +#include "wfb-ng/src/fec.h" +#include "wfb-ng/src/rx.hpp" +#include "devourer/src/FrameParser.h" +#include +#include +#include + +#define TAG "AggregatorManager" + +AggregatorManager::AggregatorManager(const WfbConfiguration& config) + : config_(config), initialized_(false), should_clear_stats_(false) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "AggregatorManager created"); +} + +AggregatorManager::~AggregatorManager() { + std::lock_guard lock(aggregator_mutex_); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "AggregatorManager destroyed"); + + // Aggregators will be automatically destroyed by smart ptr + video_aggregator_.reset(); + mavlink_aggregator_.reset(); + udp_aggregator_.reset(); +} + +void AggregatorManager::initializeAggregators() { + std::lock_guard lock(aggregator_mutex_); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Initializing aggregators"); + + const std::string& client_addr = config_.adaptive_link.client_ip; + uint64_t epoch = config_.device.epoch; + const char* key_path = config_.device.key_path.c_str(); + + try { + // Initialize video aggregator + uint8_t video_radio_port = 0; + uint32_t video_channel_id_f = (config_.device.link_id << 8) + video_radio_port; + video_channel_id_be_ = htobe32(video_channel_id_f); + + video_aggregator_ = std::make_unique( + client_addr, + config_.network_ports.VIDEO_CLIENT_PORT, + key_path, + epoch, + video_channel_id_f, + 0 + ); + + // Initialize mavlink aggregator + uint8_t mavlink_radio_port = config_.network_ports.MAVLINK_RADIO_PORT; + uint32_t mavlink_channel_id_f = (config_.device.link_id << 8) + mavlink_radio_port; + mavlink_channel_id_be_ = htobe32(mavlink_channel_id_f); + + mavlink_aggregator_ = std::make_unique( + client_addr, + config_.network_ports.MAVLINK_CLIENT_PORT, + key_path, + epoch, + mavlink_channel_id_f, + 0 + ); + + // Initialize UDP aggregator + uint8_t udp_radio_port = config_.network_ports.WFB_RX_PORT; + uint32_t udp_channel_id_f = (config_.device.link_id << 8) + udp_radio_port; + udp_channel_id_be_ = htobe32(udp_channel_id_f); + + __android_log_print(ANDROID_LOG_WARN, TAG, "UDP Channel ID: link_id=%d, radio_port=%d, channel_id_f=0x%x, channel_id_be=0x%x", + config_.device.link_id, udp_radio_port, udp_channel_id_f, udp_channel_id_be_); + + udp_aggregator_ = std::make_unique( + client_addr, + config_.network_ports.UDP_CLIENT_PORT, + key_path, + epoch, + udp_channel_id_f, + 0 + ); + + initialized_ = true; + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Aggregators initialized successfully"); + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to initialize aggregators: %s", e.what()); + + // Clean up partially initialized aggregators + video_aggregator_.reset(); + mavlink_aggregator_.reset(); + udp_aggregator_.reset(); + initialized_ = false; + } +} + +void AggregatorManager::processPacket(const Packet& packet) { + std::lock_guard lock(aggregator_mutex_); + + if (!initialized_) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Cannot process packet - aggregators not initialized"); + return; + } + + RxFrame frame(packet.Data); + if (!frame.IsValidWfbFrame()) { + return; + } + + // Setup common packet parameters + uint8_t antenna[4]; + int8_t rssi[4]; + int8_t noise[4]; + uint32_t freq; + setupPacketParams(packet, antenna, rssi, noise, freq); + + // Route packet based on channel ID + uint8_t* video_channel_id_be8 = reinterpret_cast(&video_channel_id_be_); + uint8_t* mavlink_channel_id_be8 = reinterpret_cast(&mavlink_channel_id_be_); + uint8_t* udp_channel_id_be8 = reinterpret_cast(&udp_channel_id_be_); + + if (frame.MatchesChannelID(video_channel_id_be8)) { + processVideoPacket(packet, frame); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Processed VIDEO packet"); + } else if (frame.MatchesChannelID(mavlink_channel_id_be8)) { + processMavlinkPacket(packet, frame); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Processed MAVLINK packet"); + } else if (frame.MatchesChannelID(udp_channel_id_be8)) { + processUdpPacket(packet, frame); + __android_log_print(ANDROID_LOG_WARN, TAG, "Processed UDP packet (VPN)"); + } else { + __android_log_print(ANDROID_LOG_WARN, TAG, "Unknown packet type - not matching any channel ID"); + } + + // Handle stats clearing if requested + if (should_clear_stats_) { + clearStats(); + should_clear_stats_ = false; + } +} + +void AggregatorManager::processVideoPacket(const Packet& packet, const RxFrame& frame) { + if (!video_aggregator_) return; + + // Update signal quality metrics for video packets + SignalQualityCalculator::get_instance().add_rssi(packet.RxAtrib.rssi[0], packet.RxAtrib.rssi[1]); + SignalQualityCalculator::get_instance().add_snr(packet.RxAtrib.snr[0], packet.RxAtrib.snr[1]); + + // Setup packet parameters + uint8_t antenna[4] = {1, 1, 1, 1}; + int8_t rssi[4] = {(int8_t)packet.RxAtrib.rssi[0], (int8_t)packet.RxAtrib.rssi[1], 1, 1}; + int8_t noise[4] = {1, 1, 1, 1}; + uint32_t freq = 0; + + // Process packet through video aggregator + video_aggregator_->process_packet( + packet.Data.data() + sizeof(ieee80211_header), + packet.Data.size() - sizeof(ieee80211_header) - 4, + 0, + antenna, + rssi, + noise, + freq, + 0, + 0, + NULL + ); +} + +void AggregatorManager::processMavlinkPacket(const Packet& packet, const RxFrame& frame) { + if (!mavlink_aggregator_) return; + + // Setup packet parameters + uint8_t antenna[4] = {1, 1, 1, 1}; + int8_t rssi[4] = {(int8_t)packet.RxAtrib.rssi[0], (int8_t)packet.RxAtrib.rssi[1], 1, 1}; + int8_t noise[4] = {1, 1, 1, 1}; + uint32_t freq = 0; + + // Process packet through mavlink aggregator + mavlink_aggregator_->process_packet( + packet.Data.data() + sizeof(ieee80211_header), + packet.Data.size() - sizeof(ieee80211_header) - 4, + 0, + antenna, + rssi, + noise, + freq, + 0, + 0, + NULL + ); +} + +void AggregatorManager::processUdpPacket(const Packet& packet, const RxFrame& frame) { + if (!udp_aggregator_) return; + + // Setup packet parameters + uint8_t antenna[4] = {1, 1, 1, 1}; + int8_t rssi[4] = {(int8_t)packet.RxAtrib.rssi[0], (int8_t)packet.RxAtrib.rssi[1], 1, 1}; + int8_t noise[4] = {1, 1, 1, 1}; + uint32_t freq = 0; + + // Process packet through UDP aggregator + udp_aggregator_->process_packet( + packet.Data.data() + sizeof(ieee80211_header), + packet.Data.size() - sizeof(ieee80211_header) - 4, + 0, + antenna, + rssi, + noise, + freq, + 0, + 0, + NULL + ); +} + +void AggregatorManager::clearStats() { + std::lock_guard lock(aggregator_mutex_); + + if (video_aggregator_) { + video_aggregator_->clear_stats(); + } + if (mavlink_aggregator_) { + mavlink_aggregator_->clear_stats(); + } + if (udp_aggregator_) { + udp_aggregator_->clear_stats(); + } + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Aggregator stats cleared"); +} + +AggregatorUDPv4* AggregatorManager::getVideoAggregator() const { + std::lock_guard lock(aggregator_mutex_); + return video_aggregator_.get(); +} + +AggregatorUDPv4* AggregatorManager::getMavlinkAggregator() const { + std::lock_guard lock(aggregator_mutex_); + return mavlink_aggregator_.get(); +} + +AggregatorUDPv4* AggregatorManager::getUdpAggregator() const { + std::lock_guard lock(aggregator_mutex_); + return udp_aggregator_.get(); +} + +bool AggregatorManager::isInitialized() const { + std::lock_guard lock(aggregator_mutex_); + return initialized_ && video_aggregator_ && mavlink_aggregator_ && udp_aggregator_; +} + +void AggregatorManager::updateConfiguration(const WfbConfiguration& new_config) { + std::lock_guard lock(aggregator_mutex_); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Updating configuration"); + config_ = new_config; + + // Reinitialize aggregators with new configuration + initialized_ = false; + video_aggregator_.reset(); + mavlink_aggregator_.reset(); + udp_aggregator_.reset(); + + // Initialize with new config (unlock temporarily to avoid recursive lock) + lock.~lock_guard(); + initializeAggregators(); +} + +const WfbConfiguration& AggregatorManager::getConfiguration() const { + return config_; +} + +uint32_t AggregatorManager::calculateChannelId(uint8_t radio_port) const { + uint32_t channel_id_f = (config_.device.link_id << 8) + radio_port; + return htobe32(channel_id_f); +} + +void AggregatorManager::setupPacketParams(const Packet& packet, uint8_t antenna[4], + int8_t rssi[4], int8_t noise[4], uint32_t& freq) const { + // Setup antenna array + antenna[0] = antenna[1] = antenna[2] = antenna[3] = 1; + + // Setup RSSI array + rssi[0] = static_cast(packet.RxAtrib.rssi[0]); + rssi[1] = static_cast(packet.RxAtrib.rssi[1]); + rssi[2] = rssi[3] = 1; + + // Setup noise array + noise[0] = noise[1] = noise[2] = noise[3] = 1; + + // Setup frequency + freq = 0; +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/AggregatorManager.hpp b/app/wfbngrtl8812/src/main/cpp/AggregatorManager.hpp new file mode 100644 index 0000000..b6af4ff --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/AggregatorManager.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "WfbConfiguration.hpp" +#include "RxFrame.h" +#include "SignalQualityCalculator.h" +#include +#include +#include + +// Forward declarations +struct Packet; +class AggregatorUDPv4; + +/** + * @brief Manages network aggregation for video, mavlink, and UDP streams + * + * Encapsulates the aggregator initialization and packet processing logic + * with proper thread safety and configuration management. + */ +class AggregatorManager { +public: + /** + * @brief Constructor + * @param config Configuration containing network settings + */ + explicit AggregatorManager(const WfbConfiguration& config); + + /** + * @brief Destructor + */ + ~AggregatorManager(); + + /** + * @brief Initialize all aggregators + * + * Creates video, mavlink, and UDP aggregators with the configured settings. + * This should be called after construction and whenever configuration changes. + */ + void initializeAggregators(); + + /** + * @brief Process an incoming packet + * @param packet The packet to process + * + * Routes the packet to the appropriate aggregator based on channel ID. + */ + void processPacket(const Packet& packet); + + /** + * @brief Clear statistics for all aggregators + */ + void clearStats(); + + /** + * @brief Get video aggregator statistics + * @return Pointer to video aggregator (for stats access), or nullptr if not initialized + */ + AggregatorUDPv4* getVideoAggregator() const; + + /** + * @brief Get mavlink aggregator statistics + * @return Pointer to mavlink aggregator (for stats access), or nullptr if not initialized + */ + AggregatorUDPv4* getMavlinkAggregator() const; + + /** + * @brief Get UDP aggregator statistics + * @return Pointer to UDP aggregator (for stats access), or nullptr if not initialized + */ + AggregatorUDPv4* getUdpAggregator() const; + + /** + * @brief Check if aggregators are initialized + * @return true if all aggregators are created and ready + */ + bool isInitialized() const; + + /** + * @brief Update configuration and reinitialize aggregators + * @param new_config New configuration to apply + */ + void updateConfiguration(const WfbConfiguration& new_config); + + /** + * @brief Get current configuration + * @return Reference to current configuration + */ + const WfbConfiguration& getConfiguration() const; + +private: + /** + * @brief Process video packet + * @param packet The packet to process + * @param frame Parsed frame information + */ + void processVideoPacket(const Packet& packet, const RxFrame& frame); + + /** + * @brief Process mavlink packet + * @param packet The packet to process + * @param frame Parsed frame information + */ + void processMavlinkPacket(const Packet& packet, const RxFrame& frame); + + /** + * @brief Process UDP packet + * @param packet The packet to process + * @param frame Parsed frame information + */ + void processUdpPacket(const Packet& packet, const RxFrame& frame); + + /** + * @brief Calculate channel ID in big-endian format + * @param radio_port Radio port for the channel + * @return Channel ID in big-endian format + */ + uint32_t calculateChannelId(uint8_t radio_port) const; + + /** + * @brief Setup common packet processing parameters + * @param packet Source packet + * @param antenna Output antenna array + * @param rssi Output RSSI array + * @param noise Output noise array + * @param freq Output frequency + */ + void setupPacketParams(const Packet& packet, uint8_t antenna[4], int8_t rssi[4], + int8_t noise[4], uint32_t& freq) const; + + // Configuration + WfbConfiguration config_; + + // Aggregators + std::unique_ptr video_aggregator_; + std::unique_ptr mavlink_aggregator_; + std::unique_ptr udp_aggregator_; + + // Channel IDs (in big-endian format) + uint32_t video_channel_id_be_; + uint32_t mavlink_channel_id_be_; + uint32_t udp_channel_id_be_; + + // Thread safety + mutable std::mutex aggregator_mutex_; + + // State + bool initialized_; + bool should_clear_stats_; +}; \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/CMakeLists.txt b/app/wfbngrtl8812/src/main/cpp/CMakeLists.txt index 71e1975..17fa373 100644 --- a/app/wfbngrtl8812/src/main/cpp/CMakeLists.txt +++ b/app/wfbngrtl8812/src/main/cpp/CMakeLists.txt @@ -71,6 +71,21 @@ add_library(${CMAKE_PROJECT_NAME} SHARED TxFrame.cpp SignalQualityCalculator.h SignalQualityCalculator.cpp + DeviceManager.hpp + DeviceManager.cpp + AggregatorManager.hpp + AggregatorManager.cpp + ThreadManager.hpp + ThreadManager.cpp + WfbConfiguration.hpp + AdaptiveLinkController.hpp + AdaptiveLinkController.cpp + PacketProcessor.hpp + PacketProcessor.cpp + WfbngLinkJNI.hpp + WfbngLinkJNI.cpp + WfbLogger.hpp + WfbLogger.cpp ) target_link_libraries(${CMAKE_PROJECT_NAME} @@ -84,3 +99,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME} set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY CXX_STANDARD 20) target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -fno-omit-frame-pointer) +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + __WFB_RX_SHARED_LIBRARY__ + PREINCLUDE_FILE=<${CMAKE_SOURCE_DIR}/wfb_log.h> +) diff --git a/app/wfbngrtl8812/src/main/cpp/DeviceManager.cpp b/app/wfbngrtl8812/src/main/cpp/DeviceManager.cpp new file mode 100644 index 0000000..3ce289c --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/DeviceManager.cpp @@ -0,0 +1,173 @@ +#include "DeviceManager.hpp" +#include +#include + +#define TAG "DeviceManager" + +// DeviceManager::Device implementation +DeviceManager::Device::Device(std::unique_ptr rtl_device, int fd) + : rtl_device_(std::move(rtl_device)), fd_(fd), valid_(true) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Device created for fd=%d", fd); +} + +DeviceManager::Device::~Device() { + if (rtl_device_) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Device destroyed for fd=%d", fd_); + // Mark for stopping if not already done + markForStop(); + } +} + +Rtl8812aDevice* DeviceManager::Device::get() const { + return rtl_device_.get(); +} + +bool DeviceManager::Device::isValid() const { + return valid_ && rtl_device_ && !rtl_device_->should_stop; +} + +void DeviceManager::Device::markForStop() { + valid_ = false; + if (rtl_device_) { + rtl_device_->should_stop = true; + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Device marked for stop, fd=%d", fd_); + } +} + +int DeviceManager::Device::getFileDescriptor() const { + return fd_; +} + +void DeviceManager::Device::setTxPower(int power) { + if (rtl_device_ && valid_) { + rtl_device_->SetTxPower(power); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "TX power set to %d for fd=%d", power, fd_); + } +} + +// DeviceManager implementation +DeviceManager::DeviceManager(std::shared_ptr wifi_driver) + : wifi_driver_(std::move(wifi_driver)) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "DeviceManager created"); +} + +DeviceManager::~DeviceManager() { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "DeviceManager destructor - cleaning up %zu devices", devices_.size()); + destroyAllDevices(); +} + +std::shared_ptr DeviceManager::createDevice(int fd, struct libusb_device_handle* dev_handle) { + std::lock_guard lock(devices_mutex_); + + // Check if device already exists + if (devices_.find(fd) != devices_.end()) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Device with fd=%d already exists", fd); + return devices_[fd]; + } + + return createDeviceInternal(fd, dev_handle); +} + +std::shared_ptr DeviceManager::createDeviceInternal(int fd, struct libusb_device_handle* dev_handle) { + if (!wifi_driver_) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "WiFi driver not available"); + return nullptr; + } + + try { + // Create RTL device using WiFi driver + std::unique_ptr rtl_device = wifi_driver_->CreateRtlDevice(dev_handle); + if (!rtl_device) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to create RTL device for fd=%d", fd); + return nullptr; + } + + // Create our RAII wrapper + auto device = std::make_shared(std::move(rtl_device), fd); + devices_[fd] = device; + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Successfully created device for fd=%d", fd); + return device; + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Exception creating device for fd=%d: %s", fd, e.what()); + return nullptr; + } +} + +void DeviceManager::destroyDevice(int fd) { + std::lock_guard lock(devices_mutex_); + + auto it = devices_.find(fd); + if (it != devices_.end()) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Destroying device for fd=%d", fd); + + // Mark device for stop before removing + it->second->markForStop(); + devices_.erase(it); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Device destroyed for fd=%d", fd); + } else { + __android_log_print(ANDROID_LOG_WARN, TAG, "Attempted to destroy non-existent device fd=%d", fd); + } +} + +std::shared_ptr DeviceManager::getDevice(int fd) const { + std::lock_guard lock(devices_mutex_); + + auto it = devices_.find(fd); + if (it != devices_.end()) { + return it->second; + } + + return nullptr; +} + +bool DeviceManager::hasDevice(int fd) const { + std::lock_guard lock(devices_mutex_); + return devices_.find(fd) != devices_.end(); +} + +size_t DeviceManager::getDeviceCount() const { + std::lock_guard lock(devices_mutex_); + return devices_.size(); +} + +void DeviceManager::stopAllDevices() { + std::lock_guard lock(devices_mutex_); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Stopping all %zu devices", devices_.size()); + + for (auto& [fd, device] : devices_) { + if (device) { + device->markForStop(); + } + } +} + +void DeviceManager::destroyAllDevices() { + std::lock_guard lock(devices_mutex_); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Destroying all %zu devices", devices_.size()); + + // Mark all devices for stop first + for (auto& [fd, device] : devices_) { + if (device) { + device->markForStop(); + } + } + + // Clear the map (devices will be destroyed when their shared_ptr count reaches 0) + devices_.clear(); +} + +void DeviceManager::forEachDevice(const std::function)>& func) const { + std::lock_guard lock(devices_mutex_); + + for (const auto& [fd, device] : devices_) { + if (device && func) { + func(fd, device); + } + } +} + diff --git a/app/wfbngrtl8812/src/main/cpp/DeviceManager.hpp b/app/wfbngrtl8812/src/main/cpp/DeviceManager.hpp new file mode 100644 index 0000000..de0bc2e --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/DeviceManager.hpp @@ -0,0 +1,137 @@ +#pragma once + +#include "devourer/src/WiFiDriver.h" +#include "WfbConfiguration.hpp" +#include "WfbLogger.hpp" +#include +#include +#include +#include + +/** + * @brief RAII wrapper for RTL8812 device management + * + * Manages the lifecycle of RTL devices with proper cleanup and thread safety. + */ +class DeviceManager { +public: + /** + * @brief RAII wrapper for individual RTL device + */ + class Device { + public: + Device(std::unique_ptr rtl_device, int fd); + ~Device(); + + // Non-copyable, movable + Device(const Device&) = delete; + Device& operator=(const Device&) = delete; + Device(Device&&) = default; + Device& operator=(Device&&) = default; + + /** + * @brief Get the underlying RTL device + * @return Raw pointer to RTL device (managed by this wrapper) + */ + Rtl8812aDevice* get() const; + + /** + * @brief Check if device is valid + * @return true if device is valid and not marked for stopping + */ + bool isValid() const; + + /** + * @brief Mark device for stopping + */ + void markForStop(); + + /** + * @brief Get the file descriptor associated with this device + * @return File descriptor + */ + int getFileDescriptor() const; + + /** + * @brief Set TX power for this device + * @param power TX power value + */ + void setTxPower(int power); + + private: + std::unique_ptr rtl_device_; + int fd_; + bool valid_; + }; + + /** + * @brief Constructor + * @param wifi_driver Shared pointer to WiFi driver + */ + explicit DeviceManager(std::shared_ptr wifi_driver); + + /** + * @brief Destructor - ensures all devices are properly cleaned up + */ + ~DeviceManager(); + + /** + * @brief Create a new device + * @param fd File descriptor for the device + * @param dev_handle libusb device handle + * @return Shared pointer to the created device, or nullptr on failure + */ + std::shared_ptr createDevice(int fd, struct libusb_device_handle* dev_handle); + + /** + * @brief Remove and cleanup a device + * @param fd File descriptor of the device to remove + */ + void destroyDevice(int fd); + + /** + * @brief Get an existing device + * @param fd File descriptor of the device + * @return Shared pointer to the device, or nullptr if not found + */ + std::shared_ptr getDevice(int fd) const; + + /** + * @brief Check if a device exists + * @param fd File descriptor to check + * @return true if device exists + */ + bool hasDevice(int fd) const; + + /** + * @brief Get count of managed devices + * @return Number of devices currently managed + */ + size_t getDeviceCount() const; + + /** + * @brief Mark all devices for stopping + */ + void stopAllDevices(); + + /** + * @brief Remove all devices + */ + void destroyAllDevices(); + + /** + * @brief Apply function to all devices + * @param func Function to apply (receives fd and device) + */ + void forEachDevice(const std::function)>& func) const; + +private: + mutable std::mutex devices_mutex_; + std::map> devices_; + std::shared_ptr wifi_driver_; + + /** + * @brief Internal device creation without mutex (caller must hold lock) + */ + std::shared_ptr createDeviceInternal(int fd, struct libusb_device_handle* dev_handle); +}; \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/PacketProcessor.cpp b/app/wfbngrtl8812/src/main/cpp/PacketProcessor.cpp new file mode 100644 index 0000000..57cf835 --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/PacketProcessor.cpp @@ -0,0 +1,130 @@ +#include "PacketProcessor.hpp" +#include "AggregatorManager.hpp" + +#include +#include + +#undef TAG +#define TAG "PacketProcessor" + +PacketProcessor::PacketProcessor(std::shared_ptr aggregator_manager, + const WfbConfiguration& config) + : aggregator_manager_(aggregator_manager) + , config_(config) { + + // Initialize channel IDs from configuration + video_channel_id_be_ = htonl(config_.network_ports.VIDEO_CLIENT_PORT); + mavlink_channel_id_be_ = htonl(config_.network_ports.MAVLINK_CLIENT_PORT); + udp_channel_id_be_ = htonl(config_.network_ports.UDP_CLIENT_PORT); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "PacketProcessor initialized with channel IDs: video=%d, mavlink=%d, udp=%d", + config_.network_ports.VIDEO_CLIENT_PORT, + config_.network_ports.MAVLINK_CLIENT_PORT, + config_.network_ports.UDP_CLIENT_PORT); +} + +bool PacketProcessor::processPacket(const Packet& packet) { + stats_.total_packets_processed++; + + if (!aggregator_manager_) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Cannot process packet - aggregator manager not available"); + stats_.invalid_packets++; + return false; + } + + // Validate packet + if (!isValidWfbPacket(packet)) { + stats_.invalid_packets++; + return false; + } + + stats_.valid_packets++; + + // Create RxFrame for channel ID detection + RxFrame frame(packet.Data); + if (!frame.IsValidWfbFrame()) { + stats_.invalid_packets++; + return false; + } + + // Determine packet type and route appropriately + PacketType type = determinePacketType(frame); + routePacket(packet, type); + + // Update type-specific statistics + switch (type) { + case PacketType::VIDEO: + stats_.video_packets++; + break; + case PacketType::MAVLINK: + stats_.mavlink_packets++; + break; + case PacketType::UDP: + stats_.udp_packets++; + break; + case PacketType::UNKNOWN: + stats_.unknown_packets++; + break; + } + + // Handle stats clearing if requested + if (should_clear_stats_.exchange(false)) { + aggregator_manager_->clearStats(); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Cleared aggregator stats"); + } + + return true; +} + +void PacketProcessor::resetStats() { + stats_ = ProcessingStats{}; + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Reset packet processing statistics"); +} + +bool PacketProcessor::isValidWfbPacket(const Packet& packet) { + // Basic validation - check if packet has valid data span + if (packet.Data.empty()) { + return false; + } + + // Additional validation could be added here + return true; +} + +PacketProcessor::PacketType PacketProcessor::determinePacketType(const RxFrame& frame) { + uint8_t* video_channel_id_be8 = reinterpret_cast(&video_channel_id_be_); + uint8_t* mavlink_channel_id_be8 = reinterpret_cast(&mavlink_channel_id_be_); + uint8_t* udp_channel_id_be8 = reinterpret_cast(&udp_channel_id_be_); + + if (frame.MatchesChannelID(video_channel_id_be8)) { + return PacketType::VIDEO; + } else if (frame.MatchesChannelID(mavlink_channel_id_be8)) { + return PacketType::MAVLINK; + } else if (frame.MatchesChannelID(udp_channel_id_be8)) { + return PacketType::UDP; + } else { + return PacketType::UNKNOWN; + } +} + +void PacketProcessor::routePacket(const Packet& packet, PacketType type) { + try { + // Route to aggregator manager for actual processing + aggregator_manager_->processPacket(packet); + + #ifdef DEBUG_PACKET_ROUTING + const char* type_name = "unknown"; + switch (type) { + case PacketType::VIDEO: type_name = "video"; break; + case PacketType::MAVLINK: type_name = "mavlink"; break; + case PacketType::UDP: type_name = "udp"; break; + case PacketType::UNKNOWN: type_name = "unknown"; break; + } + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Routed %s packet (size=%zu)", type_name, packet.Data.size()); + #endif + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error routing packet: %s", e.what()); + stats_.invalid_packets++; + } +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/PacketProcessor.hpp b/app/wfbngrtl8812/src/main/cpp/PacketProcessor.hpp new file mode 100644 index 0000000..5778f8b --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/PacketProcessor.hpp @@ -0,0 +1,116 @@ +#ifndef PACKET_PROCESSOR_HPP +#define PACKET_PROCESSOR_HPP + +#include "WfbConfiguration.hpp" +#include "RxFrame.h" +#include "devourer/src/FrameParser.h" + +#include +#include +#include + +// Forward declarations +class AggregatorManager; + +/** + * @brief Handles packet processing and routing + * + * This class extracts the packet processing functionality from WfbngLink to provide + * a dedicated component for managing: + * - Packet validation + * - Packet routing to appropriate aggregators + * - Statistics management coordination + * - Channel ID management + */ +class PacketProcessor { +public: + /** + * @brief Constructs the packet processor + * @param aggregator_manager Reference to aggregator manager for packet routing + * @param config Configuration containing channel settings + */ + explicit PacketProcessor(std::shared_ptr aggregator_manager, + const WfbConfiguration& config); + + /** + * @brief Destructor + */ + ~PacketProcessor() = default; + + /** + * @brief Processes an incoming packet + * @param packet The packet to process + * @return true if packet was processed successfully, false otherwise + */ + bool processPacket(const Packet& packet); + + /** + * @brief Requests stats to be cleared on next packet processing + */ + void requestStatsClear() { should_clear_stats_ = true; } + + /** + * @brief Gets packet processing statistics + * @return Structure containing processing stats + */ + struct ProcessingStats { + uint64_t total_packets_processed = 0; + uint64_t valid_packets = 0; + uint64_t invalid_packets = 0; + uint64_t video_packets = 0; + uint64_t mavlink_packets = 0; + uint64_t udp_packets = 0; + uint64_t unknown_packets = 0; + }; + + ProcessingStats getStats() const { return stats_; } + + /** + * @brief Resets processing statistics + */ + void resetStats(); + +private: + /** + * @brief Validates that a packet is a valid WFB frame + * @param packet Packet to validate + * @return true if valid, false otherwise + */ + bool isValidWfbPacket(const Packet& packet); + + /** + * @brief Determines packet type based on channel ID + * @param frame Parsed frame data + * @return Packet type enum + */ + enum class PacketType { + VIDEO, + MAVLINK, + UDP, + UNKNOWN + }; + + PacketType determinePacketType(const RxFrame& frame); + + /** + * @brief Routes packet to appropriate aggregator + * @param packet Packet to route + * @param type Packet type determined from channel ID + */ + void routePacket(const Packet& packet, PacketType type); + + // Dependencies + std::shared_ptr aggregator_manager_; + WfbConfiguration config_; + + // Channel IDs for packet routing + uint32_t video_channel_id_be_; + uint32_t mavlink_channel_id_be_; + uint32_t udp_channel_id_be_; + + // Statistics and state + mutable std::atomic should_clear_stats_{false}; + mutable ProcessingStats stats_; +}; + +#endif // PACKET_PROCESSOR_HPP \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/ThreadManager.cpp b/app/wfbngrtl8812/src/main/cpp/ThreadManager.cpp new file mode 100644 index 0000000..f4a860a --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/ThreadManager.cpp @@ -0,0 +1,153 @@ +#include "ThreadManager.hpp" +#include +#include + +#define TAG "ThreadManager" + +ThreadManager::ThreadManager() { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "ThreadManager created"); +} + +ThreadManager::~ThreadManager() { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "ThreadManager destructor - stopping %zu threads", threads_.size()); + stopAllThreads(); +} + +bool ThreadManager::stopThread(const std::string& name) { + std::lock_guard lock(threads_mutex_); + return stopThreadInternal(name); +} + +bool ThreadManager::stopThreadInternal(const std::string& name) { + auto it = threads_.find(name); + if (it == threads_.end()) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Cannot stop thread '%s' - not found", name.c_str()); + return false; + } + + ThreadInfo& info = it->second; + + // Signal thread to stop + info.stop_requested.store(true); + + // Join the thread if it's joinable + if (info.thread && info.thread->joinable()) { + try { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Joining thread '%s'", name.c_str()); + info.thread->join(); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Thread '%s' joined successfully", name.c_str()); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Exception joining thread '%s': %s", name.c_str(), e.what()); + } + } + + // Remove from map + threads_.erase(it); + return true; +} + +void ThreadManager::stopAllThreads() { + std::lock_guard lock(threads_mutex_); + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Stopping all %zu threads", threads_.size()); + + // First, signal all threads to stop + for (auto& [name, info] : threads_) { + info.stop_requested.store(true); + } + + // Then join all threads + std::vector thread_names; + for (const auto& [name, info] : threads_) { + thread_names.push_back(name); + } + + for (const std::string& name : thread_names) { + stopThreadInternal(name); + } + + threads_.clear(); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "All threads stopped"); +} + +bool ThreadManager::hasThread(const std::string& name) const { + std::lock_guard lock(threads_mutex_); + + auto it = threads_.find(name); + if (it == threads_.end()) { + return false; + } + + return it->second.thread && it->second.thread->joinable(); +} + +size_t ThreadManager::getThreadCount() const { + std::lock_guard lock(threads_mutex_); + return threads_.size(); +} + +std::vector ThreadManager::getThreadNames() const { + std::lock_guard lock(threads_mutex_); + + std::vector names; + names.reserve(threads_.size()); + + for (const auto& [name, info] : threads_) { + names.push_back(name); + } + + return names; +} + +bool ThreadManager::isThreadRunning(const std::string& name) const { + std::lock_guard lock(threads_mutex_); + + auto it = threads_.find(name); + if (it == threads_.end()) { + return false; + } + + const ThreadInfo& info = it->second; + return info.thread && info.thread->joinable() && !info.stop_requested.load(); +} + +bool ThreadManager::requestThreadStop(const std::string& name) { + std::lock_guard lock(threads_mutex_); + + auto it = threads_.find(name); + if (it == threads_.end()) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Cannot request stop for thread '%s' - not found", name.c_str()); + return false; + } + + it->second.stop_requested.store(true); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Stop requested for thread '%s'", name.c_str()); + return true; +} + +bool ThreadManager::isStopRequested(const std::string& name) const { + std::lock_guard lock(threads_mutex_); + + auto it = threads_.find(name); + if (it == threads_.end()) { + return false; + } + + return it->second.stop_requested.load(); +} + +void ThreadManager::cleanupFinishedThreads() { + // This method is called with mutex already held + std::vector finished_threads; + + for (const auto& [name, info] : threads_) { + if (!info.thread || !info.thread->joinable()) { + finished_threads.push_back(name); + } + } + + for (const std::string& name : finished_threads) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Cleaning up finished thread '%s'", name.c_str()); + threads_.erase(name); + } +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/ThreadManager.hpp b/app/wfbngrtl8812/src/main/cpp/ThreadManager.hpp new file mode 100644 index 0000000..82c32e6 --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/ThreadManager.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Manages thread lifecycle with named thread tracking + * + * Provides RAII-based thread management with proper cleanup and + * thread safety for concurrent access. + */ +class ThreadManager { +public: + /** + * @brief Constructor + */ + ThreadManager(); + + /** + * @brief Destructor - ensures all threads are properly joined + */ + ~ThreadManager(); + + /** + * @brief Start a new named thread + * @tparam Func Function type to execute in the thread + * @param name Unique name for the thread + * @param func Function to execute in the thread + * @return true if thread was started successfully, false if name already exists + */ + template + bool startThread(const std::string& name, Func&& func); + + /** + * @brief Stop and join a specific thread + * @param name Name of the thread to stop + * @return true if thread was found and stopped, false otherwise + */ + bool stopThread(const std::string& name); + + /** + * @brief Stop and join all threads + */ + void stopAllThreads(); + + /** + * @brief Check if a thread with given name exists + * @param name Thread name to check + * @return true if thread exists and is joinable + */ + bool hasThread(const std::string& name) const; + + /** + * @brief Get count of active threads + * @return Number of active threads + */ + size_t getThreadCount() const; + + /** + * @brief Get list of all thread names + * @return Vector of thread names + */ + std::vector getThreadNames() const; + + /** + * @brief Check if thread is still running + * @param name Thread name to check + * @return true if thread exists and is still running + */ + bool isThreadRunning(const std::string& name) const; + + /** + * @brief Request thread to stop (thread-safe signal) + * @param name Thread name + * @return true if stop signal was sent, false if thread doesn't exist + */ + bool requestThreadStop(const std::string& name); + + /** + * @brief Check if stop was requested for a thread + * @param name Thread name + * @return true if stop was requested + */ + bool isStopRequested(const std::string& name) const; + +private: + /** + * @brief Internal thread info structure + */ + struct ThreadInfo { + std::unique_ptr thread; + std::atomic stop_requested{false}; + + ThreadInfo() = default; + ThreadInfo(std::unique_ptr t) : thread(std::move(t)), stop_requested(false) {} + + // Non-copyable, movable (custom move constructor/assignment due to atomic) + ThreadInfo(const ThreadInfo&) = delete; + ThreadInfo& operator=(const ThreadInfo&) = delete; + + ThreadInfo(ThreadInfo&& other) noexcept + : thread(std::move(other.thread)), stop_requested(other.stop_requested.load()) {} + + ThreadInfo& operator=(ThreadInfo&& other) noexcept { + if (this != &other) { + thread = std::move(other.thread); + stop_requested.store(other.stop_requested.load()); + } + return *this; + } + }; + + /** + * @brief Internal thread stop and cleanup (caller must hold mutex) + * @param name Thread name + * @return true if thread was found and stopped + */ + bool stopThreadInternal(const std::string& name); + + /** + * @brief Cleanup finished threads (caller must hold mutex) + */ + void cleanupFinishedThreads(); + + mutable std::recursive_mutex threads_mutex_; + std::map threads_; +}; + +// Template implementation +template +bool ThreadManager::startThread(const std::string& name, Func&& func) { + std::lock_guard lock(threads_mutex_); + + // Check if thread with this name already exists + if (threads_.find(name) != threads_.end()) { + return false; + } + + try { + // Create the thread with a wrapper that allows for stop checking + auto thread_ptr = std::make_unique([this, name, func = std::forward(func)]() { + try { + func(); + } catch (const std::exception& e) { + // Log exception but don't crash + // Note: Could add logging here if logger is available + } + }); + + ThreadInfo info(std::move(thread_ptr)); + threads_[name] = std::move(info); + + return true; + + } catch (const std::exception& e) { + // Failed to create thread + return false; + } +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/TxFrame.cpp b/app/wfbngrtl8812/src/main/cpp/TxFrame.cpp index 814fb4df..31a3dae 100644 --- a/app/wfbngrtl8812/src/main/cpp/TxFrame.cpp +++ b/app/wfbngrtl8812/src/main/cpp/TxFrame.cpp @@ -1,6 +1,6 @@ #include "TxFrame.h" -constexpr char *TAG = "TXFrame"; +constexpr char TAG[] = "TXFrame"; //------------------------------------------------------------- // Implementation of Transmitter diff --git a/app/wfbngrtl8812/src/main/cpp/WfbConfiguration.hpp b/app/wfbngrtl8812/src/main/cpp/WfbConfiguration.hpp new file mode 100644 index 0000000..f3f2c09 --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/WfbConfiguration.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include + +/** + * @brief Central configuration management for WfbngLink + * + * This class consolidates all hardcoded values from WfbngLink into + * structured configuration objects for better maintainability. + */ +class WfbConfiguration { +public: + /** + * @brief Network port configurations + */ + struct NetworkPorts { + static constexpr int VIDEO_CLIENT_PORT = 5600; + static constexpr int MAVLINK_CLIENT_PORT = 14550; + static constexpr int UDP_CLIENT_PORT = 8000; + static constexpr int ADAPTIVE_LINK_PORT = 9999; + static constexpr int UDP_TX_PORT = 8001; + static constexpr uint8_t WFB_TX_PORT = 160; + static constexpr uint8_t WFB_RX_PORT = 32; + static constexpr uint8_t MAVLINK_RADIO_PORT = 0x10; + }; + + /** + * @brief Adaptive link quality management configuration + */ + struct AdaptiveLinkConfig { + std::string target_ip = "10.5.0.10"; + std::string client_ip = "127.0.0.1"; + std::chrono::milliseconds update_interval{100}; + std::chrono::microseconds usb_event_timeout{500000}; // 500ms + int default_tx_power = 30; + bool enabled_by_default = true; + + // Signal quality mapping parameters + int signal_quality_min = -1024; + int signal_quality_max = 1024; + int mapped_quality_min = 1000; + int mapped_quality_max = 2000; + }; + + /** + * @brief Forward Error Correction (FEC) configuration + */ + struct FecConfig { + // FEC switching thresholds + int lost_to_5 = 2; // Lost packets threshold to switch to FEC 5 + int recovered_to_4 = 30; // Recovered packets threshold to switch to FEC 4 + int recovered_to_3 = 24; // Recovered packets threshold to switch to FEC 3 + int recovered_to_2 = 14; // Recovered packets threshold to switch to FEC 2 + int recovered_to_1 = 8; // Recovered packets threshold to switch to FEC 1 + + // FEC transmission parameters + int default_k = 1; // Number of data packets + int default_n = 5; // Total packets (data + redundancy) + + bool enabled_by_default = false; + }; + + /** + * @brief Physical layer configuration + */ + struct PhyConfig { + bool ldpc_enabled = true; // Low-Density Parity-Check + bool stbc_enabled = true; // Space-Time Block Coding + bool vht_mode = false; // Very High Throughput mode + bool short_gi = false; // Short Guard Interval + int default_bandwidth = 20; // Channel bandwidth in MHz + int default_mcs_index = 0; // Modulation and Coding Scheme index + }; + + /** + * @brief Device and channel configuration + */ + struct DeviceConfig { + uint32_t link_id = 7669206; // Default link identifier + std::string key_path = "/data/user/0/com.openipc.pixelpilot/files/gs.key"; + uint64_t epoch = 0; // Default epoch for aggregators + + // Channel width constants + enum class ChannelWidth { + WIDTH_20MHZ = 20, + WIDTH_40MHZ = 40 + }; + }; + + /** + * @brief Thread and timing configuration + */ + struct ThreadConfig { + std::chrono::seconds fec_decay_tick{1}; // FEC controller decay interval + std::chrono::milliseconds stats_callback_interval{300}; // Statistics callback frequency + int max_concurrent_devices = 8; // Maximum number of RTL devices + }; + + /** + * @brief Logging configuration + */ + struct LogConfig { + std::string tag = "pixelpilot"; + bool debug_rssi_enabled = true; + bool verbose_logging = false; + }; + + // Configuration instances + NetworkPorts network_ports; + AdaptiveLinkConfig adaptive_link; + FecConfig fec; + PhyConfig phy; + DeviceConfig device; + ThreadConfig threading; + LogConfig logging; + + /** + * @brief Create default configuration + */ + static WfbConfiguration createDefault() { + return WfbConfiguration{}; + } + + /** + * @brief Create configuration optimized for testing + */ + static WfbConfiguration createForTesting() { + WfbConfiguration config; + config.adaptive_link.update_interval = std::chrono::milliseconds{10}; + config.threading.stats_callback_interval = std::chrono::milliseconds{50}; + config.logging.verbose_logging = true; + return config; + } + + /** + * @brief Validate configuration values + * @return true if configuration is valid, false otherwise + */ + bool validate() const { + // Validate port ranges + if (network_ports.VIDEO_CLIENT_PORT <= 0 || network_ports.VIDEO_CLIENT_PORT > 65535) { + return false; + } + if (network_ports.MAVLINK_CLIENT_PORT <= 0 || network_ports.MAVLINK_CLIENT_PORT > 65535) { + return false; + } + if (network_ports.UDP_CLIENT_PORT <= 0 || network_ports.UDP_CLIENT_PORT > 65535) { + return false; + } + + // Validate adaptive link config + if (adaptive_link.default_tx_power < 0 || adaptive_link.default_tx_power > 100) { + return false; + } + if (adaptive_link.update_interval.count() <= 0) { + return false; + } + + // Validate FEC thresholds + if (fec.lost_to_5 < 0 || fec.recovered_to_1 < 0) { + return false; + } + + // Validate PHY config + if (phy.default_bandwidth != 20 && phy.default_bandwidth != 40) { + return false; + } + + return true; + } +}; \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/WfbLogger.cpp b/app/wfbngrtl8812/src/main/cpp/WfbLogger.cpp new file mode 100644 index 0000000..b6ba60b --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/WfbLogger.cpp @@ -0,0 +1,131 @@ +#include "WfbLogger.hpp" + +#include +#include +#include + +WfbLogger& WfbLogger::getInstance() { + static WfbLogger instance; + return instance; +} + +void WfbLogger::log(Level level, Category category, const std::string& message) { + if (!shouldLog(level)) { + return; + } + + const char* tag = categoryToTag(category); + androidLog(level, tag, message); +} + + + +void WfbLogger::debug(Category category, const std::string& message) { + log(Level::DEBUG, category, message); +} + +void WfbLogger::info(Category category, const std::string& message) { + log(Level::INFO, category, message); +} + +void WfbLogger::warn(Category category, const std::string& message) { + log(Level::WARN, category, message); +} + +void WfbLogger::error(Category category, const std::string& message) { + log(Level::ERROR, category, message); +} + +void WfbLogger::setMinLevel(Level level) { + min_level_ = level; +} + +bool WfbLogger::shouldLog(Level level) const { + return static_cast(level) >= static_cast(min_level_); +} + +const char* WfbLogger::levelToString(Level level) { + switch (level) { + case Level::DEBUG: return "DEBUG"; + case Level::INFO: return "INFO"; + case Level::WARN: return "WARN"; + case Level::ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} + +const char* WfbLogger::categoryToString(Category category) { + switch (category) { + case Category::DEVICE: return "DEVICE"; + case Category::NETWORK: return "NETWORK"; + case Category::THREAD: return "THREAD"; + case Category::ADAPTIVE: return "ADAPTIVE"; + case Category::JNI: return "JNI"; + case Category::CONFIG: return "CONFIG"; + case Category::AGGREGATOR: return "AGGREGATOR"; + case Category::GENERAL: return "GENERAL"; + default: return "UNKNOWN"; + } +} + +const char* WfbLogger::categoryToTag(Category category) { + switch (category) { + case Category::DEVICE: return "WFB_Device"; + case Category::NETWORK: return "WFB_Network"; + case Category::THREAD: return "WFB_Thread"; + case Category::ADAPTIVE: return "WFB_Adaptive"; + case Category::JNI: return "WFB_JNI"; + case Category::CONFIG: return "WFB_Config"; + case Category::AGGREGATOR: return "WFB_Aggregator"; + case Category::GENERAL: return "WFB_General"; + default: return "WFB_Unknown"; + } +} + +void WfbLogger::androidLog(Level level, const char* tag, const std::string& message) { + int android_level; + + switch (level) { + case Level::DEBUG: + android_level = ANDROID_LOG_DEBUG; + break; + case Level::INFO: + android_level = ANDROID_LOG_INFO; + break; + case Level::WARN: + android_level = ANDROID_LOG_WARN; + break; + case Level::ERROR: + android_level = ANDROID_LOG_ERROR; + break; + default: + android_level = ANDROID_LOG_INFO; + break; + } + + __android_log_print(android_level, tag, "%s", message.c_str()); +} + +// LogContext implementation +LogContext::LogContext(WfbLogger::Category category, const std::string& function_name) + : category_(category), function_name_(function_name), + start_time_(std::chrono::steady_clock::now()) { + + WfbLogger::getInstance().debug(category_, "→ " + function_name_); +} + +LogContext::~LogContext() { + auto end_time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time_); + + char buffer[256]; + std::snprintf(buffer, sizeof(buffer), "← %s (%.3fms)", + function_name_.c_str(), duration.count() / 1000.0); + + WfbLogger::getInstance().debug(category_, std::string(buffer)); +} + +void LogContext::log(WfbLogger::Level level, const std::string& message) { + std::string contextual_message = function_name_ + ": " + message; + WfbLogger::getInstance().log(level, category_, contextual_message); +} \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/WfbLogger.hpp b/app/wfbngrtl8812/src/main/cpp/WfbLogger.hpp new file mode 100644 index 0000000..2fbf49a --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/WfbLogger.hpp @@ -0,0 +1,224 @@ +#ifndef WFB_LOGGER_HPP +#define WFB_LOGGER_HPP + +#include +#include +#include +#include +#include + +/** + * @brief Structured logging system for WFB components + * + * This class provides a centralized logging system with categories and levels, + * making it easier to filter and analyze logs. It wraps Android's logging + * system while providing additional structure and type safety. + */ +class WfbLogger { +public: + /** + * @brief Logging levels in order of severity + */ + enum class Level { + DEBUG = 0, ///< Detailed debug information + INFO = 1, ///< General information + WARN = 2, ///< Warning messages + ERROR = 3 ///< Error messages + }; + + /** + * @brief Logging categories for different system components + */ + enum class Category { + DEVICE, ///< Device management and hardware operations + NETWORK, ///< Network operations and packet processing + THREAD, ///< Thread management and lifecycle + ADAPTIVE, ///< Adaptive link quality and control + JNI, ///< JNI interface and Java bindings + CONFIG, ///< Configuration management + AGGREGATOR, ///< Packet aggregation and processing + GENERAL ///< General purpose logging + }; + + /** + * @brief Get the singleton logger instance + * @return Reference to the global logger instance + */ + static WfbLogger& getInstance(); + + /** + * @brief Log a message with specified level and category + * @param level Logging level + * @param category Component category + * @param message Log message + */ + void log(Level level, Category category, const std::string& message); + + /** + * @brief Log a formatted message with specified level and category + * @tparam Args Variadic template arguments + * @param level Logging level + * @param category Component category + * @param format Format string + * @param args Format arguments + */ + template + void logf(Level level, Category category, const char* format, Args... args) { + if (!shouldLog(level)) { + return; + } + + // Format the message + char buffer[1024]; + int result = std::snprintf(buffer, sizeof(buffer), format, args...); + + if (result > 0 && result < static_cast(sizeof(buffer))) { + log(level, category, std::string(buffer)); + } else { + // Fallback for oversized messages + log(Level::ERROR, Category::GENERAL, "Log message too long or formatting error"); + } + } + + /** + * @brief Log a debug message + * @param category Component category + * @param message Log message + */ + void debug(Category category, const std::string& message); + + /** + * @brief Log an info message + * @param category Component category + * @param message Log message + */ + void info(Category category, const std::string& message); + + /** + * @brief Log a warning message + * @param category Component category + * @param message Log message + */ + void warn(Category category, const std::string& message); + + /** + * @brief Log an error message + * @param category Component category + * @param message Log message + */ + void error(Category category, const std::string& message); + + /** + * @brief Set the minimum logging level + * @param level Minimum level to log + */ + void setMinLevel(Level level); + + /** + * @brief Check if a level should be logged + * @param level Level to check + * @return true if the level should be logged + */ + bool shouldLog(Level level) const; + + /** + * @brief Convert logging level to string + * @param level Logging level + * @return String representation of the level + */ + static const char* levelToString(Level level); + + /** + * @brief Convert category to string + * @param category Logging category + * @return String representation of the category + */ + static const char* categoryToString(Category category); + + /** + * @brief Convert category to Android log tag + * @param category Logging category + * @return Android log tag for the category + */ + static const char* categoryToTag(Category category); + +private: + /** + * @brief Private constructor for singleton pattern + */ + WfbLogger() = default; + + /** + * @brief Send log message to Android logging system + * @param level Logging level + * @param tag Android log tag + * @param message Log message + */ + void androidLog(Level level, const char* tag, const std::string& message); + + Level min_level_{Level::DEBUG}; ///< Minimum logging level +}; + +/** + * @brief RAII-style log context for scoped logging + * + * This class provides automatic logging of function entry/exit with timing, + * useful for debugging performance and control flow. + */ +class LogContext { +public: + /** + * @brief Constructor that logs function entry + * @param category Logging category + * @param function_name Name of the function + */ + LogContext(WfbLogger::Category category, const std::string& function_name); + + /** + * @brief Destructor that logs function exit with timing + */ + ~LogContext(); + + /** + * @brief Log a message within this context + * @param level Logging level + * @param message Log message + */ + void log(WfbLogger::Level level, const std::string& message); + +private: + WfbLogger::Category category_; + std::string function_name_; + std::chrono::steady_clock::time_point start_time_; +}; + +// Convenience macros for common logging operations +#define WFB_LOG_DEBUG(category, message) \ + WfbLogger::getInstance().debug(WfbLogger::Category::category, message) + +#define WFB_LOG_INFO(category, message) \ + WfbLogger::getInstance().info(WfbLogger::Category::category, message) + +#define WFB_LOG_WARN(category, message) \ + WfbLogger::getInstance().warn(WfbLogger::Category::category, message) + +#define WFB_LOG_ERROR(category, message) \ + WfbLogger::getInstance().error(WfbLogger::Category::category, message) + +#define WFB_LOG_CONTEXT(category, function) \ + LogContext _log_ctx(WfbLogger::Category::category, function) + +// Formatted logging macros +#define WFB_LOGF_DEBUG(category, format, ...) \ + WfbLogger::getInstance().logf(WfbLogger::Level::DEBUG, WfbLogger::Category::category, format, __VA_ARGS__) + +#define WFB_LOGF_INFO(category, format, ...) \ + WfbLogger::getInstance().logf(WfbLogger::Level::INFO, WfbLogger::Category::category, format, __VA_ARGS__) + +#define WFB_LOGF_WARN(category, format, ...) \ + WfbLogger::getInstance().logf(WfbLogger::Level::WARN, WfbLogger::Category::category, format, __VA_ARGS__) + +#define WFB_LOGF_ERROR(category, format, ...) \ + WfbLogger::getInstance().logf(WfbLogger::Level::ERROR, WfbLogger::Category::category, format, __VA_ARGS__) + +#endif // WFB_LOGGER_HPP \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp b/app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp index d79e06f..ec7f6af 100644 --- a/app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp +++ b/app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp @@ -1,34 +1,16 @@ #include "WfbngLink.hpp" +#include "DeviceManager.hpp" +#include "AggregatorManager.hpp" +#include "ThreadManager.hpp" +#include "AdaptiveLinkController.hpp" +#include "PacketProcessor.hpp" -#include -#include #include #include +#include -#include "RxFrame.h" -#include "SignalQualityCalculator.h" #include "TxFrame.h" #include "libusb.h" -#include "wfb-ng/src/wifibroadcast.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #undef TAG #define TAG "pixelpilot" @@ -53,39 +35,27 @@ std::string generate_random_string(size_t length) { return result; } -WfbngLink::WfbngLink(JNIEnv *env, jobject context) - : current_fd(-1), adaptive_link_enabled(true), adaptive_tx_power(30) { - initAgg(); +WfbngLink::WfbngLink(JNIEnv *env, jobject context, const WfbConfiguration& config) + : config_(config), current_fd(-1), adaptive_link_enabled(config.adaptive_link.enabled_by_default), + adaptive_tx_power(config.adaptive_link.default_tx_power) { Logger_t log; - wifi_driver = std::make_unique(log); -} - -void WfbngLink::initAgg() { - std::string client_addr = "127.0.0.1"; - uint64_t epoch = 0; - - uint8_t video_radio_port = 0; - uint32_t video_channel_id_f = (link_id << 8) + video_radio_port; - video_channel_id_be = htobe32(video_channel_id_f); - auto udsName = std::string("my_socket"); - - video_aggregator = std::make_unique(client_addr, 5600, keyPath, epoch, video_channel_id_f, 0); + wifi_driver = std::make_shared(log); - int mavlink_client_port = 14550; - uint8_t mavlink_radio_port = 0x10; - uint32_t mavlink_channel_id_f = (link_id << 8) + mavlink_radio_port; - mavlink_channel_id_be = htobe32(mavlink_channel_id_f); + // Initialize component managers + device_manager = std::make_unique(wifi_driver); + aggregator_manager = std::make_unique(config_); + thread_manager_ = std::make_unique(); + adaptive_controller_ = std::make_unique(config_, std::shared_ptr(device_manager.get(), [](DeviceManager*){}), fec); + packet_processor_ = std::make_unique(std::shared_ptr(aggregator_manager.get(), [](AggregatorManager*){}), config_); - mavlink_aggregator = - std::make_unique(client_addr, mavlink_client_port, keyPath, epoch, mavlink_channel_id_f, 0); - - int udp_client_port = 8000; - uint8_t udp_radio_port = wfb_rx_port; - uint32_t udp_channel_id_f = (link_id << 8) + udp_radio_port; - udp_channel_id_be = htobe32(udp_channel_id_f); + // Initialize aggregators + initAgg(); +} - udp_aggregator = - std::make_unique(client_addr, udp_client_port, keyPath, epoch, udp_channel_id_f, 0); +void WfbngLink::initAgg() { + if (aggregator_manager) { + aggregator_manager->initializeAggregators(); + } } int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint fd) { @@ -115,8 +85,8 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint r = libusb_claim_interface(dev_handle, 0); __android_log_print(ANDROID_LOG_DEBUG, TAG, "Creating driver and device for fd=%d", fd); - rtl_devices.emplace(fd, wifi_driver->CreateRtlDevice(dev_handle)); - if (!rtl_devices.at(fd)) { + auto device = device_manager->createDevice(fd, dev_handle); + if (!device) { libusb_exit(ctx); __android_log_print(ANDROID_LOG_ERROR, TAG, "CreateRtlDevice error"); return -1; @@ -127,60 +97,15 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint uint8_t *mavlink_channel_id_be8 = reinterpret_cast(&mavlink_channel_id_be); try { - auto packetProcessor = - [this, video_channel_id_be8, mavlink_channel_id_be8, udp_channel_id_be8](const Packet &packet) { - RxFrame frame(packet.Data); - if (!frame.IsValidWfbFrame()) { - return; - } - int8_t rssi[4] = {(int8_t)packet.RxAtrib.rssi[0], (int8_t)packet.RxAtrib.rssi[1], 1, 1}; - uint32_t freq = 0; - int8_t noise[4] = {1, 1, 1, 1}; - uint8_t antenna[4] = {1, 1, 1, 1}; - - std::lock_guard lock(agg_mutex); - if (frame.MatchesChannelID(video_channel_id_be8)) { - SignalQualityCalculator::get_instance().add_rssi(packet.RxAtrib.rssi[0], packet.RxAtrib.rssi[1]); - SignalQualityCalculator::get_instance().add_snr(packet.RxAtrib.snr[0], packet.RxAtrib.snr[1]); - - video_aggregator->process_packet(packet.Data.data() + sizeof(ieee80211_header), - packet.Data.size() - sizeof(ieee80211_header) - 4, - 0, - antenna, - rssi, - noise, - freq, - 0, - 0, - NULL); - if (should_clear_stats) { - video_aggregator->clear_stats(); - should_clear_stats = false; - } - } else if (frame.MatchesChannelID(mavlink_channel_id_be8)) { - mavlink_aggregator->process_packet(packet.Data.data() + sizeof(ieee80211_header), - packet.Data.size() - sizeof(ieee80211_header) - 4, - 0, - antenna, - rssi, - noise, - freq, - 0, - 0, - NULL); - } else if (frame.MatchesChannelID(udp_channel_id_be8)) { - udp_aggregator->process_packet(packet.Data.data() + sizeof(ieee80211_header), - packet.Data.size() - sizeof(ieee80211_header) - 4, - 0, - antenna, - rssi, - noise, - freq, - 0, - 0, - NULL); - } - }; + auto packetProcessor = [this](const Packet &packet) { + if (aggregator_manager) { + aggregator_manager->processPacket(packet); + } + if (should_clear_stats && aggregator_manager) { + aggregator_manager->clearStats(); + should_clear_stats = false; + } + }; // Store the current fd for later TX power updates. current_fd = fd; @@ -188,9 +113,10 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint if (!usb_event_thread) { auto usb_event_thread_func = [ctx, this, fd] { while (true) { - auto dev = this->rtl_devices.at(fd).get(); - if (dev == nullptr || dev->should_stop) break; - struct timeval timeout = {0, 500000}; // 500ms timeout + auto device = this->device_manager->getDevice(fd); + if (!device || !device->isValid()) break; + auto timeout_us = config_.adaptive_link.usb_event_timeout.count(); + struct timeval timeout = {0, static_cast(timeout_us)}; int r = libusb_handle_events_timeout(ctx, &timeout); if (r < 0) { this->log->error("Error handling events: {}", r); @@ -202,24 +128,25 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint init_thread(usb_event_thread, [=]() { return std::make_unique(usb_event_thread_func); }); std::shared_ptr args = std::make_shared(); - args->udp_port = 8001; - args->link_id = link_id; - args->keypair = keyPath; - args->stbc = stbc_enabled; - args->ldpc = ldpc_enabled; - args->mcs_index = 0; - args->vht_mode = false; - args->short_gi = false; - args->bandwidth = 20; - args->k = 1; - args->n = 5; - args->radio_port = wfb_tx_port; + args->udp_port = config_.network_ports.UDP_TX_PORT; + args->link_id = config_.device.link_id; + args->keypair = config_.device.key_path.c_str(); + args->stbc = config_.phy.stbc_enabled; + args->ldpc = config_.phy.ldpc_enabled; + args->mcs_index = config_.phy.default_mcs_index; + args->vht_mode = config_.phy.vht_mode; + args->short_gi = config_.phy.short_gi; + args->bandwidth = config_.phy.default_bandwidth; + args->k = config_.fec.default_k; + args->n = config_.fec.default_n; + args->radio_port = config_.network_ports.WFB_TX_PORT; __android_log_print( ANDROID_LOG_ERROR, TAG, "radio link ID %d, radio PORT %d", args->link_id, args->radio_port); - Rtl8812aDevice *current_device = rtl_devices.at(fd).get(); - if (!usb_tx_thread) { + auto current_device_wrapper = device_manager->getDevice(fd); + if (current_device_wrapper && !usb_tx_thread) { + Rtl8812aDevice *current_device = current_device_wrapper->get(); init_thread(usb_tx_thread, [&]() { return std::make_unique([this, current_device, args] { txFrame->run(current_device, args.get()); @@ -228,24 +155,33 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint }); } - if (adaptive_link_enabled) { - stop_adaptive_link(); - start_link_quality_thread(fd); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Checking adaptive link: enabled=%d, controller=%p", + adaptive_link_enabled, adaptive_controller_.get()); + + if (adaptive_link_enabled && adaptive_controller_) { + bool started = adaptive_controller_->start(fd); + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Adaptive link start result: %d", started); + } else { + __android_log_print(ANDROID_LOG_WARN, TAG, "Adaptive link not started: enabled=%d, controller=%p", + adaptive_link_enabled, adaptive_controller_.get()); } } auto bandWidth = (bw == 20 ? CHANNEL_WIDTH_20 : CHANNEL_WIDTH_40); - rtl_devices.at(fd)->Init(packetProcessor, - SelectedChannel{ - .Channel = static_cast(wifiChannel), - .ChannelOffset = 0, - .ChannelWidth = bandWidth, - }); + auto device = device_manager->getDevice(fd); + if (device) { + device->get()->Init(packetProcessor, + SelectedChannel{ + .Channel = static_cast(wifiChannel), + .ChannelOffset = 0, + .ChannelWidth = bandWidth, + }); + } } catch (const std::runtime_error &error) { __android_log_print(ANDROID_LOG_ERROR, TAG, "runtime_error: %s", error.what()); - auto dev = rtl_devices.at(fd).get(); - if (dev) { - dev->should_stop = true; + auto device = device_manager->getDevice(fd); + if (device) { + device->markForStop(); } txFrame->stop(); @@ -256,9 +192,9 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint } __android_log_print(ANDROID_LOG_DEBUG, TAG, "Init done, releasing..."); - auto dev = rtl_devices.at(fd).get(); - if (dev) { - dev->should_stop = true; + auto cleanup_device = device_manager->getDevice(fd); + if (cleanup_device) { + cleanup_device->markForStop(); } txFrame->stop(); @@ -273,311 +209,62 @@ int WfbngLink::run(JNIEnv *env, jobject context, jint wifiChannel, jint bw, jint } void WfbngLink::stop(JNIEnv *env, jobject context, jint fd) { - if (rtl_devices.find(fd) == rtl_devices.end()) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "rtl_devices.find(%d) == rtl_devices.end()", fd); + if (!device_manager || !device_manager->hasDevice(fd)) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Device with fd=%d not found", fd); CRASH(); return; } - auto dev = rtl_devices.at(fd).get(); - if (dev) { - dev->should_stop = true; + auto device = device_manager->getDevice(fd); + if (device) { + device->markForStop(); } else { - __android_log_print(ANDROID_LOG_ERROR, TAG, "rtl_devices.at(%d) is nullptr", fd); + __android_log_print(ANDROID_LOG_ERROR, TAG, "Device at fd=%d is nullptr", fd); } stop_adaptive_link(); } -//--------------------------------------JAVA bindings-------------------------------------- -inline jlong jptr(WfbngLink *wfbngLinkN) { return reinterpret_cast(wfbngLinkN); } - -inline WfbngLink *native(jlong ptr) { return reinterpret_cast(ptr); } - -inline std::list toList(JNIEnv *env, jobject list) { - // Get the class and method IDs for java.util.List and its methods - jclass listClass = env->GetObjectClass(list); - jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I"); - jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); - // Method ID to get int value from Integer object - jclass integerClass = env->FindClass("java/lang/Integer"); - jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I"); - - // Get the size of the list - jint size = env->CallIntMethod(list, sizeMethod); - - // Create a C++ list to store the elements - std::list res; - - // Iterate over the list and add elements to the C++ list - for (int i = 0; i < size; ++i) { - // Get the element at index i - jobject element = env->CallObjectMethod(list, getMethod, i); - // Convert the element to int - jint value = env->CallIntMethod(element, intValueMethod); - // Add the element to the C++ list - res.push_back(value); - } - - return res; -} -extern "C" JNIEXPORT jlong JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeInitialize(JNIEnv *env, - jclass clazz, - jobject context) { - auto *p = new WfbngLink(env, context); - return jptr(p); -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRun( - JNIEnv *env, jclass clazz, jlong wfbngLinkN, jobject androidContext, jint wifiChannel, int bandWidth, jint fd) { - native(wfbngLinkN)->run(env, androidContext, wifiChannel, bandWidth, fd); -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStartAdaptivelink(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN) { - if (native(wfbngLinkN)->video_aggregator == nullptr) { - return; - } - auto aggregator = native(wfbngLinkN)->video_aggregator.get(); -} - -extern "C" JNIEXPORT jint JNICALL Java_com_openipc_pixelpilot_UsbSerialService_nativeGetSignalQuality(JNIEnv *env, - jclass clazz) { - return SignalQualityCalculator::get_instance().calculate_signal_quality().quality; -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStop( - JNIEnv *env, jclass clazz, jlong wfbngLinkN, jobject androidContext, jint fd) { - native(wfbngLinkN)->stop(env, androidContext, fd); -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeCallBack(JNIEnv *env, - jclass clazz, - jobject wfbStatChangedI, - jlong wfbngLinkN) { - if (native(wfbngLinkN)->video_aggregator == nullptr) { - return; - } - auto aggregator = native(wfbngLinkN)->video_aggregator.get(); - jclass jClassExtendsIWfbStatChangedI = env->GetObjectClass(wfbStatChangedI); - jclass jcStats = env->FindClass("com/openipc/wfbngrtl8812/WfbNGStats"); - if (jcStats == nullptr) { - return; +void WfbngLink::stopDevice() { + if (!device_manager || current_fd == -1) return; + auto device = device_manager->getDevice(current_fd); + if (device) { + device->markForStop(); } - jmethodID jcStatsConstructor = env->GetMethodID(jcStats, "", "(IIIIIIII)V"); - if (jcStatsConstructor == nullptr) { - return; - } - SignalQualityCalculator::get_instance().add_fec_data( - aggregator->count_p_all, aggregator->count_p_fec_recovered, aggregator->count_p_lost); - auto stats = env->NewObject(jcStats, - jcStatsConstructor, - (jint)aggregator->count_p_all, - (jint)aggregator->count_p_dec_err, - (jint)(aggregator->count_p_all - aggregator->count_p_dec_err), - (jint)aggregator->count_p_fec_recovered, - (jint)aggregator->count_p_lost, - (jint)aggregator->count_p_bad, - (jint)aggregator->count_p_override, - (jint)aggregator->count_p_outgoing); - if (stats == nullptr) { - return; - } - jmethodID onStatsChanged = env->GetMethodID( - jClassExtendsIWfbStatChangedI, "onWfbNgStatsChanged", "(Lcom/openipc/wfbngrtl8812/WfbNGStats;)V"); - if (onStatsChanged == nullptr) { - return; - } - env->CallVoidMethod(wfbStatChangedI, onStatsChanged, stats); - native(wfbngLinkN)->should_clear_stats = true; } -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRefreshKey(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN) { - native(wfbngLinkN)->initAgg(); +void WfbngLink::setFecThresholds(int lostTo5, int recTo4, int recTo3, int recTo2, int recTo1) { + config_.fec.lost_to_5 = lostTo5; + config_.fec.recovered_to_4 = recTo4; + config_.fec.recovered_to_3 = recTo3; + config_.fec.recovered_to_2 = recTo2; + config_.fec.recovered_to_1 = recTo1; } -// Modified start_link_quality_thread: use adaptive_link_enabled and adaptive_tx_power -void WfbngLink::start_link_quality_thread(int fd) { - auto thread_func = [this, fd]() { - std::this_thread::sleep_for(std::chrono::seconds(1)); - const char *ip = "10.5.0.10"; - int port = 9999; - int sockfd; - struct sockaddr_in server_addr; - // Create UDP socket - if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Socket creation failed"); - return; - } - int opt = 1; - setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Invalid IP address"); - close(sockfd); - return; - } +void WfbngLink::setAdaptiveLinkEnabled(bool enabled) { + adaptive_link_enabled = enabled; - while (!this->adaptive_link_should_stop) { - auto quality = SignalQualityCalculator::get_instance().calculate_signal_quality(); -#if defined(ANDROID_DEBUG_RSSI) || true - __android_log_print(ANDROID_LOG_WARN, TAG, "quality %d", quality.quality); -#endif - time_t currentEpoch = time(nullptr); - const auto map_range = - [](double value, double inputMin, double inputMax, double outputMin, double outputMax) { - return outputMin + ((value - inputMin) * (outputMax - outputMin) / (inputMax - inputMin)); - }; - // map to 1000..2000 - quality.quality = map_range(quality.quality, -1024, 1024, 1000, 2000); - { - uint32_t len; - char message[100]; - - /** - 1741491090:1602:1602:1:0:-70:24:num_ants:pnlt:fec_change:code - - :::::::::: - - gs_time: gs clock - link_score: 1000 - 2000 sent twice (already including any penalty) - link_score: 1000 - 2000 sent twice (already including any penalty) - fec: instantaneus fec_rec (only used by old fec_rec_pntly now disabled by default) - lost: instantaneus lost (not used) - rssi_dB: best antenna rssi (for osd) - snr_dB: best antenna snr_dB (for osd) - num_ants: number of gs antennas (for osd) - noise_penalty: penalty deducted from score due to noise (for osd) - fec_change: int from 0 to 5 : how much to alter fec based on noise - optional idr_request_code: 4 char unique code to request 1 keyframe (no need to send special extra - packets) - */ - - - // Use the new public FEC threshold values - if (quality.lost_last_second > fec_lost_to_5) { - fec.bump(5); // Bump to FEC 5 - } else if (quality.recovered_last_second > fec_recovered_to_4) { - fec.bump(4); // Bump to FEC 4 - } else if (quality.recovered_last_second > fec_recovered_to_3) { - fec.bump(3); // Bump to FEC 3 - } else if (quality.recovered_last_second > fec_recovered_to_2) { - fec.bump(2); // Bump to FEC 2 - } else if (quality.recovered_last_second > fec_recovered_to_1) { - fec.bump(1); // Bump to FEC 1 - } - - snprintf(message + sizeof(len), - sizeof(message) - sizeof(len), - "%ld:%d:%d:%d:%d:%d:%f:0:-1:%d:%s\n", - static_cast(currentEpoch), - quality.quality, - quality.quality, - quality.recovered_last_second, - quality.lost_last_second, - quality.quality, - quality.snr, - fec.value(), - quality.idr_code.c_str()); - len = strlen(message + sizeof(len)); - len = htonl(len); - memcpy(message, &len, sizeof(len)); - __android_log_print(ANDROID_LOG_ERROR, TAG, " message %s", message + 4); - ssize_t sent = sendto(sockfd, - message, - strlen(message + sizeof(len)) + sizeof(len), - 0, - (struct sockaddr *)&server_addr, - sizeof(server_addr)); - if (sent < 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to send message"); - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - close(sockfd); - this->adaptive_link_should_stop = false; - }; - - init_thread(link_quality_thread, [=]() { return std::make_unique(thread_func); }); - rtl_devices.at(fd)->SetTxPower(adaptive_tx_power); -} + if (adaptive_controller_) { + adaptive_controller_->setEnabled(enabled); -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetAdaptiveLinkEnabled( - JNIEnv *env, jclass clazz, jlong wfbngLinkN, jboolean enabled) { - WfbngLink *link = native(wfbngLinkN); - bool wasEnabled = link->adaptive_link_enabled; - link->adaptive_link_enabled = enabled; - // If we are enabling adaptive mode (and it was previously disabled) - if (enabled && !wasEnabled) { - link->stop_adaptive_link(); - if (link->current_fd != -1) { - // If a previous adaptive thread exists, join it first. - // Restart the adaptive (link quality) thread. - link->start_link_quality_thread(link->current_fd); + if (enabled && current_fd != -1) { + adaptive_controller_->start(current_fd); + } else if (!enabled) { + adaptive_controller_->stop(); } } - // When disabling, wait for the thread to exit (if running). - if (!enabled && wasEnabled) { - link->stop_adaptive_link(); - } } -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetTxPower(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN, - jint power) { - WfbngLink *link = native(wfbngLinkN); - if (link->adaptive_tx_power == power) return; +void WfbngLink::setAdaptiveTxPower(int power) { + adaptive_tx_power = power; - link->adaptive_tx_power = power; - if (link->current_fd != -1 && link->rtl_devices.find(link->current_fd) != link->rtl_devices.end()) { - link->rtl_devices.at(link->current_fd)->SetTxPower(power); + if (adaptive_controller_) { + adaptive_controller_->setTxPower(power); } - // If adaptive mode is enabled and the adaptive thread is not running, restart it. - if (link->adaptive_link_enabled) { - link->stop_adaptive_link(); - if (link->current_fd != -1) { - link->start_link_quality_thread(link->current_fd); - } - } -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseFec(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN, - jint use) { - WfbngLink *link = native(wfbngLinkN); - link->fec.setEnabled(use); } -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseLdpc(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN, - jint use) { - WfbngLink *link = native(wfbngLinkN); - link->ldpc_enabled = (use != 0); -} - -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseStbc(JNIEnv *env, - jclass clazz, - jlong wfbngLinkN, - jint use) { - WfbngLink *link = native(wfbngLinkN); - link->stbc_enabled = (use != 0); +void WfbngLink::stop_adaptive_link() { + if (adaptive_controller_) { + adaptive_controller_->stop(); + } } -extern "C" JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetFecThresholds( - JNIEnv *env, jclass clazz, jlong nativeInstance, jint lostTo5, jint recTo4, jint recTo3, jint recTo2, jint recTo1) { - WfbngLink *link = reinterpret_cast(nativeInstance); - if (!link) return; - link->fec_lost_to_5 = lostTo5; - link->fec_recovered_to_4 = recTo4; - link->fec_recovered_to_3 = recTo3; - link->fec_recovered_to_2 = recTo2; - link->fec_recovered_to_1 = recTo1; -} \ No newline at end of file +// JNI functions have been moved to WfbngLinkJNI.cpp for better separation of concerns \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/WfbngLink.hpp b/app/wfbngrtl8812/src/main/cpp/WfbngLink.hpp index fbc20ca..e14d3f9 100644 --- a/app/wfbngrtl8812/src/main/cpp/WfbngLink.hpp +++ b/app/wfbngrtl8812/src/main/cpp/WfbngLink.hpp @@ -1,6 +1,7 @@ #ifndef FPV_VR_WFBNG_LINK_H #define FPV_VR_WFBNG_LINK_H +#include "WfbConfiguration.hpp" #include "FecChangeController.h" #include "SignalQualityCalculator.h" #include "TxFrame.h" @@ -11,6 +12,13 @@ extern "C" { #include "devourer/src/WiFiDriver.h" #include "wfb-ng/src/rx.hpp" + +// Forward declarations +class DeviceManager; +class AggregatorManager; +class ThreadManager; +class AdaptiveLinkController; +class PacketProcessor; #include #include #include @@ -28,7 +36,7 @@ class WfbngLink { int fec_recovered_to_3 = 24; int fec_recovered_to_2 = 14; int fec_recovered_to_1 = 8; - WfbngLink(JNIEnv *env, jobject context); + WfbngLink(JNIEnv *env, jobject context, const WfbConfiguration& config = WfbConfiguration::createDefault()); int run(JNIEnv *env, jobject androidContext, jint wifiChannel, jint bw, jint fd); @@ -36,28 +44,27 @@ class WfbngLink { void stop(JNIEnv *env, jobject androidContext, jint fd); - std::mutex agg_mutex; - std::unique_ptr video_aggregator; - std::unique_ptr mavlink_aggregator; - std::unique_ptr udp_aggregator; + // Configuration methods + void setFecThresholds(int lostTo5, int recTo4, int recTo3, int recTo2, int recTo1); - void start_link_quality_thread(int fd); + // Adaptive link control methods + void setAdaptiveLinkEnabled(bool enabled); + void setAdaptiveTxPower(int power); - // adaptive link - // TODO: move this to private section - int current_fd; - bool adaptive_link_enabled; - bool adaptive_link_should_stop{false}; - int adaptive_tx_power; + // Legacy public access for JNI compatibility + std::unique_ptr aggregator_manager; + std::unique_ptr device_manager; + FecChangeController fec; // Runtime configurable PHY parameters bool ldpc_enabled{true}; bool stbc_enabled{true}; - std::map> rtl_devices; - std::unique_ptr link_quality_thread{nullptr}; + // For backward compatibility with JNI layer + int current_fd{-1}; + bool adaptive_link_enabled{true}; + int adaptive_tx_power{30}; bool should_clear_stats{false}; - FecChangeController fec; void init_thread(std::unique_ptr &thread, const std::function()> &init_func) { @@ -74,26 +81,22 @@ class WfbngLink { } } - void stop_adaptive_link() { - std::unique_lock lock(thread_mutex); - - if (!link_quality_thread) return; - this->adaptive_link_should_stop = true; - destroy_thread(link_quality_thread); - } + void stop_adaptive_link(); private: - void stopDevice() { - if (rtl_devices.find(current_fd) == rtl_devices.end()) return; - auto dev = rtl_devices.at(current_fd).get(); - if (dev) { - dev->should_stop = true; - } - } + void stopDevice(); + + WfbConfiguration config_; + + // Component managers + std::unique_ptr thread_manager_; + std::unique_ptr adaptive_controller_; + std::unique_ptr packet_processor_; + // Legacy members (to be removed after full migration) const char *keyPath = "/data/user/0/com.openipc.pixelpilot/files/gs.key"; std::recursive_mutex thread_mutex; - std::unique_ptr wifi_driver; + std::shared_ptr wifi_driver; std::shared_ptr txFrame; uint32_t video_channel_id_be; uint32_t mavlink_channel_id_be; diff --git a/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.cpp b/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.cpp new file mode 100644 index 0000000..c4281e3 --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.cpp @@ -0,0 +1,388 @@ +#include "WfbngLinkJNI.hpp" +#include "SignalQualityCalculator.h" +#include "AggregatorManager.hpp" +#include "DeviceManager.hpp" + +#include +#include + +#undef TAG +#define TAG "WfbngLinkJNI" + +// Helper function implementations +WfbngLink* WfbngLinkJNI::getNativeInstance(jlong ptr) { + return reinterpret_cast(ptr); +} + +jlong WfbngLinkJNI::getJniPointer(WfbngLink* instance) { + return reinterpret_cast(instance); +} + +bool WfbngLinkJNI::validateInstance(WfbngLink* instance, const char* operation) { + if (!instance) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Invalid native instance in %s", operation); + return false; + } + return true; +} + +bool WfbngLinkJNI::handleJniException(JNIEnv* env, const char* operation) { + if (env->ExceptionCheck()) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "JNI exception in %s", operation); + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; + } + return false; +} + +WfbConfiguration WfbngLinkJNI::createConfiguration(JNIEnv* env, jobject context) { + // Create default configuration for JNI interface + return WfbConfiguration::createDefault(); +} + +std::list WfbngLinkJNI::javaListToStdList(JNIEnv* env, jobject list) { + std::list result; + + if (!list) { + return result; + } + + // Get the class and method IDs for java.util.List and its methods + jclass listClass = env->GetObjectClass(list); + jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I"); + jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); + + // Method ID to get int value from Integer object + jclass integerClass = env->FindClass("java/lang/Integer"); + jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I"); + + // Get the size of the list + jint size = env->CallIntMethod(list, sizeMethod); + + // Iterate over the list and add elements to the C++ list + for (int i = 0; i < size; ++i) { + jobject element = env->CallObjectMethod(list, getMethod, i); + jint value = env->CallIntMethod(element, intValueMethod); + result.push_back(value); + } + + return result; +} + +// JNI method implementations + +jlong WfbngLinkJNI::nativeInitialize(JNIEnv* env, jclass clazz, jobject context) { + try { + WfbConfiguration config = createConfiguration(env, context); + auto* instance = new WfbngLink(env, context, config); + return getJniPointer(instance); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to initialize WfbngLink: %s", e.what()); + return 0; + } +} + +void WfbngLinkJNI::nativeRun(JNIEnv* env, jclass clazz, jlong instance, + jobject context, jint wifiChannel, jint bandwidth, jint fd) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeRun")) { + return; + } + + try { + link->run(env, context, wifiChannel, bandwidth, fd); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeRun: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeStartAdaptivelink(JNIEnv* env, jclass clazz, jlong instance) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeStartAdaptivelink")) { + return; + } + + if (!link->aggregator_manager || !link->aggregator_manager->getVideoAggregator()) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Cannot start adaptive link - aggregator not available"); + return; + } + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Manual adaptive link start requested"); + + // Force enable and start adaptive link + link->setAdaptiveLinkEnabled(true); + + if (link->current_fd != -1) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Starting adaptive link for fd=%d", link->current_fd); + } else { + __android_log_print(ANDROID_LOG_WARN, TAG, "No device fd available for adaptive link"); + } +} + +jint WfbngLinkJNI::nativeGetSignalQuality(JNIEnv* env, jclass clazz) { + try { + return SignalQualityCalculator::get_instance().calculate_signal_quality().quality; + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error getting signal quality: %s", e.what()); + return 0; + } +} + +void WfbngLinkJNI::nativeStop(JNIEnv* env, jclass clazz, jlong instance, + jobject context, jint fd) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeStop")) { + return; + } + + try { + link->stop(env, context, fd); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeStop: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeCallBack(JNIEnv* env, jclass clazz, + jobject callback, jlong instance) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeCallBack")) { + return; + } + + if (!link->aggregator_manager || !link->aggregator_manager->getVideoAggregator()) { + return; + } + + try { + auto aggregator = link->aggregator_manager->getVideoAggregator(); + jclass jClassExtendsIWfbStatChangedI = env->GetObjectClass(callback); + jclass jcStats = env->FindClass("com/openipc/wfbngrtl8812/WfbNGStats"); + + if (jcStats == nullptr) { + handleJniException(env, "nativeCallBack - FindClass"); + return; + } + + jmethodID jcStatsConstructor = env->GetMethodID(jcStats, "", "(IIIIIIII)V"); + if (jcStatsConstructor == nullptr) { + handleJniException(env, "nativeCallBack - GetMethodID constructor"); + return; + } + + SignalQualityCalculator::get_instance().add_fec_data( + aggregator->count_p_all, aggregator->count_p_fec_recovered, aggregator->count_p_lost); + + auto stats = env->NewObject(jcStats, + jcStatsConstructor, + (jint)aggregator->count_p_all, + (jint)aggregator->count_p_dec_err, + (jint)(aggregator->count_p_all - aggregator->count_p_dec_err), + (jint)aggregator->count_p_fec_recovered, + (jint)aggregator->count_p_lost, + (jint)aggregator->count_p_bad, + (jint)aggregator->count_p_override, + (jint)aggregator->count_p_outgoing); + + if (stats == nullptr) { + handleJniException(env, "nativeCallBack - NewObject"); + return; + } + + jmethodID onStatsChanged = env->GetMethodID( + jClassExtendsIWfbStatChangedI, "onWfbNgStatsChanged", "(Lcom/openipc/wfbngrtl8812/WfbNGStats;)V"); + + if (onStatsChanged == nullptr) { + handleJniException(env, "nativeCallBack - GetMethodID onStatsChanged"); + return; + } + + env->CallVoidMethod(callback, onStatsChanged, stats); + link->should_clear_stats = true; + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeCallBack: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeRefreshKey(JNIEnv* env, jclass clazz, jlong instance) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeRefreshKey")) { + return; + } + + try { + link->initAgg(); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeRefreshKey: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetAdaptiveLinkEnabled(JNIEnv* env, jclass clazz, + jlong instance, jboolean enabled) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetAdaptiveLinkEnabled")) { + return; + } + + try { + link->setAdaptiveLinkEnabled(enabled); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetAdaptiveLinkEnabled: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetTxPower(JNIEnv* env, jclass clazz, + jlong instance, jint power) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetTxPower")) { + return; + } + + try { + link->adaptive_tx_power = power; + + if (link->current_fd != -1 && link->device_manager && + link->device_manager->hasDevice(link->current_fd)) { + auto device = link->device_manager->getDevice(link->current_fd); + if (device) { + device->setTxPower(power); + } + } + + // Update TX power in adaptive controller + link->setAdaptiveTxPower(power); + + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetTxPower: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetUseFec(JNIEnv* env, jclass clazz, + jlong instance, jint enabled) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetUseFec")) { + return; + } + + try { + link->fec.setEnabled(enabled); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetUseFec: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetUseLdpc(JNIEnv* env, jclass clazz, + jlong instance, jint enabled) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetUseLdpc")) { + return; + } + + try { + link->ldpc_enabled = (enabled != 0); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetUseLdpc: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetUseStbc(JNIEnv* env, jclass clazz, + jlong instance, jint enabled) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetUseStbc")) { + return; + } + + try { + link->stbc_enabled = (enabled != 0); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetUseStbc: %s", e.what()); + } +} + +void WfbngLinkJNI::nativeSetFecThresholds(JNIEnv* env, jclass clazz, jlong instance, + jint lostTo5, jint recTo4, jint recTo3, + jint recTo2, jint recTo1) { + WfbngLink* link = getNativeInstance(instance); + if (!validateInstance(link, "nativeSetFecThresholds")) { + return; + } + + try { + link->setFecThresholds(lostTo5, recTo4, recTo3, recTo2, recTo1); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error in nativeSetFecThresholds: %s", e.what()); + } +} + +// C-style JNI function implementations that delegate to the class methods +extern "C" { + +JNIEXPORT jlong JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeInitialize( + JNIEnv* env, jclass clazz, jobject context) { + return WfbngLinkJNI::nativeInitialize(env, clazz, context); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRun( + JNIEnv* env, jclass clazz, jlong instance, jobject context, + jint wifiChannel, jint bandwidth, jint fd) { + WfbngLinkJNI::nativeRun(env, clazz, instance, context, wifiChannel, bandwidth, fd); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStartAdaptivelink( + JNIEnv* env, jclass clazz, jlong instance) { + WfbngLinkJNI::nativeStartAdaptivelink(env, clazz, instance); +} + +JNIEXPORT jint JNICALL Java_com_openipc_pixelpilot_UsbSerialService_nativeGetSignalQuality( + JNIEnv* env, jclass clazz) { + return WfbngLinkJNI::nativeGetSignalQuality(env, clazz); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStop( + JNIEnv* env, jclass clazz, jlong instance, jobject context, jint fd) { + WfbngLinkJNI::nativeStop(env, clazz, instance, context, fd); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeCallBack( + JNIEnv* env, jclass clazz, jobject callback, jlong instance) { + WfbngLinkJNI::nativeCallBack(env, clazz, callback, instance); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRefreshKey( + JNIEnv* env, jclass clazz, jlong instance) { + WfbngLinkJNI::nativeRefreshKey(env, clazz, instance); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetAdaptiveLinkEnabled( + JNIEnv* env, jclass clazz, jlong instance, jboolean enabled) { + WfbngLinkJNI::nativeSetAdaptiveLinkEnabled(env, clazz, instance, enabled); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetTxPower( + JNIEnv* env, jclass clazz, jlong instance, jint power) { + WfbngLinkJNI::nativeSetTxPower(env, clazz, instance, power); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseFec( + JNIEnv* env, jclass clazz, jlong instance, jint enabled) { + WfbngLinkJNI::nativeSetUseFec(env, clazz, instance, enabled); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseLdpc( + JNIEnv* env, jclass clazz, jlong instance, jint enabled) { + WfbngLinkJNI::nativeSetUseLdpc(env, clazz, instance, enabled); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseStbc( + JNIEnv* env, jclass clazz, jlong instance, jint enabled) { + WfbngLinkJNI::nativeSetUseStbc(env, clazz, instance, enabled); +} + +JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetFecThresholds( + JNIEnv* env, jclass clazz, jlong instance, jint lostTo5, jint recTo4, + jint recTo3, jint recTo2, jint recTo1) { + WfbngLinkJNI::nativeSetFecThresholds(env, clazz, instance, lostTo5, recTo4, recTo3, recTo2, recTo1); +} + +} // extern "C" \ No newline at end of file diff --git a/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.hpp b/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.hpp new file mode 100644 index 0000000..4eca62f --- /dev/null +++ b/app/wfbngrtl8812/src/main/cpp/WfbngLinkJNI.hpp @@ -0,0 +1,245 @@ +#ifndef WFBNG_LINK_JNI_HPP +#define WFBNG_LINK_JNI_HPP + +#include +#include +#include "WfbngLink.hpp" +#include "WfbConfiguration.hpp" + +/** + * @brief JNI wrapper layer for WfbngLink + * + * This class provides a clean separation between the JNI interface and the core + * WfbngLink functionality. It handles all JNI-specific concerns including: + * - Type conversions between Java and C++ + * - Error handling and exception management + * - Memory management for JNI objects + * - Thread safety for JNI calls + */ +class WfbngLinkJNI { +public: + /** + * @brief Initialize a new WfbngLink instance + * @param env JNI environment + * @param clazz Java class reference + * @param context Android context object + * @return Pointer to native WfbngLink instance as jlong + */ + static jlong nativeInitialize(JNIEnv* env, jclass clazz, jobject context); + + /** + * @brief Start WFB link operation + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param context Android context object + * @param wifiChannel WiFi channel number + * @param bandwidth Channel bandwidth + * @param fd Device file descriptor + */ + static void nativeRun(JNIEnv* env, jclass clazz, jlong instance, + jobject context, jint wifiChannel, jint bandwidth, jint fd); + + /** + * @brief Start adaptive link functionality + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + */ + static void nativeStartAdaptivelink(JNIEnv* env, jclass clazz, jlong instance); + + /** + * @brief Get current signal quality + * @param env JNI environment + * @param clazz Java class reference + * @return Signal quality value + */ + static jint nativeGetSignalQuality(JNIEnv* env, jclass clazz); + + /** + * @brief Stop WFB link operation + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param context Android context object + * @param fd Device file descriptor + */ + static void nativeStop(JNIEnv* env, jclass clazz, jlong instance, + jobject context, jint fd); + + /** + * @brief Callback for statistics updates + * @param env JNI environment + * @param clazz Java class reference + * @param callback Java callback object + * @param instance Native WfbngLink instance pointer + */ + static void nativeCallBack(JNIEnv* env, jclass clazz, + jobject callback, jlong instance); + + /** + * @brief Refresh encryption keys + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + */ + static void nativeRefreshKey(JNIEnv* env, jclass clazz, jlong instance); + + /** + * @brief Enable or disable adaptive link + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param enabled Enable/disable flag + */ + static void nativeSetAdaptiveLinkEnabled(JNIEnv* env, jclass clazz, + jlong instance, jboolean enabled); + + /** + * @brief Set transmission power + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param power TX power value + */ + static void nativeSetTxPower(JNIEnv* env, jclass clazz, + jlong instance, jint power); + + /** + * @brief Enable or disable FEC + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param enabled Enable/disable flag + */ + static void nativeSetUseFec(JNIEnv* env, jclass clazz, + jlong instance, jint enabled); + + /** + * @brief Enable or disable LDPC + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param enabled Enable/disable flag + */ + static void nativeSetUseLdpc(JNIEnv* env, jclass clazz, + jlong instance, jint enabled); + + /** + * @brief Enable or disable STBC + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param enabled Enable/disable flag + */ + static void nativeSetUseStbc(JNIEnv* env, jclass clazz, + jlong instance, jint enabled); + + /** + * @brief Set FEC threshold values + * @param env JNI environment + * @param clazz Java class reference + * @param instance Native WfbngLink instance pointer + * @param lostTo5 Lost packets threshold for FEC 5 + * @param recTo4 Recovered packets threshold for FEC 4 + * @param recTo3 Recovered packets threshold for FEC 3 + * @param recTo2 Recovered packets threshold for FEC 2 + * @param recTo1 Recovered packets threshold for FEC 1 + */ + static void nativeSetFecThresholds(JNIEnv* env, jclass clazz, jlong instance, + jint lostTo5, jint recTo4, jint recTo3, + jint recTo2, jint recTo1); + +private: + /** + * @brief Convert jlong pointer to native WfbngLink instance + * @param ptr JNI pointer as jlong + * @return Pointer to WfbngLink instance + */ + static WfbngLink* getNativeInstance(jlong ptr); + + /** + * @brief Convert native WfbngLink pointer to jlong + * @param instance Pointer to WfbngLink instance + * @return JNI pointer as jlong + */ + static jlong getJniPointer(WfbngLink* instance); + + /** + * @brief Create configuration from Android context + * @param env JNI environment + * @param context Android context object + * @return WfbConfiguration instance + */ + static WfbConfiguration createConfiguration(JNIEnv* env, jobject context); + + /** + * @brief Convert Java List to std::list + * @param env JNI environment + * @param javaList Java List object + * @return C++ list of integers + */ + static std::list javaListToStdList(JNIEnv* env, jobject javaList); + + /** + * @brief Handle JNI exceptions and log errors + * @param env JNI environment + * @param operation Name of the operation that failed + * @return true if exception was handled, false otherwise + */ + static bool handleJniException(JNIEnv* env, const char* operation); + + /** + * @brief Validate native instance pointer + * @param instance Pointer to validate + * @param operation Name of the operation for error logging + * @return true if valid, false otherwise + */ + static bool validateInstance(WfbngLink* instance, const char* operation); +}; + +// C-style JNI function declarations for export +extern "C" { + JNIEXPORT jlong JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeInitialize( + JNIEnv* env, jclass clazz, jobject context); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRun( + JNIEnv* env, jclass clazz, jlong instance, jobject context, + jint wifiChannel, jint bandwidth, jint fd); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStartAdaptivelink( + JNIEnv* env, jclass clazz, jlong instance); + + JNIEXPORT jint JNICALL Java_com_openipc_pixelpilot_UsbSerialService_nativeGetSignalQuality( + JNIEnv* env, jclass clazz); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeStop( + JNIEnv* env, jclass clazz, jlong instance, jobject context, jint fd); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeCallBack( + JNIEnv* env, jclass clazz, jobject callback, jlong instance); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeRefreshKey( + JNIEnv* env, jclass clazz, jlong instance); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetAdaptiveLinkEnabled( + JNIEnv* env, jclass clazz, jlong instance, jboolean enabled); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetTxPower( + JNIEnv* env, jclass clazz, jlong instance, jint power); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseFec( + JNIEnv* env, jclass clazz, jlong instance, jint enabled); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseLdpc( + JNIEnv* env, jclass clazz, jlong instance, jint enabled); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetUseStbc( + JNIEnv* env, jclass clazz, jlong instance, jint enabled); + + JNIEXPORT void JNICALL Java_com_openipc_wfbngrtl8812_WfbNgLink_nativeSetFecThresholds( + JNIEnv* env, jclass clazz, jlong instance, jint lostTo5, jint recTo4, + jint recTo3, jint recTo2, jint recTo1); +} + +#endif // WFBNG_LINK_JNI_HPP \ No newline at end of file