Presenting a simple fetch class that talks to ip-api.com to get various information about your location with an ESP32 IoT widget
Introduction
I've produced this with a couple of projects but never really talked about it much, and I have since improved on the code. It deserves its own tip at least.
Without a GPS how do you get your time zone? NTP services don't provide it. The answer is IP geolocation, and http://ip-api.com fits the bill nicely.
Prerequisites
- You'll need VS Code w/ PlatformIO installed
- You'll need an ESP32
Using the code
The API is simple to use. You'll need to connect your WiFi before using it.
Add the following library to your project: codewitch-honey-crisis/htcw_json
Copy these files into your project:
ip_loc.hpp
#pragma once
#ifndef ESP32
#error "This library only supports the ESP32 MCU."
#endif
#include <Arduino.h>
namespace arduino {
struct ip_loc final {
static bool fetch(float* out_lat,
float* out_lon,
long* out_utc_offset,
char* out_region,
size_t region_size,
char* out_city,
size_t city_size,
char* out_time_zone,
size_t time_zone_size);
};
}
ip_loc.cpp
#ifdef ESP32
#include <ip_loc.hpp>
#include <HTTPClient.h>
#include <json.hpp>
namespace arduino {
static char* ip_loc_fetch_replace_char(char* str, char find, char replace){
char *current_pos = strchr(str,find);
while (current_pos) {
*current_pos = replace;
current_pos = strchr(current_pos+1,find);
}
return str;
}
bool ip_loc::fetch(float* out_lat,
float* out_lon,
long* out_utc_offset,
char* out_region,
size_t region_size,
char* out_city,
size_t city_size,
char* out_time_zone,
size_t time_zone_size) {
char url[256];
*url = 0;
strcpy(url,"http://ip-api.com/json/?fields=status"); int count = 0;
if(out_lat!=nullptr) {
*out_lat = 0.0f;
strcat(url,",lat");
++count;
}
if(out_lon!=nullptr) {
*out_lon = 0.0f;
strcat(url,",lon");
++count;
}
if(out_utc_offset!=nullptr) {
*out_utc_offset = 0;
strcat(url,",offset");
++count;
}
if(out_region!=nullptr && region_size>0) {
*out_region = 0;
strcat(url,",region");
++count;
}
if(out_city!=nullptr && city_size>0) {
*out_city = 0;
strcat(url,",city");
++count;
}
if(out_time_zone!=nullptr && time_zone_size>0) {
*out_time_zone = 0;
strcat(url,",timezone");
++count;
}
HTTPClient client;
client.begin(url);
if(0>=client.GET()) {
return false;
}
Stream& stm = client.getStream();
io::arduino_stream astm(&stm);
json::json_reader_ex<512> reader(astm);
while(reader.read()) {
if(reader.depth()==1 && reader.node_type()==json::json_node_type::field) {
if(out_lat!=nullptr && 0==strcmp("lat",reader.value())) {
reader.read();
*out_lat = reader.value_real();
--count;
} else if(out_lon!=nullptr && 0==strcmp("lon",reader.value())) {
reader.read();
*out_lon = reader.value_real();
--count;
} else if(out_utc_offset!=nullptr && 0==strcmp("offset",reader.value())) {
reader.read();
*out_utc_offset = reader.value_int();
--count;
} else if(out_region!=nullptr && region_size>0 && 0==strcmp("region",reader.value())) {
reader.read();
strncpy(out_region,reader.value(),region_size);
--count;
} else if(out_city!=nullptr && city_size > 0 && 0==strcmp("city",reader.value())) {
reader.read();
strncpy(out_city,reader.value(),city_size);
--count;
} else if(out_time_zone!=nullptr && time_zone_size>0 && 0==strcmp("timezone",reader.value())) {
reader.read();
strncpy(out_time_zone, reader.value(),time_zone_size);
ip_loc_fetch_replace_char(out_time_zone,'_',' ');
--count;
}
} else if(count<1 || reader.depth()==0) {
break;
}
}
client.end();
return true;
}
}
#endif
To use it you call ip_loc::fetch()
passing nulls and zeroes for any arguments you don't need, but otherwise providing buffers for the out data. The string data requires you to tell it how big the buffer is.
#include <ip_loc.hpp>
...
long time_offset;
char time_zone_buffer[128];
...
arduino::ip_loc::fetch(
nullptr,
nullptr,
&time_offset,
nullptr,
0,
nullptr,
0,
time_zone_buffer,
sizeof(time_zone_buffer)
);
History
- 9th May, 2024 - Initial submission