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...

", 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...

"); http.begin("http://"+ String(host) +"/admin/api.php?summary"); //HTTP int httpCode = http.GET(); if(httpCode > 0) { USE_SERIAL.printf("[HTTP] GET... code: %d

", 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

", http.errorToString(httpCode).c_str()); display.drawString(0, 20, "Cant Connect"); } } if((WiFi.status() == WL_CONNECTED)) { HTTPClient http2; USE_SERIAL.print("[HTTP] begin...

"); http2.begin("http://"+ String(host) +"/admin/api.php?recentBlocked"); //HTTP int httpCode2 = http2.GET(); if(httpCode2 > 0) { USE_SERIAL.printf("[HTTP] GET... code: %d

", 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

", 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

Connecting 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

"); 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