Skip to content

Commit 40e94d8

Browse files
authored
Added MQTT support. (#5)
1 parent 107f83c commit 40e94d8

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed

websocket/lvgl_elk.h

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
// For BLE
77
#include <NimBLEDevice.h>
88

9+
#include <WiFi.h> // WiFi library that also provides WiFiClient
10+
#include <PubSubClient.h> // For MQTT
11+
12+
// Global WiFiClient + PubSubClient
13+
static WiFiClient g_wifiClient;
14+
static PubSubClient g_mqttClient(g_wifiClient);
15+
916
// NimBLE globals
1017
static NimBLEServer* g_bleServer = nullptr;
1118
static NimBLECharacteristic* g_bleChar = nullptr;
@@ -16,6 +23,11 @@ extern "C" {
1623
#include "elk.h"
1724
}
1825

26+
// For storing a JavaScript callback to handle incoming messages
27+
static char g_mqttCallbackName[32]; // Big enough for a function name
28+
static unsigned long lastMqttReconnectAttempt = 0;
29+
static unsigned long lastWiFiReconnectAttempt = 0;
30+
1931
/******************************************************************************
2032
* A) Elk Memory + Global Instances
2133
******************************************************************************/
@@ -2615,6 +2627,210 @@ static jsval_t js_ble_write(struct js *js, jsval_t *args, int nargs) {
26152627
return js_mktrue();
26162628
}
26172629

2630+
// MQTT message callback from PubSubClient
2631+
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
2632+
Serial.printf("[MQTT] Message arrived on topic '%s'\n", topic);
2633+
2634+
// If we have a non-empty callback name, build a snippet and eval
2635+
if (g_mqttCallbackName[0] != '\0') {
2636+
// Convert char* topic and payload to a C++ string
2637+
String topicStr(topic);
2638+
String msgStr;
2639+
msgStr.reserve(length);
2640+
for (unsigned int i = 0; i < length; i++) {
2641+
msgStr += (char) payload[i];
2642+
}
2643+
2644+
// Build snippet: myCallback('topicString','payloadString')
2645+
char snippet[512];
2646+
// Use %s for the function name, plus single quotes around the data
2647+
snprintf(snippet, sizeof(snippet),
2648+
"%s('%s','%s');",
2649+
g_mqttCallbackName,
2650+
topicStr.c_str(),
2651+
msgStr.c_str());
2652+
2653+
Serial.printf("[MQTT] Evaluating snippet: %s\n", snippet);
2654+
2655+
// Evaluate snippet
2656+
jsval_t res = js_eval(js, snippet, strlen(snippet));
2657+
// Optionally check if res is error
2658+
if (js_type(res) == JS_ERR) {
2659+
Serial.print("[MQTT] Callback error: ");
2660+
Serial.println(js_str(js, res));
2661+
}
2662+
}
2663+
}
2664+
2665+
//------------------------------------------
2666+
// JavaScript-exposed bridging functions
2667+
//------------------------------------------
2668+
2669+
// mqtt_init(broker, port)
2670+
static jsval_t js_mqtt_init(struct js *js, jsval_t *args, int nargs) {
2671+
if(nargs < 2) return js_mkfalse();
2672+
const char* broker = js_str(js, args[0]);
2673+
int port = (int)js_getnum(args[1]);
2674+
2675+
if(!broker || port <= 0) return js_mkfalse();
2676+
2677+
g_mqttClient.setServer(broker, port);
2678+
g_mqttClient.setCallback(onMqttMessage);
2679+
Serial.printf("[MQTT] init => broker=%s port=%d\n", broker, port);
2680+
2681+
return js_mktrue();
2682+
}
2683+
2684+
// mqtt_connect(clientID, user, pass)
2685+
static jsval_t js_mqtt_connect(struct js *js, jsval_t *args, int nargs) {
2686+
if(nargs < 1) return js_mkfalse();
2687+
const char* clientID = js_str(js, args[0]);
2688+
const char* user = (nargs >= 2) ? js_str(js, args[1]) : nullptr;
2689+
const char* pass = (nargs >= 3) ? js_str(js, args[2]) : nullptr;
2690+
2691+
bool ok = false;
2692+
if(user && pass && strlen(user) && strlen(pass)) {
2693+
ok = g_mqttClient.connect(clientID, user, pass);
2694+
} else {
2695+
ok = g_mqttClient.connect(clientID);
2696+
}
2697+
2698+
if(ok) {
2699+
Serial.println("[MQTT] Connected successfully");
2700+
return js_mktrue();
2701+
} else {
2702+
Serial.printf("[MQTT] Connect failed, rc=%d\n", g_mqttClient.state());
2703+
return js_mkfalse();
2704+
}
2705+
}
2706+
2707+
// mqtt_publish(topic, message)
2708+
static jsval_t js_mqtt_publish(struct js *js, jsval_t *args, int nargs) {
2709+
if(nargs < 2) return js_mkfalse();
2710+
const char* topic = js_str(js, args[0]);
2711+
const char* message = js_str(js, args[1]);
2712+
if(!topic || !message) return js_mkfalse();
2713+
2714+
bool ok = g_mqttClient.publish(topic, message);
2715+
return ok ? js_mktrue() : js_mkfalse();
2716+
}
2717+
2718+
// mqtt_subscribe(topic)
2719+
static jsval_t js_mqtt_subscribe(struct js *js, jsval_t *args, int nargs) {
2720+
if(nargs < 1) return js_mkfalse();
2721+
const char* topic = js_str(js, args[0]);
2722+
if(!topic) return js_mkfalse();
2723+
2724+
bool ok = g_mqttClient.subscribe(topic);
2725+
Serial.printf("[MQTT] Subscribed to '%s'? => %s\n", topic, ok ? "OK" : "FAIL");
2726+
return ok ? js_mktrue() : js_mkfalse();
2727+
}
2728+
2729+
// mqtt_loop()
2730+
static jsval_t js_mqtt_loop(struct js *js, jsval_t *args, int nargs) {
2731+
g_mqttClient.loop();
2732+
return js_mknull();
2733+
}
2734+
2735+
// mqtt_on_message("myCallback")
2736+
static jsval_t js_mqtt_on_message(struct js *js, jsval_t *args, int nargs) {
2737+
if(nargs < 1) return js_mkfalse();
2738+
2739+
// Check if user passed a string naming the function
2740+
size_t len = 0;
2741+
char *str = js_getstr(js, args[0], &len);
2742+
if(!str || len == 0 || len >= sizeof(g_mqttCallbackName)) {
2743+
return js_mkfalse();
2744+
}
2745+
2746+
// Copy that name to our global
2747+
memset(g_mqttCallbackName, 0, sizeof(g_mqttCallbackName));
2748+
memcpy(g_mqttCallbackName, str, len); // not zero-terminated by default
2749+
g_mqttCallbackName[len] = '\0';
2750+
2751+
Serial.print("[MQTT] JS callback name set to: ");
2752+
Serial.println(g_mqttCallbackName);
2753+
return js_mktrue();
2754+
}
2755+
2756+
// This function tries to connect to your MQTT broker
2757+
bool doMqttConnect() {
2758+
// Example: If you stored broker/port in global variables or from prior init:
2759+
// extern const char* g_mqttBroker;
2760+
// extern int g_mqttPort;
2761+
// g_mqttClient.setServer(g_mqttBroker, g_mqttPort);
2762+
2763+
Serial.println("[MQTT] Checking broker connection...");
2764+
2765+
// Attempt to connect with e.g. a default clientID (or user/pass if needed)
2766+
bool ok = g_mqttClient.connect("WebScreenClient");
2767+
if(!ok) {
2768+
// Print the reason code: g_mqttClient.state()
2769+
Serial.printf("[MQTT] Connect fail, rc=%d\n", g_mqttClient.state());
2770+
return false;
2771+
}
2772+
2773+
// If connected, subscribe to any default topic if you want:
2774+
// g_mqttClient.subscribe("some/topic");
2775+
2776+
Serial.println("[MQTT] Connected successfully");
2777+
return true;
2778+
}
2779+
2780+
// This function tries to reconnect Wi-Fi if Wi-Fi is down
2781+
bool doWiFiReconnect() {
2782+
Serial.println("[WiFi] Checking connection...");
2783+
2784+
// If you have an SSID/pass stored
2785+
// extern String g_ssid;
2786+
// extern String g_pass;
2787+
// WiFi.begin(g_ssid.c_str(), g_pass.c_str());
2788+
2789+
// We'll do a quick wait for up to ~3 seconds, just for example:
2790+
// (Tune to your needs)
2791+
for (int i = 0; i < 15; i++) {
2792+
if (WiFi.status() == WL_CONNECTED) {
2793+
Serial.print("[WiFi] Reconnected. IP=");
2794+
Serial.println(WiFi.localIP());
2795+
return true;
2796+
}
2797+
delay(200);
2798+
}
2799+
Serial.println("[WiFi] Still not connected");
2800+
return false;
2801+
}
2802+
2803+
// Call this regularly to maintain Wi-Fi + MQTT
2804+
void wifiMqttMaintainLoop() {
2805+
// 1) Check if Wi-Fi is alive
2806+
if (WiFi.status() != WL_CONNECTED) {
2807+
unsigned long now = millis();
2808+
// Try reconnect every 10 seconds
2809+
if(now - lastWiFiReconnectAttempt > 10000) {
2810+
lastWiFiReconnectAttempt = now;
2811+
Serial.println("[WiFi] Connection lost, attempting recon...");
2812+
doWiFiReconnect();
2813+
}
2814+
// If Wi-Fi is still down, we skip MQTT
2815+
return;
2816+
}
2817+
2818+
// 2) If Wi-Fi is up, handle MQTT
2819+
if(!g_mqttClient.connected()) {
2820+
unsigned long now = millis();
2821+
if(now - lastMqttReconnectAttempt > 10000) {
2822+
lastMqttReconnectAttempt = now;
2823+
Serial.println("[MQTT] Lost MQTT, trying reconnect...");
2824+
if(doMqttConnect()) {
2825+
lastMqttReconnectAttempt = 0;
2826+
}
2827+
}
2828+
}
2829+
2830+
// If connected, let PubSubClient process inbound/outbound messages
2831+
g_mqttClient.loop();
2832+
}
2833+
26182834
/******************************************************************************
26192835
* I) Register All JS Functions
26202836
******************************************************************************/
@@ -2798,6 +3014,14 @@ void register_js_functions() {
27983014
// ---------- BUTTON bridging
27993015
js_set(js, global, "lv_btn_create", js_mkfun(js_lv_btn_create));
28003016
js_set(js, global, "lv_button_set_text", js_mkfun(js_lv_button_set_text));
3017+
3018+
// MQTT bridging
3019+
js_set(js, global, "mqtt_init", js_mkfun(js_mqtt_init));
3020+
js_set(js, global, "mqtt_connect", js_mkfun(js_mqtt_connect));
3021+
js_set(js, global, "mqtt_publish", js_mkfun(js_mqtt_publish));
3022+
js_set(js, global, "mqtt_subscribe", js_mkfun(js_mqtt_subscribe));
3023+
js_set(js, global, "mqtt_loop", js_mkfun(js_mqtt_loop));
3024+
js_set(js, global, "mqtt_on_message", js_mkfun(js_mqtt_on_message));
28013025
}
28023026

28033027
//------------------------------------------------------------------------------
@@ -2826,6 +3050,8 @@ static void elk_task(void *pvParam) {
28263050
// 4) Now keep running lv_timer_handler() or your lvgl_loop
28273051
// so that the UI remains active
28283052
for(;;) {
3053+
// Check Wi-Fi and MQTT, handle reconnections
3054+
wifiMqttMaintainLoop();
28293055
lv_timer_handler();
28303056
delay(5);
28313057
// or lvgl_loop() if you prefer

0 commit comments

Comments
 (0)