Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

IP Geolocation with an ESP32 and Arduino

5.00/5 (3 votes)
9 May 2024CPOL 6.2K  
Get location information based on your IP address using an ESP32 and ip-api.com
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

C++
#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

C++
#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) {
    // URL for IP resolution service
    char url[256];
    *url = 0;
    strcpy(url,"http://ip-api.com/json/?fields=status");//,region,city,lat,lon,timezone,offset";
    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) {
            // don't wait for end of document to terminate the connection
            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.

C++
#include <ip_loc.hpp>
...
long time_offset;
char time_zone_buffer[128];
...
// grabs the timezone and tz offset based on IP
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)