Nextion display for Photon WiFi setup

I’m using a nextion display that I have setup with my Photon and I’m using the docs to plan out a program using the WiFi features and SYSTEM THREAD ENABLED to serve up a list of SSIDs to the nextion display for the user to pick from and enter a password for to write back to the Photon… all my reading to date makes me believe this is possible.

Can anyone confirm this should be possible or Has anyone done this before and … any examples out there?

We use the Nextion with system thread enabled in our project an don’t have any problems with it. Yes you can do what you want. The Nextion isn’t without it’s quirks and unfortunately the community forums aren’t as friendly over there as they are here but you can do quite a bit with it if you spend the time.

2 Likes

Ah, good to know. and +1 on the nextion forums… a bit hostile at times between the company and customers… MUCH more friendly and professional in this community! Kudos to the Particle team!

I got my screens all built in nextion with keyboards for the ssid select and wifi password entry… painfully slow work… now I’m working the code to capture the wifi_scan_callback networks into a char array so I can loop through it and post it to the nextion.

Carpe Diem!
Brian

1 Like

I am making good progress, but need some help on a block. I have the nextion and photon talking to each other populating a list of SSID values on the Nextion so I can choose one and enter into a password setting process. My current block is my use of the wifi_scan_callback firmware. For my app when I go from the MAIN menu to the SETUP menu, I have a Wireless Setup option. When the user clicks this, the nextion goes to the WiFiSetup page and when I arrive on this page, the nextion does a pushcallback that I process to set a current_page variable. I use the page variable to kick off the wifi_scan_callback process to keep running while the user is on the page to continue to populate SSID’s to choose. My problem is I can’t seem to break out of the wifi_scan_callback. and my current_page variable, once set equal to 1, it never changes from that point. Is there a way to break out of this so I can accept other pushcallbacks from the nextion to update my current_page variable?
Here’s my current code:

// This #include statement was automatically added by the Particle IDE.
#include <ITEADLIB_Nextion.h>
#include "Particle.h"

SYSTEM_THREAD(ENABLED);
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL)); // selects the u.FL antenna

// Forward declarations
void wifi_scan_callback(WiFiAccessPoint* wap, void* data);
void wifiUpdateNextion(void);
void submittedPopCallback(void *ptr);
void MAINPushCallback(void *ptr);
void SetupWiFiPushCallback(void *ptr);
void SetupKeyPushCallback(void *ptr);
void SetupAltPushCallback(void *ptr);
void SETUPPushCallback(void *ptr);

// Nextion variables
int current_page = 0;
USARTSerial& nexSerial = Serial1;       
NexPage MAIN = NexPage(0, 0, "MAIN");
NexPage SetupWiFi = NexPage(1, 0, "SetupWiFi");
NexPage SetupKey = NexPage(2, 0, "SetupKey");
NexPage SetupAlt = NexPage(3, 0, "SetupAlt");
NexPage SETUP = NexPage(5, 0, "SETUP");
NexText SSID1 = NexText(1, 3, "SSID1");
NexText SSID2 = NexText(1, 4, "SSID2");
NexText SSID3 = NexText(1, 5, "SSID3");
NexText SSID4 = NexText(1, 6, "SSID4");
NexText SSID5 = NexText(1, 7, "SSID5");
NexText SSIDinput = NexText(1, 8, "SSIDinput");
NexText SSIDpassword = NexText(1, 11, "SSIDpassword");
NexButton submitted = NexButton(1, 2, "submitted");  /* submitted button for wifi ssid */

struct networks
{
    char ssid[33];
    int security;
    int channel;
    int rssi;
};

networks wifi[5]; // an array of struct "networks"
char ssidselected[33];
char ssidselectedpassword[33];
char ssidsubmittedtxt[11];
int WifiSignalStrength(int);
int wifi_strength; // -128 is weak to -1 is strong signal strength. A 1 means Wi-Fi chip error and 2 means a time-out error.
int wifi_bars; // a calculation of the signal strength -128 to -97 = 1bar; <-97 && >-64 = 2bar; <-64 & >-32 = 3bar; <-32 && > 0 = 4bar;
int loopcounter = 0;
int result_count;
int callback_count;

NexTouch *nex_listen_list[] = {&submitted, &MAIN, &SetupWiFi, &SetupKey, &SetupAlt, &SETUP, NULL};

