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
1017static NimBLEServer * g_bleServer = nullptr ;
1118static 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