[Solved-ish] ESP8266 Stand-alone, Need GPS location w/o GPS module

I’m programming an ESP8266 Standalone For an entomologist and he would like to be able to make multiple units for himself and provide some for some other groups around the country. And all I need to get is my GPS location to determine sunset/sunrise times for the trap. GPS module was left off due to cost constraints + the relative ease to acquire this through internet or worst case enter coordinates yourself from your phone.

IFTTT was suggested But I was only really able to find it as being a trigger (move inside/ move outside zone), and all I need is the actual GPS location coordinates upon start up. I watched videos on getting data from ThingSpeak, and thought I had the problem fixed (either run with Blynk, or first run ThingSpeak connection to get GPS, then loop running Blynk). But when i tried to get location from a mylocation site, no data was parsed, and i realized i was probrably making the request from ThingSpeak and they may not wanna give me their GPS location and even if they did, it would do me no good.

The planned GPS widget would crush this issue, but for now does anyone have any suggestions? I hope I’m just making it more complicated than it is. Thanks as always.

This might not work depending on number of locations, devices and your Blynk layout, but would it work to have users select their location (or timezone) from the Blynk Menu widget (from a Menu widget for each trap)? The code will then determine the timezone based on that menu selection.

However this certainly doesn’t automate timezone or location which I think is what you’re looking for.

1 Like

Thanks for taking the time to read my post.

Yes, I had thought about having them enter the data via a terminal, or two sliders (long,lat), from which im sure i could pull time zone and hopefully if they use daylight savings time (DST). Although DST may need to be user fed.

But I was hoping for a slightly more automated system. I’m still not sure exactly if i’m able to get location data through thingspeak because, I’m able to get some static site data, yet not able to get some others. I’ve emailed them asking if this is possible but still no reply.

Another thought… assuming the ESP8266 has an active internet connection, I believe you can use Weather Underground’s API and get a JSON file returned for a location based on “autoip.” Which would be a request something like:

http://api.wunderground.com/api/YOUR-API-KEY/astronomy/q/autoip.json

The astronomy feature requested above burps out a JSON with sunrise and sunset information. Looks something like this for my area:

{
  "response": {
  "version":"0.1",
  "termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
  "features": {
  "astronomy": 1
  }
	}
		,	"moon_phase": {
		"percentIlluminated":"21",
		"ageOfMoon":"4",
		"phaseofMoon":"Waxing Crescent",
		"hemisphere":"North",
		"current_time": {
		"hour":"11",
		"minute":"51"
		},
		"sunrise": {
		"hour":"5",
		"minute":"30"
		},
		"sunset": {
		"hour":"19",
		"minute":"17"
		},
		"moonrise": {
		"hour":"9",
		"minute":"19"
		},
		"moonset": {
		"hour":"23",
		"minute":"27"
		}
	},
	"sun_phase": {
		"sunrise": {
		"hour":"5",
		"minute":"30"
		},
		"sunset": {
		"hour":"19",
		"minute":"17"
		}
	}
}

In this case it’s a matter of parsing that JSON data in a fashion similar to this to get what you need. Maybe this is more what you’re looking for.

1 Like

This seems like a great solution. This is actually my first project, first time using esp8266, arduino, blynk, thingspeak or anything else. but thanks to everyone here and all the examples and tutorials, I’ve been able to put some things together.

But I still dont fully understand api stuff. only what I’ve learned from tinkering on these sites. seems like a way to send and receive data, and json has become more popular. i’ve seen examples on the blynk api page that shows sending high and low signal. Can I send this data to blynk, or would I connect and get it separately?

could you possibly link any examples of this being done? i’ll try to google all i can on it, and thanks for the help and suggestion.

Much like you I’m pretty new to all of this! I got into fiddling with the Weather Underground API because I wanted to display the temperature of my neighbor’s weather station on my Blynk app (luckily he has a weather station registered with wunderground.com and lives right across the street!).

It’s going to take some tinkering and experimenting on your part, but this was the sketch I ended up with that wrote the temperature to a Blynk virtual pin to display in the app.