void setup()
{

    Serial.begin(9600);
    Particle.syncTime();
    Time.zone(-6); //set to CST
    delay(3000); // gives me time to crank up PuTTY
    wifi_strength = WiFi.RSSI();
    Serial.printf("WiFi Strength: %d\r\n", wifi_strength);
    Serial.printf("*************************************** P R O G R A M    B E G I N ************************************************\r\n");
    Serial.println(Time.timeStr());
    Serial.println(Time.format(TIME_FORMAT_ISO8601_FULL));

    /* Nextion Display code ********************************************************************************************************/
    /* Set the baudrate which is for debug and communicate with Nextion screen. */
    nexInit();/* Register the pop event callback function of the current text component. */
    submitted.attachPop(submittedPopCallback);/* Register the push event callback function of the current Mode component. */
    MAIN.attachPush(MAINPushCallback);
    SetupWiFi.attachPush(SetupWiFiPushCallback);
    SetupKey.attachPush(SetupKeyPushCallback);
    SetupAlt.attachPush(SetupAltPushCallback);
    SETUP.attachPush(SETUPPushCallback);
    /* END Nextion Display code ****************************************************************************************************/
    
    dbSerialPrintln("setup done");
    Serial.println("Finishing SETUP in 1 second, awaiting command!");
    delay(1000); // just 1 seconds to chill...
}

void loop()
{
    nexLoop(nex_listen_list);

    if(current_page == 1)
    {
        loopcounter += 1;
        callback_count=0;
        result_count = WiFi.scan(wifi_scan_callback);
        wifiUpdateNextion();
        Serial.printf("Loop count: %d   |  %d SSID scan trys:\r\n", result_count, loopcounter);
    }
    
    nexLoop(nex_listen_list);

}

void MAINPushCallback(void *ptr) {

   Serial.printf("Nextion is on MAIN menu\r\n");
   current_page=0;
   // any other code important to the user goes here

}

void SetupWiFiPushCallback(void *ptr) {

    Serial.printf("Nextion is on SetupWiFi menu\r\n");
    current_page=1;
   // any other code important to the user goes here
}

void wifi_scan_callback(WiFiAccessPoint* wap, void* data)
{
    callback_count += 1;
    WiFiAccessPoint& ap = *wap;
    //Serial.printf("the count of SSIDs is: %d %d\r\n", callback_count, current_page);

    strcpy(wifi[callback_count-1].ssid, ap.ssid);
    wifi[callback_count-1].security = ap.security;
    wifi[callback_count-1].channel = ap.channel;
    wifi[callback_count-1].rssi = ap.rssi;
}

void wifiUpdateNextion(void)
{
    for (int i=0; i<callback_count; i++)
    {
        char nextionSSID[6] = "";
        char nextionSSIDnum[2];
        itoa(i+1, nextionSSIDnum, 10);
        strcat(nextionSSID, "SSID");
        strcat(nextionSSID, nextionSSIDnum);
        Serial.printf("SSID: %s  Security: %d  Channel: %d  RSSI: %d  nextionSSID: %s page:%d\r\n", wifi[i].ssid, wifi[i].security, wifi[i].channel, wifi[i].rssi, nextionSSID, current_page);
 
        if(i==0){SSID1.setText(wifi[i].ssid);}
        if(i==1){SSID2.setText(wifi[i].ssid);}
        if(i==2){SSID3.setText(wifi[i].ssid);}
        if(i==3){SSID4.setText(wifi[i].ssid);}
        if(i==4){SSID5.setText(wifi[i].ssid);}
    }
    Serial.printf("******************** End of SSID list ************************** page:%d\r\n", current_page);
}

void SetupKeyPushCallback(void *ptr) {

   Serial.printf("Nextion is on SetupKey main keyboard character page\r\n");
   current_page=2;
   // any other code important to the user arriving at SetupKey

}

void SetupAltPushCallback(void *ptr) {

   Serial.printf("Nextion is on SetupAlt keyboard character page\r\n");
   current_page=3;
   // any other code important to the user arriving at SetupAlt

}

void SETUPPushCallback(void *ptr) {

  current_page=5;
  Serial.printf("Nextion is on SETUP menu - page: %d\r\n", current_page);

   // any other code important to the user arriving at SETUP

}

