diff --git a/.gitignore b/.gitignore index a0ad5f6ea9..30750635ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,38 @@ -.direnv -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch -out/ -.direnv/ -.DS_Store -.vscode/settings.json -.vscode/extensions.json -.idea -cmake-* -.cache -.ccls -compile_commands.json -.venv/ +``` +# Compiled and build artifacts +*.o +*.obj +*.a +*.so +*.dll +*.exe +*.out + +# Dependencies +.pycache/ +__pycache__/ +node_modules/ venv/ -platformio.local.ini +.venv/ +.env +.env.local +*.env.* + +# Build directories +build/ +dist/ +target/ + +# Logs and temp files +*.log +*.tmp +*.swp + +# Editors +.vscode/ +.idea/ + +# System files +.DS_Store +Thumbs.db +``` \ No newline at end of file diff --git a/examples/companion_radio/MqttConfig.h b/examples/companion_radio/MqttConfig.h new file mode 100644 index 0000000000..13c5629e32 --- /dev/null +++ b/examples/companion_radio/MqttConfig.h @@ -0,0 +1,37 @@ +#pragma once + +// Configuration file for WiFi and MQTT settings +// Edit this file before flashing the device + +#ifndef MQTT_CONFIG_H +#define MQTT_CONFIG_H + +// ============= WiFi Configuration ============= +// Set to your WiFi network credentials +#define WIFI_SSID "your_wifi_ssid" +#define WIFI_PWD "your_wifi_password" + +// ============= MQTT Configuration ============= +// Enable or disable MQTT feature (true/false) +#define MQTT_ENABLED false + +// MQTT Broker settings +#define MQTT_SERVER "mqtt.example.com" +#define MQTT_PORT 1883 + +// MQTT Authentication (leave empty if not required) +#define MQTT_USER "" +#define MQTT_PASSWORD "" + +// MQTT Topic prefix (e.g., "meshcore/device123") +// Messages will be published to: +// - {prefix}/messages/incoming +// - {prefix}/messages/outgoing +// - {prefix}/messages/channel +#define MQTT_TOPIC_PREFIX "meshcore/companion_radio" + +// ============= Advanced Settings ============= +// Debug logging for MQTT (true/false) +#define MQTT_DEBUG_LOGGING false + +#endif // MQTT_CONFIG_H diff --git a/src/helpers/esp32/MqttClient.cpp b/src/helpers/esp32/MqttClient.cpp new file mode 100644 index 0000000000..ce7a7cee42 --- /dev/null +++ b/src/helpers/esp32/MqttClient.cpp @@ -0,0 +1,186 @@ +#include "MqttClient.h" +#include + +MqttClient mqtt_client; + +MqttClient::MqttClient() : mqttClient(wifiClient) { + connected = false; + lastReconnectAttempt = 0; + reconnectDelay = 5000; + memset(&config, 0, sizeof(config)); + memset(clientId, 0, sizeof(clientId)); +} + +void MqttClient::begin(const MqttConfig& cfg, const char* nodeId) { + config = cfg; + + if (!config.enabled) { + MQTT_DEBUG_PRINTLN("MQTT is disabled in configuration"); + return; + } + + // Generate client ID from node ID + snprintf(clientId, sizeof(clientId), "meshcore-%s", nodeId); + + // Configure MQTT client + mqttClient.setServer(config.mqtt_server, config.mqtt_port); + mqttClient.setBufferSize(2048); // Increase buffer for larger messages + + // Start WiFi connection + MQTT_DEBUG_PRINT("Connecting to WiFi: %s ... ", config.wifi_ssid); + WiFi.begin(config.wifi_ssid, config.wifi_password); + + int attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < 30) { + delay(500); + MQTT_DEBUG_PRINT("."); + attempts++; + } + + if (WiFi.status() == WL_CONNECTED) { + MQTT_DEBUG_PRINTLN("\nWiFi connected successfully"); + MQTT_DEBUG_PRINT("IP address: "); + MQTT_DEBUG_PRINTLN(WiFi.localIP().toString()); + } else { + MQTT_DEBUG_PRINTLN("\nWiFi connection failed!"); + return; + } + + connected = true; + reconnect(); +} + +void MqttClient::reconnect() { + if (!connected || mqttClient.connected()) { + return; + } + + unsigned long now = millis(); + if (now - lastReconnectAttempt < reconnectDelay) { + return; + } + + lastReconnectAttempt = now; + + MQTT_DEBUG_PRINT("Attempting MQTT connection to %s:%d ... ", config.mqtt_server, config.mqtt_port); + + // Attempt to connect + bool result; + if (strlen(config.mqtt_user) > 0) { + result = mqttClient.connect(clientId, config.mqtt_user, config.mqtt_password); + } else { + result = mqttClient.connect(clientId); + } + + if (result) { + MQTT_DEBUG_PRINTLN("connected!"); + reconnectDelay = 5000; // Reset delay on success + } else { + MQTT_DEBUG_PRINT("failed, rc=%d ", mqttClient.state()); + MQTT_DEBUG_PRINT("retrying in %d seconds\n", reconnectDelay / 1000); + reconnectDelay = min(reconnectDelay * 2, 60000UL); // Exponential backoff, max 60s + } +} + +void MqttClient::loop() { + if (!connected || !config.enabled) { + return; + } + + // Maintain MQTT connection + if (!mqttClient.connected()) { + reconnect(); + return; + } + + mqttClient.loop(); + + // Check WiFi connection + if (WiFi.status() != WL_CONNECTED) { + MQTT_DEBUG_PRINTLN("WiFi disconnected, attempting to reconnect..."); + connected = false; + WiFi.begin(config.wifi_ssid, config.wifi_password); + } +} + +void MqttClient::publishMessage(const char* topic, const char* payload) { + if (!connected || !mqttClient.connected()) { + MQTT_DEBUG_PRINTLN("Cannot publish: not connected to MQTT broker"); + return; + } + + MQTT_DEBUG_PRINT("Publishing to topic: %s\n", topic); + MQTT_DEBUG_PRINT("Payload: %s\n", payload); + + if (mqttClient.publish(topic, payload, false)) { + MQTT_DEBUG_PRINTLN("Published successfully"); + } else { + MQTT_DEBUG_PRINTLN("Publish failed"); + } +} + +void MqttClient::onIncomingMessage(const char* fromName, const char* fromPubKeyPrefix, const char* text, float snr, float rssi) { + if (!config.enabled) return; + + StaticJsonDocument<512> doc; + doc["type"] = "incoming_message"; + doc["from_name"] = fromName; + doc["from_pubkey_prefix"] = fromPubKeyPrefix; + doc["text"] = text; + doc["snr"] = snr; + doc["rssi"] = rssi; + doc["timestamp"] = millis(); + + char jsonBuffer[1024]; + serializeJson(doc, jsonBuffer); + + char topic[256]; + snprintf(topic, sizeof(topic), "%s/messages/incoming", config.mqtt_topic_prefix); + publishMessage(topic, jsonBuffer); +} + +void MqttClient::onOutgoingMessage(const char* toName, const char* toPubKeyPrefix, const char* text) { + if (!config.enabled) return; + + StaticJsonDocument<512> doc; + doc["type"] = "outgoing_message"; + doc["to_name"] = toName; + doc["to_pubkey_prefix"] = toPubKeyPrefix; + doc["text"] = text; + doc["timestamp"] = millis(); + + char jsonBuffer[1024]; + serializeJson(doc, jsonBuffer); + + char topic[256]; + snprintf(topic, sizeof(topic), "%s/messages/outgoing", config.mqtt_topic_prefix); + publishMessage(topic, jsonBuffer); +} + +void MqttClient::onChannelMessage(const char* channelName, const char* senderName, const char* text, float snr, float rssi) { + if (!config.enabled) return; + + StaticJsonDocument<512> doc; + doc["type"] = "channel_message"; + doc["channel"] = channelName; + doc["sender_name"] = senderName; + doc["text"] = text; + doc["snr"] = snr; + doc["rssi"] = rssi; + doc["timestamp"] = millis(); + + char jsonBuffer[1024]; + serializeJson(doc, jsonBuffer); + + char topic[256]; + snprintf(topic, sizeof(topic), "%s/messages/channel", config.mqtt_topic_prefix); + publishMessage(topic, jsonBuffer); +} + +void MqttClient::disconnect() { + if (mqttClient.connected()) { + mqttClient.disconnect(); + } + connected = false; + MQTT_DEBUG_PRINTLN("MQTT disconnected"); +} diff --git a/src/helpers/esp32/MqttClient.h b/src/helpers/esp32/MqttClient.h new file mode 100644 index 0000000000..c28f93f58f --- /dev/null +++ b/src/helpers/esp32/MqttClient.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +// Configuration structure for WiFi and MQTT +struct MqttConfig { + char wifi_ssid[33]; // WiFi SSID (max 32 chars + null) + char wifi_password[65]; // WiFi password (max 64 chars + null) + char mqtt_server[129]; // MQTT broker address (max 128 chars + null) + uint16_t mqtt_port; // MQTT broker port + char mqtt_user[65]; // MQTT username (optional, max 64 chars + null) + char mqtt_password[65]; // MQTT password (optional, max 64 chars + null) + char mqtt_topic_prefix[65]; // Base topic prefix (max 64 chars + null) + bool enabled; // Enable/disable MQTT feature +}; + +class MqttClient { +private: + WiFiClient wifiClient; + PubSubClient mqttClient; + MqttConfig config; + bool connected; + unsigned long lastReconnectAttempt; + unsigned long reconnectDelay; + char clientId[32]; + + void publishMessage(const char* topic, const char* payload); + void reconnect(); + +public: + MqttClient(); + + void begin(const MqttConfig& cfg, const char* nodeId); + void loop(); + bool isConnected() const { return connected && mqttClient.connected(); } + + // Message publishing methods + void onIncomingMessage(const char* fromName, const char* fromPubKeyPrefix, const char* text, float snr, float rssi); + void onOutgoingMessage(const char* toName, const char* toPubKeyPrefix, const char* text); + void onChannelMessage(const char* channelName, const char* senderName, const char* text, float snr, float rssi); + + // Connection management + void disconnect(); +}; + +extern MqttClient mqtt_client; + +#if MQTT_DEBUG_LOGGING && ARDUINO + #include + #define MQTT_DEBUG_PRINT(F, ...) Serial.printf("MQTT: " F, ##__VA_ARGS__) + #define MQTT_DEBUG_PRINTLN(F, ...) Serial.printf("MQTT: " F "\n", ##__VA_ARGS__) +#else + #define MQTT_DEBUG_PRINT(...) {} + #define MQTT_DEBUG_PRINTLN(...) {} +#endif