0

Darksky weather forecast, AIO service, location setup. 400 e
Moderators: adafruit_support_bill, adafruit

Forum rules
If you're posting code, please make sure your code does not include your Adafruit IO Active Key or WiFi network credentials.
Please be positive and constructive with your questions and comments.

Darksky weather forecast, AIO service, location setup. 400 e

by chrim on Mon Sep 02, 2019 3:10 am

Hi,

I'm trying to use the darksky weather service without success and I've no doubt it will be down to me. I can very happily send json data and display it in a dashboard (https://io.adafruit.com/chrim/dashboard ... ation-data)..

I'm trying to make the call to set up the weather location as documented as:

Code: Select all | TOGGLE FULL SIZE
POST /api/v2/:username/integrations/weather
Create a new weather integration record. Request body should be a JSON record in the form:

{
  "weather": {
    "location": locationValue
  }
}
Where locationValue is a location string in latitude,longitude format.


with the following function, it's a tester so hard coded location (to one of my favourite rollercoasters...). IO_HOST, SECRET_IO_USERNAME, SECRET_IO_KEY are all configured correctly and used elsewhere in the sketch to send the other dashboard data. The generated is json is displayed correctly in the monitor:

Code: Select all | TOGGLE FULL SIZE
{
  "weather": {
    "location": "53.599084,-2.839255"
  }
}


However, the function generates a 400 error, "HTTP/1.0 400 Bad request" response, no errors appear in the monitor (https://io.adafruit.com/chrim/monitor) - and no location is created. Function follows, client is a connected instance of WiFiClient. serialMessageHeader() is a little function that helps me neaten up debug messages. Any thoughts would be welcome! Thanks

Code: Select all | TOGGLE FULL SIZE
void setupAIOWeatherLocation() {
/* -
https://io.adafruit.com/services/weather
POST /api/v2/:username/integrations/weather
Create a new weather integration record. Request body should be a JSON record in the form:

{
  "weather": {
    "location": locationValue
  }
}
Where locationValue is a location string in latitude,longitude format.
//
*/
  const int capacity = 2*JSON_OBJECT_SIZE(1);
  StaticJsonDocument<capacity> doc;
 
  JsonObject weather = doc.createNestedObject("weather");
  weather["location"] = "52.986923,-1.883297";
 
  serialMessageHeader();
  Serial.println("Sending weather location to " IO_HOST);
 
  client.setTimeout(10000);
  if (!client.connect(IO_HOST, 80)) {
    serialMessageHeader();
    Serial.println("Connection to " IO_HOST " failed");
    return;
  }
 
  // Send the first line of the request
  // POST /api/v2/:username/integrations/weather
 
  client.println("POST /api/v2/" SECRET_IO_USERNAME "/integrations/weather");
 
  // Send the first part of the HTTP headers
  client.println("Host: " IO_HOST);
  client.println(F("Connection: close"));
 
  client.print(F("Content-Length: "));
  client.println(measureJson(doc));
 
  client.println(F("Content-Type: application/json"));
  client.println(F("X-AIO-Key: " SECRET_IO_KEY));
  client.println();  // Terminate headers with a blank line

  // Send the JSON document in body
  serializeJson(doc, client);
 
  //debug show the json in serial
  serializeJsonPretty(doc, Serial);
 
  // Check the  HTTP status (should be "HTTP/1.1 200 OK")
  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status + 9, "200 OK") != 0) {
    serialMessageHeader();
    Serial.print(F("Unexpected status from AIO: "));
    Serial.println(status);
    return;
  }

  // Close the connection
  client.stop();

  serialMessageHeader();
  Serial.println("Done.");


}

chrim
 
Posts: 5
Joined: Mon Feb 16, 2015 8:40 am

Re: Darksky weather forecast, AIO service, location setup. 4

by abachman on Wed Sep 25, 2019 12:24 pm

Hi chrim,


A common cause is improperly set Content-Type header. IO is pretty specific about requiring that `Content-Type: application/json` is set when the data is in that format. You seem to have them set up correctly, but you didn't include any of the libraries you're using in your sketch, so I can't reproduce the error :/

Is this still an issue for you and do you have a more complete version of this code, or at least the #include statements you're using?


- adam b.

abachman
 
Posts: 352
Joined: Mon Feb 01, 2010 12:48 pm

Re: Darksky weather forecast, AIO service, location setup. 4

by chrim on Sat Sep 28, 2019 4:42 am

Hi Adam, full code is below, thanks for looking at this for me. Running on a MKR1010 wifi.

Cheers!

Code: Select all | TOGGLE FULL SIZE
/*   --------------
     Author: chrim
     Project: smart irrigator linked to adafruit iot platform
     vesion: 2
     date: Started on 1/9/19
     ---------------
*/
//store your secrets in here

#include "SPI.h"
#include "WiFiNINA.h"
#include "ArduinoJson.h"
#include "DHT.h"

