Display API info using an ESP32

Hey everyone!

Donated to our pi-hole devs 6 months ago. created a calendar reminder to remind myself to donate again... but curently unable to donate at the moment. This bothered the hell out of me, as pi-hole is the #1 tool i use every day at home... and its all in the background., no intervention required except for the occasional update or whitelist .so figured i would contribute in another way and post some code from a project i had some fun with.

Whats Used

  • ESP32 - $9
  • SH1106 OLED - $5
  • Power Supply - free/cheap
  • Arduino IDE (software) - free

What it is

I wanted something to sit on my desk and display some basic Pi-Hole stats.

Was introduced to the ESP8266 by espressif and had quite a bit of fun. Its a SoC (system of a chip) that has its own built in Wi-Fi (802.11 2.4Ghz a/b/g/n) and a ~200Mhz* processor with 4MB* of RAM. Was then introduced to the ESP32 family of SoC's, which builds upon the esp8266 but has a dual core processor, more memory, bluetooth, and other perks. This was my first project with the ESP32.
*specifications vary depending on model and manufacturer

The device boots up, connects to your wireless network, and the queries the Pi-Hole's API to get real time data. That data is then displayed on the OLED screen is a presentable format. Every 20 seconds, the ESP32 will query the Pi-Hole API, and update the data on the screen.

All the heavy working of displaying exactly what i wanted is taken care of by the awesome devs with the introduction of the API.

Replace 'pihole' below with that of your Pi-Hole and paste it into a browser for an example

http://pihole/admin/api.php?summary

Returns

{"domains_being_blocked":"107,265","dns_queries_today":"16,572","ads_blocked_today":"932","ads_percentage_today":"5.6","unique_domains":"1,166","queries_forwarded":"4,531","queries_cached":"11,107","clients_ever_seen":"8","unique_clients":"8"}

here's a link with more info on other commands that can request data over the Pi-Hole API. For example, overTimeData10mins could be used to add graphs, charts, etc. to this device!

The two we will be using are summary & recentBlocked.

summary - used for the above example, the data sent back to us is neatly presented to us in JSON format. This makes working with the data a super simple task. The Arduino IDE has a library called ArduinoJSON developed by Benoît Blanchon that makes parsing the data a freaking piece of cake.

recentBlocked - simply returns a string of the last pi-holed domain.

Wiring

OLED         ESP32
VIN    =>    5V
GND    =>    GND
SDA    =>    PIN 5
SCL    =>    PIN 4

Code

Make sure to replace the IP adddress with that of your Pi-Hole, as well as updating the SSID and PASSWORD fields to that of your wireless router.

Two common displays are the SH1106 & SSD1306. I included code for using both below, simply comment out the proper display for your situation.

/**
 *  esp32-PiHole-Stats.ino
 *  arejaywolfe@gmail.com
 *  
 *  Free for everyone to use, 
 *  modify, break & hack.
 */

// OLED SH1106
#include "SH1106.h"
SH1106Wire display(0x3c, 5, 4);    // OLED display object definition SH1106 (address, SDA, SCL)

// OLED SSD1306 -- uncomment next 2 lines if using SSD1306
// #include "SSD1306.h"
// SSD1306  display(0x3c, 5, 4); // OLED display object SSD1306 (address, SDA, SCL)

#include <ArduinoJson.h>
#include <WiFi.h>
#include <HTTPClient.h>
#define USE_SERIAL Serial

WiFiClient client; // wifi client object

const char* host         = "192.168.0.100";
const char* ssid         = "SSID";
const char* password     = "PASSWORD";

void setup() {
    display.init();
    display.display();
    USE_SERIAL.begin(115200);
    for(uint8_t t = 4; t > 0; t--) {
        USE_SERIAL.printf("[SETUP] WAIT %d...\n", t);
        USE_SERIAL.flush();
        delay(1000);
    }
    Start_WiFi(ssid,password);
}

