Change SECRET_SSID and SECRET_OPTIONAL_PASS through Serial Interface

Hello,

Does anyone know of a way to change these through serial write commands for an Arduino that's running in realtime? In other words, I'm looking to update the Wifi SSID and the Wifi Password for an Andruino Opta that's already running instead of having to recompile through OTA or USB connection.

I'll be doing it through the Modbus interface by reading holding registers through an HMI that allows data input.

const char SSID[]     = SECRET_SSID;    // Network SSID (name)
const char PASS[]     = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)

Thanks!

See Serial input basics - updated

I should probably redo this post...

I'm reading the data over serial just fine. The question is how to change SECRET_SSID and SECRETE_OPTIONAL_PASS without having to recompile, if possible.

In 'thingProperties.h' there's a call to

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

I'm wondering if there's a way to modify code in setup() and loop() that allows the device to change these based on the serial data over that it's reading.

It would have been clearer what you were trying to do if you had told us that you were using the Cloud

You might begin by looking at the code for any of the multitude of "WiFi modems" on Github (and other places) and seeing how they do it.

You can't change SECRET_SSID, because that's a macro

#define SECRET_SSID "ThisIsMyAP"

and the fact that there was a thing named SECRET_SSID disappeared at compile time. Sounds like you want to change the value of the variable SSID, since that is what is actually used in the code, and you change a variable -- with some caveats, especially with C++

Effectively, the global variable statement is

const char SSID[11] = "ThisIsMyAP";

The compiler counts the number of characters in the literal string and adds one for the terminating NUL. Turns out you can't simply reassign an array, though. You're better off declaring it as a pointer, which should work identically unless you somewhere did sizeof(SSID).

#define SECRET_SSID "ThisIsMyAP"
const char *SSID = SECRET_SSID;
String ssidOverride;

void setup() {
  Serial.begin(115200);
  Serial.print("before: ");
  Serial.println(SSID);

  ssidOverride = "Read from Serial";
  SSID = ssidOverride.c_str();

  Serial.print("after: ");
  Serial.println(SSID);
}

void loop() {}

It's easier to use a String to hold the override value, since it will do some dynamic memory management for you. Then if you actually read something, you can change the pointer value.

I've done more work on this since your follow-up. I see the logic in the pointer, however,

const char *SSID = SECRET_SSID;

would be a modification to the auto-generated 'thingProperties.h' file from the Cloud editor. If I was using the IDE the approach would be different, using commands like,

WiFi.begin(SSID, PASS);
// do some things to change SSID over Modbus to update a new SSID
WiFi.disconnect();
WiFi.begin(SSID, PASS);

But in moving off the IDE so as to utilize the OTA and cloud services, the traditional .ino files need to be stored as a Thing with all edits and compiling being done through the Cloud. Which then presents the problem of modifying auto-generated .h files as noted above...

The file 'thingProperties.h' has

#include <Arduino_ConnectionHandler.h>

which makes reference to 'WiFi.h' when looking at the source code on Github.

I'm easily moving character I/O between the OPTA and the external HMI with Modbus. But ultimately, I need to change the SSID and PASS variables through the HMI display (I want to be able to install a product in the field and configure site WiFi without having to bring a laptop).

I've tried this:

char wifiAddressConfig[32];

void setup() {

  // ...

}
void loop() {

  // ...

  ModbusRTUClient.requestFrom(2, HOLDING_REGISTERS, 100, 16);
  while( ModbusRTUClient.available() ) {
    // Wifi SSID is 32 characters = max of 2x16 Holding Registers
    uint16_t wifiAddressRead = ModbusRTUClient.read();
    uint8_t wifiAddressReadW1 = wifiAddressRead;
    uint8_t wifiAddressReadW2 = wifiAddressRead >> 8;
    wifiAddressConfig[2*i] = (char) wifiAddressReadW1;
    wifiAddressConfig[2*i+1] = (char) wifiAddressReadW2;
    i=i+1;
  }
  Serial.println("changed wifi address");
  Serial.println(wifiAddressConfig);

  // disconnect from existing network and connect to new network
  WiFi.disconnect();

  // ...
  
}