As far as JSON is concerned, it just a bunch of raw data. Microcontrollers aren’t generally good at sifting through webpages looking for information, but using ArduinoJson the ESP sifts though the JSON file byte by byte, finds what you’re looking for, and then it’s yours to do whatever with. I referenced this project as well because his was a good example of how to pluck information from Weather Underground’s API… that’s where I got started.

If I can do it… :grin:

1 Like

So, i have this running with wifi manager connecting to wifi,but after wifi is connected all i see is this and it reads bytesin at an extremely fast rate. i can post code if that helps. I tried to go through your code and use the important stuff, i wont be sending this data to blynk so i took all of that out to get this part working solo. i also dont need to call it but one time because i only need the initial longitude latitude coordinates. then i wikk connect to blynk and do my other stuff/

Well that first line looks promising… not sure why the rest is there.

Ah… are you running a timer in your code too?

timer.setInterval(300000L, sendWU); // 5 minutes between Wunderground API calls.

If not the code might be hammering wunderground.com (giving you the info once but not again until after a certain amt of time). With the SimpleTimer.h I was regulating the API calls to every 5 minutes. Otherwise I’d have to mess around with it on my end.

Also, when you sign into https://www.wunderground.com/weather/api/ there is an Analytics tab that will show you how many API calls/queries and if there were any errors (very bottom of that page).

1 Like

thanks, yea i had it on a timer at first but since i only need it once on start up i actually have it only being called on start up, it should also be printing some other things i believe if it was looping, it almost seem like its looping just at the one serial print.

to be honest i actually clipped your code before i tested it (big nono). i’ll start properly testing it and post what i come up with.

No worries. I usually start with everything… then prune… then test… then prune, etc.

Check out that Simple Timer library as you have the need and time. It’s used a lot with Blynk as you really don’t want to put any code other than Blynk.run(); and timer.run(); in your loop().

I mention that as I have some sketches that use it for things like timer.setTimeout(60000L, yourThingHere); which runs yourThingHere() 60 seconds after being called (and not again until called again).

1 Like

So, I noticed that it was receiving 4096 and then a bunch of 0’s, and i remembered seeing it being declared. I also noticed it’s a power of 2, and having taken some computer classes i was wondering if this was a max or something. but i doubled it to 8192, and it was able to get 5137 then maybe it drops the load and gets the zeros and picks up what is left, totaling 6405. So it seems that the 4096 may have been there to stop from issues, i’ll take a second look to see it there’s another limiter in the program.


I checked and http://api.wunderground.com/api/api code/conditions/q/autoip.json gives me actuate data. im gonna try to parse the latitude longitude from that.

Quite true! static char respBuf[4096];… that should mean 4096 characters buffered (don’t know if that’s the right word or not). Based on the JSON character count (I think I used this) you should be able to scale that number.

Either way… hope this is fruitful!

1 Like

I haven’t gone back to see if I could change it to get the larger page in (although I need to do so for future projects). But, using conditions instead of geolocation gave me the latitude and longitude just on a smaller page. but i was having issues getting to the data because it was inside "display_location", and after trying everything i could think of a quick google search showed an example of a guy using location = parsed_json['location']['city'], dont think I would have tried that.

But its working now. Thanks again for your help. I’ll be sure to post my project when its finished.

Cool… look forward to seeing it!

Current weather conditions for Waco, Texas are shown in the Terminal window of the screenshot below.

We can set a location in WiFi Manager and change it with the Terminal, or Menu, widget etc.

@structure7 and @JoeS have you worked out what the json commands are for pulling the weather forecast from Wunderground based on an http request of http://api.wunderground.com/api/InsertYourAPIkey/conditions/forecast/q/TX/Waco.json rather than a simple http://api.wunderground.com/api/InsertYourAPIkey/forecast/q/TX/Waco.json

This is the code I ended up with to get latitiude, longitude, and timezone. It stores the lat, lon in EEPROM if they are different from whats already saved and the timezone is used to set the physical RTC with the blynk RTC in a separate function. I adapted my code from structure7 's example that was posted earlier in the post.

