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