// adafruit IOT
#define IO_GROUP  "irrigator"
#define IO_HOST   "io.adafruit.com"     

//physical connections
#define DHTPIN 7

//DHT sensor
#define DHTTYPE DHT22

// Wifi
char ssid[] = SECRET_SSID;
char pass[] = SECRET_PASS;
int status = WL_IDLE_STATUS;

//location
const double  DEVICE_LAT = 53.599084;
const double  DEVICE_LONG = -2.839255;

//timers
// -- intervals
const int     AIO_SEND_INTERVAL = 10000;  //needs to be > 2000 for DHT sensor
const int     WIFI_INTERVAL = 1000;

// -- checks
unsigned long AIO_LAST_SEND = 0;

//initialise
WiFiClient client;
DHT dht(DHTPIN, DHTTYPE);

// ***-----------------------------------------------

void setup() {

  startSerial();
  startWifi();
  startSensors();
 
  setupAIOWeatherLocation();
}

// ***-----------------------------------------------

void loop() {

  if (WiFi.status() == WL_CONNECTED) { //are we connected to wifi

    //check each timer and perform the actions

    if (millis() - AIO_LAST_SEND > AIO_SEND_INTERVAL) {
      //time to update adafruit io cloud
      sendAmbientData();
      AIO_LAST_SEND = millis();
    }


  } else {
    serialMessageHeader();
    Serial.println(F("Not connected to WiFi, trying again."));
    startWifi();
  }
}

// ***-----------------------------------------------

void sendAmbientData() {

  /*
       //Allocate the JsonDocument to store this document:
      {
         "location": {
           "lat": 53.599094,
           "lon": -2.839383,
         },
         "feeds": [
           {
             "key": "temperature",
             "value": 15.1
           },
           {
             "key": "humidity",
             "value": 60.2
           }
         ]
       }


  */

  // get data from DHT
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  serialMessageHeader();
  Serial.print(F("Sensor readings, humidity is "));
  Serial.print(h);
  Serial.print(F(", temperature is "));
  Serial.println(t);


  const int capacity = JSON_ARRAY_SIZE(2) + 4 * JSON_OBJECT_SIZE(2);
  StaticJsonDocument<capacity> doc;

  // Add the "location" object   ////this is wrong!!
  JsonObject location = doc.createNestedObject("devicelocation");
  location["lat"] = DEVICE_LAT;
  location["lon"] = DEVICE_LONG;

  // Add the "feeds" array
  JsonArray feeds = doc.createNestedArray("feeds");

  JsonObject feed1 = feeds.createNestedObject();
  feed1["key"] = "temperature";
  feed1["value"] = t;

  JsonObject feed2 = feeds.createNestedObject();
  feed2["key"] = "humidity";
  feed2["value"] = h;

  serialMessageHeader();
  Serial.println("Sending to " IO_HOST);

  // Connect to the HTTP server & send most of the headers
  connectToHTTPServerToPost();
 
  //send the rest of the header
  client.println(measureJson(doc));
  client.println(F("Content-Type: application/json"));
  client.println(F("X-AIO-Key: " SECRET_IO_KEY));
  client.println();  // Terminate headers with a blank line

  // Send the JSON document in body
  serializeJson(doc, client);
 
  //serializeJsonPretty(doc, Serial);
 
  checkResponseAndStop();  //clean up and close the connection

}

// ----------------

void setupAIOWeatherLocation() {
/* -
https://io.adafruit.com/services/weather
POST /api/v2/:username/integrations/weather
Create a new weather integration record. Request body should be a JSON record in the form:

{
  "weather": {
    "location": locationValue
  }
}
Where locationValue is a location string in latitude,longitude format.
//
*/
  const int capacity = 2*JSON_OBJECT_SIZE(1);
  StaticJsonDocument<capacity> doc;
 
  JsonObject weather = doc.createNestedObject("weather");
  weather["location"] = "52.986923,-1.883297";
 
  serialMessageHeader();
  Serial.println("Sending weather location to " IO_HOST);
 
  client.setTimeout(10000);
  if (!client.connect(IO_HOST, 80)) {
    serialMessageHeader();
    Serial.println("Connection to " IO_HOST " failed");
    return;
  }
 
  // Send the first line of the request
  // POST /api/v2/:username/integrations/weather
 
  client.println("POST /api/v2/" SECRET_IO_USERNAME "/integrations/weather");
 
  // Send the first part of the HTTP headers
  client.println("Host: " IO_HOST);
  client.println(F("Connection: close"));
 
  client.print(F("Content-Length: "));
  client.println(measureJson(doc));
 
  client.println(F("Content-Type: application/json"));
  client.println(F("X-AIO-Key: " SECRET_IO_KEY));
  client.println();  // Terminate headers with a blank line

  // Send the JSON document in body
  serializeJson(doc, client);
 
  //debug show the json in serial
  serializeJsonPretty(doc, Serial);
 
  // Check the  HTTP status (should be "HTTP/1.1 200 OK")
  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status + 9, "200 OK") != 0) {
    serialMessageHeader();
    Serial.print(F("Unexpected status from AIO: "));
    Serial.println(status);
    return;
  }

  // Close the connection
  client.stop();

  serialMessageHeader();
  Serial.println("Done.");
 

}

