0

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

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: 338
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: 338
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.