Reconnect

I'm trying to query other websites from my MKR1010 connected to the IOT cloud, when doing this, via a new WiFiClient (but existing wifi connection) it breaks the connection to IOT cloud.

I would like to be able to call the reconnect method, but apologies it's a long time since I've done any serious coding.

I'm starting the connection (working fine) with

ArduinoCloud.begin(ArduinoIoTPreferredConnection);

and can test the connection status ok with

if (ArduinoCloud.connected() == 0) {

but if there is no connection (true from the if above) I'd like to call

ArduinoCloud.reconnect(Client&)

Apologies for my noobiness but I'm looking for where the Client variable is defined so I can grab the name and call reconnect after making the call to the web elsewhere in code, something like:

  if (ArduinoCloud.connected() == 0) {
    Serial.println("Not connected to Arduino Cloud.");
    ArduinoCloud.reconnect(**VARHERE**);
  } else {
    //we are connected
    ArduinoCloud.update();
  }

Or - should I not use connection manager?

Thanks for pointers

hi @homegeek

let me see how I can help you :slight_smile:
would you mind sharing more of your code?

sounds strange to me that you lose connection to IoT Cloud if you have another instance of WiFiClient, but I haven't tried myself so can't swear on it :wink:

unfortunately for now the ConnectionManager instance as well as the Client are private members of the Class so you can't get ahold of them, but in the next release we'll be separating the parts and offer a new connection manager which is in its own library and more manageable by the user.

I'd still like to give a shot at finding a solution for you without having to reinitialise the whole ArduinoIoTCloud process after every external call.

Give me as much detail as possible and I'll get to it

ciao.ubi

Hi ubi,

I've been at a festival for a few days, I'll get the code posted today. Thanks for taking a look.

Cheers!

ubi, code follows. I get a connection to the IOT cloud when starting with attempted = true which prevents the call to isRainForecast() and all is fine.

As soon as calling isRainForecast(), which instantiates a new client connection with the connection to IOT cloud drops. I was trying to work round this by checking status and then calling a reconnect (where my original question came from). I use the existing WiFi connection.

Thank you for looking at this.

thingProperties.h

#include <ArduinoIoTCloud.h>
#include <WiFiConnectionManager.h>

const char THING_ID[] = "************************";

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

void onMinutesRemainingValve1Change();

bool soilNeedsWatering;
bool valve1isOpen;
int minutesRemainValve1;
float ambientHumidity;
float soilMoisturePC;
float ambientTemperature;
bool rainIsImminent;
int minutesToSunset;

void initProperties(){
  ArduinoCloud.setThingId(THING_ID);
  ArduinoCloud.addProperty(soilNeedsWatering, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(valve1isOpen, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(minutesRemainValve1, READWRITE, ON_CHANGE, onMinutesRemainingValve1Change);
  ArduinoCloud.addProperty(ambientHumidity, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(soilMoisturePC, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(ambientTemperature, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(rainIsImminent, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(minutesToSunset, READ, ON_CHANGE, NULL);
}

ConnectionManager *ArduinoIoTPreferredConnection = new WiFiConnectionManager(SSID, PASS);

sketch:

/*
  Sketch generated by the Arduino IoT Cloud Thing "SmartIrrigator"
  https://create.arduino.cc/cloud/things/2cc1911c-c24b-49a4-a04c-d2d596ba89aa

  Arduino IoT Cloud Properties description

  The following variables are automatically generated and updated when changes are made to the Thing properties

  bool soilNeedsWatering;
  bool valve1isOpen;
  int minutesRemainValve1;
  float ambientHumidity;
  float soilMoisturePC;
  float ambientTemperature;
  bool rainIsImminent;
  int minutesToSunset;

  Properties which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
#include "SPI.h"
#include "ArduinoJson.h"


const int rainLookForwardHours = 3;   //how many hours do we want to check for rain for? 3h feels about right, too far it'll dry out


/*
  globals
  -----------------------------------------
*/

//weather
String apiKeyOWM = "8ddbf758ac17a2039eeec55db60d56bd";  //your personal API key from https://openweathermap.org/appid
char weatherServer[] = "api.openweathermap.org";
String weatherLocation = "2640908"; //city id for weather location from https://openweathermap.org/current

//temp
bool attempted = true;


void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  unsigned long serialBeginTime = millis();
  while (!Serial && (millis() - serialBeginTime > 5000));

  // 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(2);
  ArduinoCloud.printDebugInfo();

  /*
    initialise application variables
    -------------------------------------
  */

  //calculated
  soilNeedsWatering = false; //result of a calculation
  valve1isOpen = false; //state of the first valve
  minutesRemainValve1 = 0; //0 to 30
  minutesToSunset = 0;  //0 to 1500;
  rainIsImminent = false; //is it going to rain within the next forecast period defined by rainLookForwardHours

  //sensor readings
  ambientHumidity = 0;  //percentage
  ambientTemperature = 0;   //-30 to 50
  soilMoisturePC = 0;  //0 to 100, 0 is bone dry, 100 is water



}


void loop() {

  
  // Your code here
  
  ArduinoCloud.update();
  
 //  Serial.println(WiFi.status());
  //Serial.println(ArduinoCloud.connected());
 
  if (ArduinoCloud.connected() == 0) {
    //Serial.println("Not connected to Arduino Cloud.");
    //ArduinoCloud.reconnect(&ArduinoIoTPreferredConnection);
  } else {
    //we are connected
    Serial.println("Connected.");
    //ArduinoCloud.update();
    //Serial.println(ArduinoCloud.connected());
  }
  //Serial.println(ArduinoCloud.connected()) 

  if (WiFi.status() == WL_CONNECTED) {

    if (!attempted) {
      
      rainIsImminent = isRainForecast();
      if (rainIsImminent) {
        Serial.println("Rain is forecast.");
      } else {
        Serial.println("Rain is not forecast.");
      }
      
      
      attempted = true;
    }

  }
  
}

void onMinutesRemainingValve1Change() {
  // the minutes remaining has been changed in the web interface

}

bool isRainForecast() {
  //check whether rain is forecast from Open Weather Map
  
  
  WiFiClient webclient;
  String webServerResponse = "";

  //Serial.println("\nStarting connection to server...");
  // if you get a connection, report back via serial:
  if (webclient.connect(weatherServer, 80)) {
    //Serial.println("connected to server");
    
    // construct a HTTP request:
    webclient.print("GET /data/2.5/forecast?");
    webclient.print("id=" + weatherLocation);
    webclient.print("&appid=" + apiKeyOWM);
    webclient.print("&cnt=3");
    webclient.println("&units=metric");
    webclient.println("Host: api.openweathermap.org");
    webclient.println("Connection: close");
    webclient.println();
    
  } else {
      Serial.println("unable to connect");
  }
  
  delay(1000);  //this needs to change to a timer
  
  
  while (webclient.connected()) {
    // grab the data coming back from the server
    webServerResponse = webclient.readStringUntil('\n');
  }
  
  if (webServerResponse.indexOf("rain") > -1) {  //does the forecast data contain the word rain?
      return true;
  } else {
      Serial.println("Rain not forecast.");
      return false;
  }
  
}

Commenting out the following in the sketch maintains the connection to arduino IOT cloud:

/*
  while (webclient.connected()) {
    // grab the data coming back from the server
    webServerResponse = webclient.readStringUntil('\n');
  } */

although breaks the test for whether it's forecast to rain or not!

Hope this helps!

hey @homegeek

I'm gonna run it locally and see what I come up with.
sorry for the late reply but sometimes notifications end up in an ocean of them and it's hard to keep track

u.

@homegeek

I have modified the code to check for issues elsewhere.
I'm able to run the request and come out of it unscathed, I can't say the same for the version which does .readUntil("\n");

here's what I've done:

  • added a Boolean property with a callback onChange and when that changes I call the API
/* thingProperties.h */

void onCheckRainForecastChange();

bool checkForecast;

void initProperties(){
...

ArduinoCloud.addProperty(checkForecast, READWRITE, ON_CHANGE, onCheckRainForecastChange);
}
/* .ino */

void loop() {
  ArduinoCloud.update();
}



void onCheckRainForecastChange(){
  Serial.println("*** calling weather API ***");
  rainIsImminent = isRainForecast();
  Serial.print("Rain is imminent: ");
  Serial.println(rainIsImminent);
  
}


bool isRainForecast() {
  //check whether rain is forecast from Open Weather Map
  
  
  WiFiClient webclient;// = (WiFiClient)ArduinoIoTPreferredConnection.getClient();
  String webServerResponse = "";

  Serial.println("\nStarting connection to server...");
  // if you get a connection, report back via serial:
  if (webclient.connect(weatherServer, 80)) {
    //Serial.println("connected to server");
    
    // construct a HTTP request:
    webclient.print("GET /data/2.5/forecast?");
    webclient.print("id=" + weatherLocation);
    webclient.print("&appid=" + apiKeyOWM);
    webclient.print("&cnt=3");
    webclient.println("&units=metric");
    webclient.println("Host: api.openweathermap.org");
    webclient.println("Connection: close");
    webclient.println();
    
  } else {
      Serial.println("unable to connect");
  }
  Serial.println("connection attempt passed");
  delay(1000);  //this needs to change to a timer
  
  
  while (webclient.connected()) {
    // grab the data coming back from the server
    Serial.print(webclient.read());
    
  }
  Serial.println(webServerResponse);
  if (webServerResponse.indexOf("rain") > -1) {  //does the forecast data contain the word rain?
      return true;
  } else {
      Serial.println("Rain not forecast.");
      return false;
  }
  
}

yes, that's it.

when you trigger the change in the dashboard the API is called.
I suspect the freeze was triggered by the response being too large to fit in the string.

the way I changed it will not parse or look for "rain" but will move forward and demonstrate that you can use another WiFiClient on the side without messing up the connection to Cloud.

let me know if this brings you on the right path, I'm willing to continue this ping-pong :slight_smile:

u.

if you change this

while (webclient.connected()) {
    // grab the data coming back from the server
    Serial.print(webclient.read());
    
}

with this

while (webclient.connected()) {
    // grab the data coming back from the server
    char wcc = webclient.read();
    Serial.print(wcc);
    
}
Serial.println();

you'll see the whole JSON output

{"cod":"200","message":0.0097,"cnt":3,"list":[{"dt":1566216000,"main":{"temp":17.27,"temp_min":15.93,"temp_max":17.27,"pressure":1010.87,"sea_level":1010.87,"grnd_level":1009.79,"humidity":72,"temp_kf":1.34},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":32},"wind":{"speed":7.76,"deg":268.907},"rain":{"3h":0.813},"sys":{"pod":"d"},"dt_txt":"2019-08-19 12:00:00"},{"dt":1566226800,"main":{"temp":17.55,"temp_min":16.55,"temp_max":17.55,"pressure":1011.75,"sea_level":1011.75,"grnd_level":1010.64,"humidity":63,"temp_kf":1},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":0},"wind":{"speed":7.36,"deg":268.7},"rain":{"3h":0.375},"sys":{"pod":"d"},"dt_txt":"2019-08-19 15:00:00"},{"dt":1566237600,"main":{"temp":16.26,"temp_min":15.6,"temp_max":16.26,"pressure":1012.9,"sea_level":1012.9,"grnd_level":1011.33,"humidity":69,"temp_kf":0.67},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":0},"wind":{"speed":7.83,"deg":264.524},"rain":{"3h":0.25},"sys":{"pod":"d"},"dt_txt":"2019-08-19 18:00:00"}],"city":{"id":2640908,"name":"Ormskirk","coord":{"lat":53.5668,"lon":-2.8818},"country":"GB","timezone":3600,"sunrise":1566190636,"sunset":1566243213}}

that is indeed a lot of JSON to fit into a string

Ubi,

Thank you! Very much appreciate your time and effort in looking at this - and spotting my error in overflowing the string.

You've given me more than enough to help, thank you again, cheers,

hg

@homegeek
my pleasure, mate :slight_smile:

don't hesitate to ping me again on this
u.

To help others, my final solution below. This wasn't an IOT issue - it was me overflowing a string!

I grab each incoming char, add it to a buffer, compare the buffer instead.

I'm sure there are optimisations that can be applied here - there must be a smart way to use pointers and strings for instance but this works for me :slight_smile:

Happy coding everyone.

Again @ubidefeo - thank you!

  char searchWord[] = "rain"; //what are we looking for in the response from the webserver
  int bufferLen = strlen(searchWord)-1;
  char searchBuffer[bufferLen] = {};
  int bufferFillPos = 0; //where are we when filling the searchBuffer
  
  
  bool foundStringFromResponse = false; 
  
  while (webclient.connected()) {  //read the whole response
    
    // grab the data coming back from the server
    char currentChar = webclient.read();  //read the next character
  
    //add it to the buffer
    if (bufferFillPos < bufferLen) {  //buffer not full
      searchBuffer[bufferFillPos] = currentChar;
      bufferFillPos++;
    } else {  //buffer is full
        
      //move everything down the buffer
      int i;
      for (i = 1; i <= bufferLen; i++) {
        searchBuffer[i-1] = searchBuffer[i];
      }
      
      //add the next character to the end of the buffer
      searchBuffer[bufferFillPos] = currentChar;
    }
    
    if (strcmp(searchBuffer,searchWord) == 0) {
      foundStringFromResponse = true;
    }
    
  
  }
  
  return foundStringFromResponse;