Interestingly, this does break the current Wifi connection and returns error code '6' to the Serial console. However, the connection reestablishes itself under the original SSID compiled from the Cloud.

If I add to the code above,

SSID = wifiAddressConfig;
WiFi.disconnect();

I get a compile error,

error: assignment of read-only variable 'SSID'

Or try adding to loop() something crazy like,

ArduinoCloud.begin(WiFiConnectionHandler(wifiAddressConfig, PASS));

hoping that maybe this will update the cloud connection without thinking through consequences of all the header files involved here... I get a very detailed compiler error,

error: cannot bind non-const lvalue reference of type 'ConnectionHandler&' to an rvalue of type 'ConnectionHandler'

I found a post on Github for a request to shut down the ArduinoCloud service through something like

ArduinoCloud.end()

but no work has been done to my knowledge or searching.

Clearly SSID must be saved to memory. There should be a way to overwrite it?

Thank you for any help,

Chris

SSID is an array of const char; remove the const and copy the NUL-terminated bytes over

char SSID[] = "ThisIsMyAP";
// ^^ in the header file ^^
constexpr size_t maxSSID = sizeof(SSID) - 1;

void setup() {
  Serial.begin(115200);
  Serial.println(SSID);
  const char * replacement = "other";
  strncpy(SSID, replacement, maxSSID);
  Serial.println(SSID);
  strncpy(SSID, "other123", maxSSID);
  Serial.println(SSID);
  strncpy(SSID, "other123456", maxSSID);
  Serial.println(SSID);
}

void loop() {}

prints

ThisIsMyAP
other
other123
other12345

Note that you can't write past the end of the array, or you'll overwrite whatever is there. So you can't have any SSID that is longer than the original. But if you're modifying the .h anyway, just set the size of the array.

char SSID[33] = "ThisIsMyAP";
// ^^ in the header file ^^
constexpr size_t maxSSID = sizeof(SSID) - 1;
static_assert(maxSSID == 32);

Then add the static_assert in your sketch to make sure you don't forget. As a global variable, the memory should be initialized to zero, including the last byte in the array, which can be problematic with strncpy at the max.

Thanks Kenb4. It's a good idea, and it probably will work through the IDE which I may have to build up using WiFi.h, forgoing the ease of the Cloud features for now... Or unless there's another work-around in the Cloud.

When the .ino and header files originate on the Arduino Cloud, and I make changes so that the sketch compiles, there's some failure at operation which I am unable to sort at this time.

The coding below will result in a compiled binary uploaded to an OPTA. But there are runtime failures which results in the dreaded Red blinking lights :open_mouth:

In the Cloud editor, in 'thingProperties.h' you have to comment out the lines (which Arduino clearly does not want anyone to do based on the DO NOT EDIT comment):

// Code generated by Arduino IoT Cloud, DO NOT EDIT.

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

//const char SSID[]     = SECRET_SSID;    // Network SSID (name)
//const char PASS[]     = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)

// ...code for cloud variables

//WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

Then in the .ino script, I have something like this:

// ...

// cloud settings -- excludes: SSID, PASS and call to WiFiConnectionHandler
#include "thingProperties.h"

// RS-485 Modbus to HMI
#include <ArduinoModbus.h>
#include <ArduinoRS485.h>

// modbus baud setting
constexpr auto baudrate { 19200 };
constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };

// HMI Wifi settings
constexpr size_t maxSSID = 32;
static_assert(maxSSID == 32);
char SSID[maxSSID];
char PASS[maxSSID];
uint8_t wd1, wd2;
uint16_t wd;

// counter
int i;