// ----------------

void checkResponseAndStop() {
 
    // Check the  HTTP status (should be "HTTP/1.1 200 OK")
  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status + 9, "200 OK") != 0) {
    serialMessageHeader();
    Serial.print(F("Unexpected status from AIO: "));
    Serial.println(status);
    return;
  }

  // Close the connection
  client.stop();

  serialMessageHeader();
  Serial.println("Done.");

}




// ----------------

void connectToHTTPServerToPost(){
 
  client.setTimeout(10000);
  if (!client.connect(IO_HOST, 80)) {
    serialMessageHeader();
    Serial.println("Connection to " IO_HOST " failed");
    return;
  }

    // Send the first line of the request
  client.println("POST /api/v2/" SECRET_IO_USERNAME "/groups/" IO_GROUP "/data HTTP/1.1");
 
 
  // Send the first part of the HTTP headers
  client.println("Host: " IO_HOST);
  client.println(F("Connection: close"));
  client.print(F("Content-Length: "));
 
}



// --------------------

void startWifi() {

  serialMessageHeader();
  Serial.print(F("Attempting to connect to WPA network with SSID: "));
  Serial.println(ssid);

  unsigned long WIFI_START = millis();
  status = WiFi.begin(ssid, pass);

  while (millis() < WIFI_START + WIFI_INTERVAL) {
    ; //give wifi a chance to start
  }

  if ( status != WL_CONNECTED) {
    serialMessageHeader();
    Serial.print(F("Unable to connect to "));
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
  }
 
  serialMessageHeader();
  Serial.print(F("Successfully connected to "));
  Serial.println(ssid);

}

// ----------------

void startSensors() {

  dht.begin();
}

// ----------------

void startSerial() {


  const     int   serialDelay = 10000;
  unsigned  long  serialStart = millis();

  Serial.begin(9600);

  while (millis() < (serialStart + serialDelay)) {
    ; //wait for serial to become available
  }

  serialMessageHeader();
  Serial.println(F("Initialised device serial connection."));
}

// ----------------

void serialMessageHeader() { //header for debug messages in serial monitor
  Serial.print(F(" [ "));
  Serial.print(millis());
  Serial.print(F(" ] "));

}

chrim
 
Posts: 5
Joined: Mon Feb 16, 2015 8:40 am

Re: Darksky weather forecast, AIO service, location setup. 4

by abachman on Mon Sep 30, 2019 11:52 am

Thanks!

Running a stripped down version with just the weather location request on an ESP8266 I was able to duplicate the error. Examining the full HTTP response from IO showed that for some reason IO was seeing the request as text/html content type rather than application/json. I don't know why, to be honest. Thought it might be F() inside client.print, could be something weird about using many client.print statements, could be a single space in some line somewhere. This link may be relevant: https://forum.arduino.cc/index.php?topic=385298.0.

I changed the way the request was built and the order of the header lines, and it went through successfully. This replaces the code after `client.connect(IO_HOST, 80)`:
Code: Select all | TOGGLE FULL SIZE
  String request = "POST /api/v2/" SECRET_IO_USERNAME "/integrations/weather HTTP/1.1\r\n"
    "Host: " IO_HOST "\r\n"
    "Content-Type: application/json \r\n"
    "Connection: close\r\n";
  request += "Content-Length: ";
  request += String(measureJson(doc));
  request += "\r\n";
  request += "X-AIO-Key: " SECRET_IO_KEY "\r\n\r\n";
   
  //debug show the json in serial
  Serial.println("Sending request:");
  Serial.print(request);
  serializeJson(doc, Serial);
  Serial.println("\r\n\r\n---");
 
  // Send the HTTP request
  client.print(request);
  serializeJson(doc, client);

  while (client.connected() || client.available()) {
    if (client.available()) {
      String line = client.readStringUntil('\n');
      Serial.println(line);
    }
  }


I haven't done any experiments to see what changes will make it fail, but this way works :|

Unrelated to making HTTP requests, it's worth noting that you can only have 5 active weather data subscriptions in Adafruit IO, so adding weather location creation requests to the top of a sketch like this will cause you to hit the account limit after it has restarted 5 times.


- adam b.

abachman
 
Posts: 352
Joined: Mon Feb 01, 2010 12:48 pm

Re: Darksky weather forecast, AIO service, location setup. 4

by chrim on Wed Oct 02, 2019 5:48 am

Thanks Adam, awesome, will give this a whirl.

By the way, my intent isn't to run this every time on a boot, rather - check the device location exists, and create it if it doesn't. Then call down the weather.

chrim
 
Posts: 5
Joined: Mon Feb 16, 2015 8:40 am

Please be positive and constructive with your questions and comments.