void submittedPopCallback(void *ptr)   /* when ssid and password are set in nextion AND the submitted button == "Submitted" then this runs */
{
    memset(ssidsubmittedtxt, 0, sizeof(ssidsubmittedtxt));
    submitted.getText(ssidsubmittedtxt, sizeof(ssidsubmittedtxt));

    if(strcmp(ssidsubmittedtxt, "SUBMITTED") == 0)
    {
        memset(ssidselected, 0, sizeof(ssidselected));
        SSIDinput.getText(ssidselected, sizeof(ssidselected));
        
        memset(ssidselectedpassword, 0, sizeof(ssidselectedpassword));
        SSIDpassword.getText(ssidselectedpassword, sizeof(ssidselectedpassword));
    
        Serial.printf("SSIDPopCallback - The selected SSID from nextion is: %s and buffer length is: %d password is:%s\r\n", ssidselected, strlen(ssidselected), ssidselectedpassword);

// ** THIS PROCEDURE UNDER CONSTRUCTION RIGHT NOW...

    }

}
1 Like

The WiFi.scan(callback) overload is meant for a cases where you expect an “unlimited” number of available networks.
In your case you are expecting only up to 5 and if your device happens to see more than that your code will most likely (hopefully) crash - if it doesn’t crash, it will keep running with corrupt data in some of your other variables.
To deal with that, you should either opt for

  • WiFi.scan(wifi, 5);, or
  • add some limiting logic in your callback to prevent callback_count from exceeding your limit of networks your code can cope with, or
  • store the found networks in some dynamically allocated space.

However, the lack of (documented) means to terminate the WiFi.scan(callback) is worth opening a GitHub issue IMO. Either the callback function should return a boolean whether or not the scanning should continue or the *data parameter should offer some (documented) means to signal back the desire to break or continue.

But your actual problem is not the fact that you can’t break out of the scan, but that you permanently re-initiate the scan after it has finished its first round.
To prevent that you need to change current_page to something other than 1 (e.g. -1) to indicate the job has been doen and shouldn’t be done again, or you add an extra “one-shot” flag (e.g. previousPage != current_page) for the same reason.

Thank you @ScruffR, I would like to move the scan out of loop() and into its own function and call it to keep iterating while I’m on my ssid page on nextion… and keep filling up my 5 slots…(I may expand this to 10). i’ll think through what you propose and come up with an approach.

I was also wondering about the *data variable as well. The docs say it can be used for OOP, but I could not find any examples of how it could be used, still researching it.

thank you for your help!
Brian

There actually is a sample in the docs right there

// EXAMPLE - class to find the strongest AP
class FindStrongestSSID
{
    char strongest_ssid[33];
    int strongest_rssi;

    // This is the callback passed to WiFi.scan()
    // It makes the call on the `self` instance - to go from a static
    // member function to an instance member function.
    static void handle_ap(WiFiAccessPoint* wap, FindStrongestSSID* self)
    {
        self->next(*wap);
    }

    // determine if this AP is stronger than the strongest seen so far
    void next(WiFiAccessPoint& ap)
    {
        if ((ap.rssi < 0) && (ap.rssi > strongest_rssi)) {
            strongest_rssi = ap.rssi;
            strcpy(strongest_ssid, ap.ssid);
        }
    }

public:

    /**
     * Scan Wi-Fi Access Points and retrieve the strongest one.
     */
    const char* scan()
    {
        // initialize data
        strongest_rssi = -128;
        strongest_ssid[0] = 0;
        // perform the scan
        WiFi.scan(handle_ap, this);
        return strongest_ssid;
    }
};

// Now use the class
FindStrongestSSID strongestFinder;
const char* ssid = strongestFinder.scan();

This uses the "arbitrary" pointer (void*) to pass a pointer to the object itself so that the callback can in turn call back into the owning class for a method therein.
Granted, it's a not very intuitive sample but it does illustrate how the extra parameter can be used. But this doesn't offer any way to bail out of the scanning process.

I can't quite follow that :blush:
I don't think WiFi.scan() can run asynchronous to your loop() no matter whether it's inside of loop() or not - unless you stuff it into its own RTOS thread which may pose a new set of issues.
But the Nextion HMI can still run its own stuff independently.
You can run some "simple" tasks on the display itself without needing to communicate with the Photon at all. I don't know your complete project's design, but if you could manage to untangle and reduce interaction between display and controller to a "thin" interface you could have some additional degree of asynchronicity.