void loop() {
       
   display.clear();
    
   if((WiFi.status() == WL_CONNECTED)) {
    HTTPClient http;
    USE_SERIAL.print("[HTTP] begin...\n");
    http.begin("http://"+ String(host) +"/admin/api.php?summary"); //HTTP
    int httpCode = http.GET();
    if(httpCode > 0) {
        USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
        if(httpCode == HTTP_CODE_OK) {
            String payload = http.getString();
            USE_SERIAL.println(payload);
            const size_t bufferSize = JSON_OBJECT_SIZE(9) + 230;
            DynamicJsonBuffer jsonBuffer(bufferSize);
            JsonObject& root = jsonBuffer.parseObject(payload);
            JsonObject& response = root["response"];
            JsonObject& response_data0 = response["data"][0];
            const char* domains_being_blocked = root["domains_being_blocked"]; // "107,265"
            const char* dns_queries_today = root["dns_queries_today"]; // "11,459"
            const char* ads_blocked_today = root["ads_blocked_today"]; // "607"
            const char* ads_percentage_today = root["ads_percentage_today"]; // "5.3"
            const char* unique_domains = root["unique_domains"]; // "1,073"
            const char* queries_forwarded = root["queries_forwarded"]; // "3,601"
            const char* queries_cached = root["queries_cached"]; // "7,247"
            const char* clients_ever_seen = root["clients_ever_seen"]; // "10"
            const char* unique_clients = root["unique_clients"]; // "10"
            Serial.print("Ads Blocked Today: ");
            Serial.println(ads_blocked_today);
            Serial.print("Domains Blocked: ");
            Serial.println(domains_being_blocked);
            Serial.print("Percentage of ads: ");
            Serial.println(ads_percentage_today);
            display.setFont(ArialMT_Plain_10);
            display.setTextAlignment(TEXT_ALIGN_LEFT);
            display.drawString(0, 0, "Ads Blocked Today: " + String(ads_blocked_today));
            display.drawString(0, 10, "Domains Blocked: " + String(domains_being_blocked));
            display.drawString(0, 20, "Percentage of Ads: " + String(ads_percentage_today) + "%");
            http.end();
            }
        } else {
            USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
            display.drawString(0, 20, "Cant Connect");
       }
    }

   if((WiFi.status() == WL_CONNECTED)) {
        HTTPClient http2;
        USE_SERIAL.print("[HTTP] begin...\n");
        http2.begin("http://"+ String(host) +"/admin/api.php?recentBlocked"); //HTTP
        int httpCode2 = http2.GET();
        if(httpCode2 > 0) {
        USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode2);
            if(httpCode2 == HTTP_CODE_OK) {
              String payload2 = http2.getString();
              USE_SERIAL.println(payload2);              
              display.drawString(0, 30, "Last Blocked:");
              display.drawString(0, 40, String(payload2));
              http2.end();
            }}
         else {
          USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http2.errorToString(httpCode2).c_str());
          display.drawString(0, 20, "Cant Connect");
     }}
        display.display();
        delay(20000);
}



int Start_WiFi(const char* ssid, const char* password){
    int connAttempts = 0;
    Serial.println("\r\nConnecting to: "+String(ssid));
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED ) {
        delay(500);
        Serial.print(".");
        if(connAttempts > 20) return -5;
        connAttempts++;
        }
    Serial.println("WiFi connected\r\n");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    return 1;
}

pictures

backside of the OLED and ESP32 modules. notice the SCL & SDA pins on the OLED module., referenced in the code. 

lighter next to unit to show size. tiny 1.3 inch screen! 

breakout board for the esp32, each pin # mapped out clearly. you can kinda see 
the usb port on the bottom side of the picture, which allows for plugging the chip 
directly to the PC via a usb cable. This allows for both powering and programming 
the esp32 

squirrel hunter 3000

3 Likes

Sigh, there's $9 I'm going to have to spend...!

Edit:

Are these the correct items?

http://www.dx.com/p/sh1106-oled-display-module-for-arduino-blue-black-428602

you got it!

lots of different companies make these esp32/esp8266 breakout boards, so the pin numbers may vary from manufacturer to manufacturer. GPIO port 2 on one board may be pin #2 on one board, and pin #4 on another board. just make sure you look at the pinout, the change the pin # in the code to whatever board you got =)

another option, you can buy an esp32 or esp8266 with an OLED board built into the board already. lots of different versions of this also, but the code will work with all by changing the pin numbers in the code. 'esp32 oled' or similiar search will point in ya in the right direction.

heres some pics from another project to display my plex server stats using an esp8266 board with the oled built in - this board was what got me introduced to the esp family, super easy to get setup and going. notice the OLED pin numbers printed nice and clearly on the back of the board for pasting into the code =)

edit: it made my day that one of yall devs liked the project!

Hi,

Thanks for sharing! Maybe you or one of the visitors can help me getting the right code to get the age in number of days from the JSON file:

"gravity_last_updated":{"file_exists":true,"absolute":1543420441,"relative":{"days":"0","hours":"22","minutes":"48"}}}

I inted to use the gravity last update info within the Arduino sketch. Hope to hear from you,

regards, Hans.,