// HTTP request const char WUNDERGROUND_REQ[] = "GET /api/*API Key*/conditions/q/autoip.json HTTP/1.1\r\n" "User-Agent: ESP8266/0.1\r\n" "Accept: */*\r\n" "Host: " WUNDERGROUND "\r\n" "Connection: close\r\n" "\r\n";

static char respBuf[4096];

bool showWeather(char *json);

void LocSync() { // Open socket to WU server port 80 Serial.print(F("Connecting to ")); Serial.println(WUNDERGROUND);

// Use WiFiClient class to create TCP connections (A LITTLE WORRIED HOW THIS WILL WORK WITH BLYNK) WiFiClient httpclient; const int httpPort = 80; if (!httpclient.connect(WUNDERGROUND, httpPort)) { Serial.println(F("connection failed")); delay(DELAY_ERROR); return; }

// This will send the http request to the server Serial.print(WUNDERGROUND_REQ); httpclient.print(WUNDERGROUND_REQ); httpclient.flush();

// Collect http response headers and content from Weather Underground // HTTP headers are discarded. // The content is formatted in JSON and is left in respBuf. int respLen = 0; bool skip_headers = true; while (httpclient.connected() || httpclient.available()) { if (skip_headers) { String aLine = httpclient.readStringUntil('\n'); //Serial.println(aLine); // Blank line denotes end of headers if (aLine.length() <= 1) { skip_headers = false; } } else { int bytesIn; bytesIn = httpclient.read((uint8_t *)&respBuf[respLen], sizeof(respBuf) - respLen); Serial.print(F("bytesIn ")); Serial.println(bytesIn); if (bytesIn > 0) { respLen += bytesIn; if (respLen > sizeof(respBuf)) respLen = sizeof(respBuf); } else if (bytesIn < 0) { Serial.print(F("read error ")); Serial.println(bytesIn); } } delay(1); } httpclient.stop();

if (respLen >= sizeof(respBuf)) { Serial.print(F("respBuf overflow ")); Serial.println(respLen); delay(DELAY_ERROR); return; } // Terminate the C string respBuf[respLen++] = '\0'; Serial.print(F("respLen ")); Serial.println(respLen); //Serial.println(respBuf);

// This part will be removed in favor of the timer feature. Probably remove delay_error and just report ERR. if (showWeather(respBuf)) { delay(DELAY_NORMAL); } else { delay(DELAY_ERROR); }

}

bool showWeather(char *json) { StaticJsonBuffer<3*1024> jsonBuffer;

// Skip characters until first '{' found // Ignore chunked length, if present char *jsonstart = strchr(json, '{'); //Serial.print(F("jsonstart ")); Serial.println(jsonstart); if (jsonstart == NULL) { Serial.println(F("JSON data missing")); return false; } json = jsonstart;

// Parse JSON JsonObject& root = jsonBuffer.parseObject(json); if (!root.success()) { Serial.println(F("jsonBuffer.parseObject() failed")); return false; }

// Extract weather info from parsed JSON JsonObject& current = root["current_observation"]; latitude = current["display_location"]["latitude"]; // Was `const float temp_f = current["temp_f"];` Serial.print(latitude);// Serial.print(F(" F ")); longitude = current["display_location"]["longitude"]; Serial.print(longitude); timezone = current["local_tz_offset"]; timezone = timezone/100; Serial.print(timezone);

int SaveFlag = 0; double Hold; Hold = ReadCoor(1); if (latitude != Hold){ WriteCoor(latitude,1); SaveFlag = 1; } Hold = ReadCoor(4); if (longitude != Hold){ WriteCoor(longitude,4); SaveFlag = 1; } if (SaveFlag ==1){ EEPROM.commit(); }

return true; }

double ReadCoor(int address){ double Hold = EEPROM.read(address); double HoldDec = EEPROM.read(address + 1); int Sign = EEPROM.read(address + 2); if (Sign == 1){ return (-(Hold +(HoldDec/100))); } else{ return (Hold + (HoldDec/100)); } }

