I am trying to develop an IoT device that should provide some functionality using a HTTP/REST API. I decided to use the ESP32 chip (on "ESP32 dev board").
Now I want to implement an easy-to-use WLAN configuration. I don't want to store credentials in my source code like many other samples do; so I decided to use WPS.
I tried to implement a basic web server using the sources here:
https://randomnerdtutorials.com/esp32-web-server-arduino-ide/ - and then I added the WPS functionality from the Wifi/WPS samples shipped with the EPS32 extensions for Arduino IDE.
Now the WPS already works, i.e. when the dev-board gets powered it is in WPS connection mode and waits for the router to accept the WPS connection. It successfully gets the SSID and connects to the WLAN.
But when I power-off the ESP32, and power-on again, I have to do the WPS reconnection procedure again. I'd expect a reconnection, that stores the credentials and is able to connect to the same WLAN again when the ESP32 device is powered-on at any time later. I guess I have to store some credentials and use them to re-establish the connection - but where do I get the credentials, and how do I reconnect?
I did search the web for "ESP32 WLAN WPS reconnect" and similar terms, but did find only reconnect strategies for non-wps (SSID + password) connections. I did also check the WiFi library documentation and the esp_wps library documentation, but didn't find anything suitable.
That's the WLAN WPS connection source:
#include <WiFi.h>
#include "esp_wps.h"
#define ESP_WPS_MODE WPS_TYPE_PBC
esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(ESP_WPS_MODE);
String wpspin2string(uint8_t a[]){
//...
}
void WiFiEvent(WiFiEvent_t event, system_event_info_t info){
switch(event){
case SYSTEM_EVENT_STA_START:
Serial.println("Station Mode Started");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("Connected to :" + String(WiFi.SSID()));
Serial.print("Got IP: ");
Serial.println(WiFi.localIP());
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Disconnected from station, attempting reconnection");
WiFi.reconnect();
break;
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
Serial.println("WPS Successfull, stopping WPS and connecting to: " + String(WiFi.SSID()));
esp_wifi_wps_disable();
delay(10);
WiFi.begin();
break;
case SYSTEM_EVENT_STA_WPS_ER_FAILED:
Serial.println("WPS Failed, retrying");
esp_wifi_wps_disable();
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
break;
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
Serial.println("WPS Timedout, retrying");
esp_wifi_wps_disable();
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
break;
case SYSTEM_EVENT_STA_WPS_ER_PIN:
Serial.println("WPS_PIN = " + wpspin2string(info.sta_er_pin.pin_code));
break;
default:
break;
}
}
// some GPIO stuff, removed for SO question
void setup() {
// initialize some GPIO for status etc. - removed for SO
//Initialize serial and wait for port to open:
Serial.begin(115200);
while(!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// We start by connecting to a WiFi network
WiFi.onEvent(WiFiEvent);
WiFi.mode(WIFI_MODE_STA);
Serial.println("Starting WPS");
esp_wifi_wps_enable(&config);
esp_wifi_wps_start(0);
// attempt to connect to Wifi network:
while(WiFi.status() != WL_CONNECTED) {
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
delay(700);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
//irrelevant for SO question
}
The ESP, both 32 and 8266, remember the last AP it was connected to. So simple call WiFi.begin(); without any credentials will make it connect to that last AP. Then in your while(WiFi.status() != WL_CONNECTED) loop you could make it timeout and then call the esp_wifi_wps_start(0); if it does not connect.
you can find the answer here https://www.esp32.com/viewtopic.php?f=19&t=27004
the SSID and password are stored in the config and given to esp_wifi_set_config, after WPS has finished, you can just use the getter function again by calling:
wifi_config_t config;
esp_err_t err = esp_wifi_get_config(WIFI_IF_STA, &config);
if (err == ESP_OK) {
printf("SSID: %s, PW: %s\n", (char*) config.sta.ssid, (char*) config.sta.password);
} else {
printf("Couldn't get config: %d\n", (int) err);
}
and you can then find the SSID and password in that struct again.
Related
I've hacked apart the ESP32 BLE arduino sketches to do what I want. The server side is easy. Please see code below:
if (con == 0){
digitalWrite(LED, LOW);
}
if (con == 1){
digitalWrite(LED, HIGH);
delay(1000);
digitalWrite(LED, LOW);
delay(1000);
}
if (deviceConnected) {
pCharacteristic->setValue((uint8_t*)&value, 4);
pCharacteristic->notify();
value++;
delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
con = 1;
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
con = 0;
}
This works exactly how I want. It simply sits idle doing nothing, when a device connects to the BLE server then it will flash an LED.
No problems there, even though I suspect my code isn't 'that pretty.
What i'm having trouble doing however is creating an ESP32 client to connect to the BLE device.
The client has the name set as
BLEDevice::init("BOX_A1");
The example code seems to want UID for both the service and characteristic. Is there any way to just connect to the short advertised name? No data is being shared, it's just simply acting as a beacon to identify a box when connected to.
Thanks
Andrew
You can't connect to a device using the advertised name, not directly at least.
If you want to use the advertised name you have to scan for all BLE devices around you and select the one matching your name. The BLE scan example shows you how this is done. The code scans for a scanTime of 5 seconds, waits 2 seconds and starts scanning again:
void loop() {
// put your main code here, to run repeatedly:
BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
Serial.print("Devices found: ");
Serial.println(foundDevices.getCount());
Serial.println("Scan done!");
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
delay(2000);
}
The example just prints the amount of found devices, you want to search through them and look for the correct name. The BLEScan returns an object of type BLEScanResults. You can access the found devices using getDevice with an index. Something like this might work to print the names of all found devices:
BLEAdvertisedDevice device;
for (int i = 0; i < foundDevices.getCount(); ++i) {
device = foundDevices.getDevice(i);
Serial.println(device.getName().c_str());
}
Now you can compare the names and work with the correct device.
To my understanding,
You want the client to connect to the server with given advertised name.
After connection is success, server turns on led.
You do have notification service running on server, but your client isn't interested.
Below is the client code which only connects to server with name "BOX_A1".
First Set our callback function that checks if device name matches when scanner discovers the devices:
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks
{
void onResult(BLEAdvertisedDevice advertisedDevice)
{
if (advertisedDevice.getName() == "BOX_A1")
{
advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for
foundDevice = new BLEAdvertisedDevice(advertisedDevice);
deviceFound = true;
Serial.print("Device found: ");
Serial.println(advertisedDevice.toString().c_str());
}
}
};
Use the BLEAdvertisedDevice object i.e. foundDevice object to connect to this server.
BLEClient* connectToServer(BLEAdvertisedDevice* device) {
BLEClient* pClient = BLEDevice::createClient();
if (pClient->connect(device)){ // Connect to the remote BLE Server.
Serial.println(" - Connected to server");
return pClient;
}
else{
Serial.println("Failed to Connect to device");
return NULL;
}
}
Use following line to call this connect function, it return the client object which can be used to disconnect from server.
if(deviceFound==true){
BLEClient* myDevice = connectToServer(device);
if(myDevice!=NULL){
Serial.print("Connected to the BLE Server: ");
Serial.println(device->getName().c_str());//print name of server
//DO SOME STUFF
//disconnect the device
myDevice->disconnect();
Serial.println("Device disconnected.");
}
}
This was the client side.
At server side to set the connection status flag use the following:
//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
Serial.println("Client Connected");
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
Serial.println("Client Disconnected");
deviceConnected = false;
}
};
I'm trying to use D1 mini to fetch some data from website. I created an API key on Thingspeak ThingHttp. However, the client didn't connect properly. I got "connection failed" from the Serial monitor.
Here is my code. I think they are almost the same as this.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
WiFiClientSecure client;
#define HOST "api.thingspeak.com"
void setup()
{
const char *ssid = "my_wifi";
const char *password = "qwertyui";
const char *API = "W0B96PD71W3Z245Q";
Serial.begin(115200);
WiFi.mode(WIFI_STA);
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(500);
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
IPAddress ip=WiFi.localIP();
Serial.println(ip);
delay(5000);
Serial.println("finish setup");
}
void loop()
{
delay(5000);
if (!client.connect(HOST, 80))
{
Serial.println(F("Connection failed"));
return;
}
Serial.println("***");
}
And here is what I got from the serial monitor.
WiFi connected
IP address:
192.168.0.53
finish setup
Connection failed
Connection failed
It's obvious that it indeed connected to my wifi correctly, but just unable to connect to the server.
Does anyone know how to fix this? Or are there any crucial step I should set on my D1mini?
(I'm using VSCode instead of Arduino IDE)
You're using the wrong port number.
Port 80 is for unencrypted HTTP.
Port 443 is for HTTPS.
You're using WiFiClientSecure, so presumably you're intending to use HTTPS. HTTPS runs on port 443, not port 80. You'll need to change your code to use 443, or you'll need to use WiFiClient in order to work with port 80 (but make sure the API you're trying to connect to allows access over plain HTTP - most will not).
I highly recommend that you use an existing HTTP client rather than implement the protocol yourself as you'll need to with WiFiClient or WiFiClientSecure, which just provide TCP and encrypted TCP connections. You can find examples of how to use ESP8266HTTPClient in the ESP8266 Arduino core repository.
I am trying to program my NodeMCU (Lolin v3) board in such a way that I can use it to configure the WiFi settings without having to hard code the credentials. I know there is a WiFiManager Library, but I don't intend to use that since I need to do my own implementation, not use the UI that the library provides. The credentials provided by the user are stored in a file using SPIFFS, used to check whether to start the board in AP_STA mode or STA mode only.
Below is the logic I use:
void connectWiFi(String ssid, String password, boolean staOnly = false) {
boolean state = true;
int i = 0;
if(staOnly)
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (i > 10) {
state = false;
break;
}
i++;
}
return state;
}
void join() {
String ssid = setupServer.arg("ssid");
String password = setupServer.arg("password");
result = connectWifi(ssid, password);
if(result) {
Serial.println("Connected");
// **THIS IS THE PROBLEMATIC PART**
setupServer.send(200, "text/plain", WiFi.localIP().toString());
// save credentials to a file
Serial.println("Conf saved");
delay(2000);
ESP.restart();
} else
setupServer.send(200, "text/plain", "fail");
}
void setup() {
Serial.begin(115200);
WiFi.disconnect(true);
boolean fileExists = SPIFFS.exists(WIFI_CONF_FILE);
if(!fileExists) {
WiFi.mode(WIFI_AP_STA);
WiFi.softAP("AP", "password");
IPAddress myIP = WiFi.softAPIP();
setupServer = ESP8266WebServer(myIP, 8888);
setupServer.on("/join", join);
setupServer.begin();
} else {
// read file contents for ssid and password
connectWifi(ssid, password, true);
// do some work here
}
}
void loop() {
setupServer.handleClient();
}
So now when I do a fresh boot, the board enters AP_STA mode and starts with SSID AP. I connect to it and open http://192.169.4.1/join?ssid=mywifi&password=12345678 in the browser. Somehow the connection gets terminated and I get "Destination Unreachable" in my browser. But the serial monitor prints 'Connected' and 'Conf saved'.
I want to know why it isn't returning the success response to the browser. I need the localIP after it has connected to the WiFi. It returns the failed response correctly in case it fails. How can I ensure it will always return the IP address assigned to it back to the client that connected to it before restarting?
Any help is appreciated.
Thanks!
Looks like it is bound to happen as radio module is shared between two modes.
Found the explaination for this issue here: https://github.com/esp8266/Arduino/issues/3282
This is related to the fact that STA will switch to the channel of the AP it is trying to connect to, and SoftAP will have to switch to the same channel. So the client (PC or smartphone connected to the SoftAP) will have to reconnect to the SoftAP on its new channel. In most cases this causes TCP connections to be reset.
I am trying to connect to AWS IoT using a basic pubsub example in my ESP32 board with the help of the Arduino IDE.
As a basic example it does connect to AWS IoT and publishes messages, but when I give a static IP to the program it does connect to the wifi with the specified IP address (I have also assigned a static IP to the MAC address of the board in my router), but it fails to publish the messages and gives me the following error:
Attempting to connect to SSID: RCB Rocks!!!!
Connected to wifi
E (37583) aws_iot: failed! mbedtls_net_connect returned -0x52
E (37583) AWS_IOT: Error(-23) connecting to ***********.iot.eu-west-2.amazonaws.com:8883,
Trying to reconnect
I am using the following code:
#include <AWS_IOT.h>
#include <WiFi.h>
AWS_IOT hornbill;
char WIFI_SSID[]="RCB Rocks!!!!";
char WIFI_PASSWORD[]="********";
char HOST_ADDRESS[]="************.iot.eu-west-2.amazonaws.com";
char CLIENT_ID[]= "1008";
char TOPIC_NAME[]= "smk";
IPAddress ip(192, 168, 0, 20);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
int status = WL_IDLE_STATUS;
int tick=0,msgCount=0,msgReceived = 0;
char payload[512];
char rcvdPayload[512];
void mySubCallBackHandler (char *topicName, int payloadLen, char *payLoad) {
strncpy(rcvdPayload,payLoad,payloadLen);
rcvdPayload[payloadLen] = 0;
msgReceived = 1;
}
void setup() {
Serial.begin(115200);
delay(2000);
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(WIFI_SSID);
WiFi.config(ip,gateway,subnet);
WiFi.mode(WIFI_STA);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// wait 5 seconds for connection:
delay(5000);
}
Serial.println("Connected to wifi");
if(hornbill.connect(HOST_ADDRESS,CLIENT_ID)== 0) {
Serial.println("Connected to AWS");
delay(1000);
if(0==hornbill.subscribe(TOPIC_NAME,mySubCallBackHandler)) {
Serial.println("Subscribe Successfull");
} else {
Serial.println("Subscribe Failed, Check the Thing Name and Certificates");
while(1);
}
} else {
Serial.println("AWS connection failed, Check the HOST Address");
while(1);
}
delay(2000);
}
void loop() {
if(msgReceived == 1) {
msgReceived = 0;
Serial.print("Received Message:");
Serial.println(rcvdPayload);
}
if(tick >= 5) {
// publish to topic every 5seconds
tick=0;
sprintf(payload,"Hello from hornbill ESP32 : %d",msgCount++);
if(hornbill.publish(TOPIC_NAME,payload) == 0) {
Serial.print("Publish Message:");
Serial.println(payload);
} else {
Serial.println("Publish failed");
}
}
vTaskDelay(1000 / portTICK_RATE_MS);
tick++;
}
I have found this AWS IoT SDK for Arduino ESP32 here, and I followed the instructions given in this website.
Attempting to connect to SSID: RCB Rocks!!!! Connected to wifi
So your board is able to get a network connection.
E (37583) aws_iot: failed! mbedtls_net_connect returned -0x52
This error means
NET - Failed to get an IP address for the given hostname
Either the host name is wrong or there's something wrong with your DNS setup. Given that your program works when you don't use a static IP address, the problem must be with the DNS setup on the board. When the board receives a dynamic IP address from DHCP, the DHCP server also sends it DNS settings. If you use a static IP address instead of DHCP, you also need to set up a DNS server statically.
You might want to try seeing if the ESP board can even connect to the internet using that static IP. You could try running this sketch. https://learn.sparkfun.com/tutorials/esp32-thing-hookup-guide#arduino-example-wifi
I had the same issue on my board and was getting the DNS_PROBE_FINISHED_NO_INTERNET if I connected to the wireless from chrome. Changing to a different network fixed the issue.
I know this is old, but moving Wifi.begin() outside the for loop did the trick for me:
WiFi.config(ip,gateway,subnet);
WiFi.mode(WIFI_STA);
Serial.print("Attempting to connect to SSID: ");
Serial.println(WIFI_SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("connected.");
I would like to know if WifiClient.connect does not allow the locally running server to be connected (e.g. http://localhost:8080). I discovered that WifiClient.connect works when the url is anything public (e.g. www.google.com). Please help if there may be something I can programmatically change in order to connect the localhost server. The code below is a portion of setup().
status = WiFi.begin(ssid, pass);
// if you're not connected, stop here:
if ( status != WL_CONNECTED) {
Serial.println("Couldn't get a wifi connection");
while(true);
}
// if you are connected, print out info about the connection:
else {
Serial.println("Connected to network");
}
// configure the uri to be called
char apiURL[] = "127.0.0.1";
uint16_t port = 8080;
if (client.connect(apiURL, port)){
Serial.println("connected to " + String(apiURL));
// it always prints this statement
}else{
Serial.println("not calling " + String(apiURL));
}
One more (and maybe silly) thing I would like to ask: would Bluetooth be a better option than Wifi when I want my Arduino to send data to server (e.g. making POST request)?