Pi-Hole info on an OLED display

the following is a copy/paste from a post i made to the pi-hole forums a few weeks back.

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 most often 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"];
            const char* dns_queries_today = root["dns_queries_today"];
            const char* ads_blocked_today = root["ads_blocked_today"];
            const char* ads_percentage_today = root["ads_percentage_today"];
            const char* unique_domains = root["unique_domains"];
            const char* queries_forwarded = root["queries_forwarded"];
            const char* queries_cached = root["queries_cached"];
            const char* clients_ever_seen = root["clients_ever_seen"];
            const char* unique_clients = root["unique_clients"];
            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.
pihole_2

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

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
pihole_4

squirrel hunter 3000
pihole_5