void WriteCoor(double Coor, int address){ int Hold = abs(Coor); int HoldDec = ((abs(Coor) - Hold) * 100); EEPROM.write(address, Hold); EEPROM.write((address + 1), HoldDec); if (Coor < 0){ EEPROM.write((address + 2), 1); } else{ EEPROM.write((address + 2), 0); } }

So first off change this to be the correct url "GET /api/*API Key*/conditions/q/autoip.json HTTP/1.1\r\n". I haven’t written the code yet but you can use WiFiManager to set the API key so it doesn’t need to be hard coded just like is done with Blynk keys.

Then this is where you should make your changes. you should be able to change root["current_observation']; to "forecast" to get the information listed inside of forecast, or change it to whatever the top level title is. all other subsections can be declared like any file directory, just listing the path to the info. latitude = current["display_location"]["latitude"];, this was called after current observation so it looks in that main section, then looks inside the subsection display location to find the data labeled latitude.

// Extract weather info from parsed JSON JsonObject& current = root["current_observation"]; latitude = current["display_location"]["latitude"]; // Was `const float temp_f = current["temp_f"];` Serial.print(latitude);// Serial.print(F(" F ")); longitude = current["display_location"]["longitude"]; Serial.print(longitude); timezone = current["local_tz_offset"]; timezone = timezone/100; Serial.print(timezone);

@JoeS thanks Joe but as shown in the screenshot I can get all that data already. I am after some data from today’s forecast. From the links I provided, with a valid API key, the forecast WITH conditions json starts with:

{
  "response": {
  "version":"0.1",
  "termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
  "features": {
  "conditions": 1
  ,
  "forecast": 1
  }
	}
  ,	"current_observation": {
		"image": {
		"url":"http://icons.wxug.com/graphics/wu2/logo_130x80.png",
		"title":"Weather Underground",
		"link":"http://www.wunderground.com"
		},
		"display_location": {

So you receive the current conditions followed by the forecast for the next few days. Tried various permutations but generally it leads to a reset of the ESP, possibly due to my mishandling of the arrays. I have found even pulling down the current conditions can lead to ESP resets caused by hundreds of rows of bytesIn 0 as you had previously. I think once everything is formatted correctly it is stable but it takes some time to get there.

In a practical use it might not be so bad but for testing purposes we are changing the location with Blynk and writing it back to memory with WiFi Manager etc.

Below is an extract from the parsing that does work for current weather conditions.

  // Parse JSON
  JsonObject& root = jsonBuffer.parseObject(json);
  if (!root.success()) {
    if (DEBUG){
      Serial.println(F("jsonBuffer.parseObject() failed"));
    }
    return false;
  }

  // Extract weather info from parsed JSON  (strcpy copies string into char array)
  JsonObject& current = root["current_observation"];
  strcpy(city, current["display_location"]["city"]);                            
  strcpy(latitude, current["display_location"]["latitude"]);                    
  strcpy(longitude, current["display_location"]["longitude"]);                  
  strcpy(mylocation, current["display_location"]["full"]);                      
  weather  = current["weather"];                                                
  temp_f = current["temp_f"];
  temp_c = current["temp_c"];
  strcpy(humidity, current["relative_humidity"]);
  strcpy(windgust, current["wind_gust_mph"]);

With JsonObject& current = root[“current_observation”]; being used for current weather conditions.
So presumably JsonObject& current = root[“forecast”]; would get the forecast data if we were just pulling down the forecast data, rather than current conditions and forecast. As shown in the json extract above there are ‘two roots’ when requesting current conditions and forecast.

Any ideas?

sorry I misunderstood the question, I believe it has something to do with these two lines of code.
static char respBuf[4096];
and
StaticJsonBuffer<3*1024> jsonBuffer;
but I haven’t gone back to try it yet, since my project works as needed without it.

@JoeS you might be right about the jsonBuffer size and thinking about it I will have problems with /conditions/forecast/ as it looks to be CONSIDERABLY bigger than 4096.

I will try pulling /conditions/ and then separately /forecast/ and see how that goes.