// SETUP
void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500);

  // delay long enough for the HMI to boot
  Serial.println("Waiting 30 seconds... Waiting for HMI to boot & flush values");
  delay(30000);

  // RS-485 -- include some tests and alarms if this doesn't initialize, maybe to RED LED
  RS485.setDelays(preDelayBR, postDelayBR);

  // OPTA Modbus 'client' (Master)
  if( !ModbusRTUClient.begin(baudrate, SERIAL_8E1) ) { Serial.println("Error: Modbus RTU Client"); }
  else { Serial.println("Modbus RTU Client Initialized"); }

  // SSID
  Serial.println("Reading Wifi & Password from HMI");
  i=0;
  ModbusRTUClient.requestFrom(2, HOLDING_REGISTERS, 100, 16);
  while( ModbusRTUClient.available() ) {
    // Wifi SSID is 32 characters = max of 2x16 Holding Registers
    wd = ModbusRTUClient.read();
    wd1 = wd;
    wd2 = wd >> 8;
    SSID[2*i] = (char) wd1;
    SSID[2*i+1] = (char) wd2;
    i=i+1;
  }

  // Password
  i=0;
  ModbusRTUClient.requestFrom(2, HOLDING_REGISTERS, 120, 16);
  while( ModbusRTUClient.available() ) {
    wd = ModbusRTUClient.read();
    wd1 = wd;
    wd2 = wd >> 8;
    PASS[2*i] = (char) wd1;
    PASS[2*i+1] = (char) wd2;
    i=i+1;
  }

  Serial.print("SSID: ");
  Serial.println(SSID);
  Serial.print("Password: ");
  Serial.println(PASS);
  delay(5000);

  // SSID and PASS have been defined and passed to the Arduino
  WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);
  
  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);

 
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(4);
  ArduinoCloud.printDebugInfo();
}

// MAIN
void loop() {
  ArduinoCloud.update();
  // Your code here 

  // Listen on Modbus Bit 2 or 3 for trigger for Wifi Config --> reboot, see notes
  int wifiAddressTrigger = ModbusRTUClient.holdingRegisterRead(2, 2);
  int wifiPasswordTrigger = ModbusRTUClient.holdingRegisterRead(2, 3);

  if( wifiAddressTrigger | wifiPasswordTrigger ) {
    Serial.println("Wifi Config Triggers Detected");
    Serial.println("Reset Trigger Holding Registers on HMI");

    if( !ModbusRTUClient.holdingRegisterWrite(2, 2, 0) ) { Serial.println("Error: Failed to Reset SSID Trigger Register"); }
    else { Serial.println("Reset SSID Trigger Register"); }

    if( !ModbusRTUClient.holdingRegisterWrite(2, 3, 0) ) { Serial.println("Error: Failed to Reset PASS Trigger Register"); }
    else { Serial.println("Reset PASS Trigger Register"); }
    Serial.println("Rebooting...");
    NVIC_SystemReset();
  }
}

At boot, the serial displays the following before the runtime error and red blinking lights on the OPTA:

Modbus RTU Client Initialized
Reading Wifi & Password from HMI
SSID: abc123
Password: xyz456
***** Arduino IoT Cloud - 2.4.1 *****
Device ID: deleted-the-device-id-for-this-posting
MQTT Broker: iot.arduino.cc:8885
WiFi.status(): 0
Current WiFi Firmware: 1.94.0

That is, setup() works through reading the SSID and password from the external device through Modbus, no issues.

The connection to the Cloud is established with the correct device ID. Even WiFi.status() returns no errors.

But then operation hangs, red light blinks and the OPTA reboots. Repeat.

Finally, I then compile an older version without these updates using the static SSID and PASS from the Cloud settings, and it then fails to upload. I have to then flash the device to factory settings.

You don't need the static_assert in this case. Its purpose is if you're doing monkey business in another file, especially one that is generated, it would double-check that your custom changes did not get overwritten. Here, it's checking something declared on the previous line.

I'm guessing that the problem is that you've declared the WiFiConnectionHandler as a local variable to the setup function. That means when the function ends, the object is destroyed, and its memory used for something else. Then bad things happen. But you also can't create it until you have set the SSID and PASS. So try declaring a global pointer, and then dynamically create the object with new at the appropriate time.

WiFiConnectionHandler *ArduinoIoTPreferredConnection;

void setup() {
  //...
  // read SSID and PASS

  ArduinoIoTPreferredConnection = new WiFiConnectionHandler(SSID, PASS);
  initProperties();
  ArduinoCloud.begin(*ArduinoIoTPreferredConnection);  // use pointer
  //...

Seems this might be the anser for my question....

I have an ESP8266 board that needs SSID and PASSWORD to be hard coded on it.

I do not want that, i want the SSID and PASSWORD to be 'flexible'.

I copied / pasted this code into the ESP8266 board: GitHub - ardboer/Tibber_Price_Monitor: Version for using ZigZag LED matrix

I want the board to create a SSID itself so i can change the SSID / PASSWORD by PC or phone to connect it onto the interwebs.

I need some help with this, I'm a Arduino (NOOB) beginner.

Kind regards,
Ben

You need to add code to prompt the user to enter the SSID and password.

Did you look at the technique used in the link that I previously posted ?

Serial input basics - updated

UKHeliBob,

I did notice your code, but i do not know how to use it. :face_with_raised_eyebrow:

The things i do might be stupidly wrong, but i just downloaded the files from the internet, opened Arduino IDE and dragged the files into it.

Than I clicked "VERIFY" and after this was done I clicked on "UPLOAD" and the files where send to the LOLIN(WEMOS) D1 mini.

After doing this, i changed the "settings" file and did the verify and upload again.

// WiFi Settings
const char* ssid = "SSID";
const char* password = "Password";

// Tibber API key - Insert your personal key here - this is the demo token
const String apiKey = "5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE";


// Usually no need to adjust the following variables:
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

const String timeZoneBerlin ="CET-1CEST,M3.5.0,M10.5.0/3";

const char* tibberApi = "https://api.tibber.com/v1-beta/gql";
const uint16_t port = 443;

I kept the quotation marks

const char* ssid = "MY SSID";
const char* password = "MY WIFI PASSWORD";

This did not work.

Than I changed it again:

const char* ssid = MY SSID;
const char* password = MY WIFI PASSWORD;

I removed the quotation marks

This did not work either.

Because this does not work, i would like to use one of your codes.

I have noticed you offer SIX options.... I do not know what to add / remove or where to place it.

I'm very sorry about it, could you pleas be more specific? :roll_eyes:

I think i should pick ONE of the three codes and change the "helper wifi" file?

I appriciate you're helping me!
Ben

Hello kenb4,

I finally had some time to come back to this problem, and your advice on using the pointer worked! In fact, I am able to use the pointer reference in loop() to update the WiFi credentials through the HMI in real-time instead of doing a reboot call.

For example,

void setup() {
  //...
  // read SSID and PASS from HMI Memory on Boot

  ArduinoIoTPreferredConnection = new WiFiConnectionHandler(SSID, PASS);
  initProperties();
  ArduinoCloud.begin(*ArduinoIoTPreferredConnection);  // use pointer
  //...
}

void loop() {
  //...
  // read SSID and PASS during any change through HMI settings
  if( hmiPASSChange || hmiSSIDChange ) {
    ArduinoCloud.end()
    ArduinoIoTPreferredConnection = new WiFiConnectionHandler(SSID, PASS);
    ArduinoCloud.begin(*ArduinoIoTPreferredConnection);  // use pointer
  }
  //...

}

This is a very handy feature for portable site equipment!

Thank you very much for the help,

Chris

Congratulations on getting it to work. You now have a memory leak :slight_smile:

Before overwriting and losing the pointer, use it properly dispose of the old object, freeing its memory on the heap

    ArduinoCloud.end();
    delete ArduinoIoTPreferredConnection;  // add this
    ArduinoIoTPreferredConnection = new WiFiConnectionHandler(SSID, PASS);
    ArduinoCloud.begin(*ArduinoIoTPreferredConnection);