Pi-Hole Integration

I think that was just something that happened when i was posting here, i checked and have no extra spaces in my .items file. Still a no go tho.

@kubawolanin awesome tutorial! Thank you for sharing it.
I’ve made a small adjustement in the formating to make the larger number easier to read.

"Domains being blocked [%,d\n]"

This format will allow to have thousand separators.

Could anyone help me to get this running in Openhab 3 with the HTTP binding 3.0?

I’ve created a new thing for the Pihole which is online:

Thing http:url:pihole "Pihole" @ "Systeme" [ baseURL="http://192.168.xxx.xxx/admin/api.php", refresh=60 ]

But i don’t get the right syntax for the items. For the HTTP binding v1 it was:

{http="<[pihole:100000:JSONPATH($.domains_being_blocked)]"}

for the v2 binding it has to start with channel="http:url:pihole...."

Any hint?

Greetings and thanks,
Huaba

I did the integration via the UI, but maybe this is of some help for you

Here is my Thing-Code

UID: http:url:pihole
label: pihole
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: false
  baseURL: http://<YOUR-PIHOLE-IP/admin/api.php
  refresh: 30
  commandMethod: GET
  contentType: application/json
  timeout: 3000
channels:
  - id: domains_being_blocked
    channelTypeUID: http:string
    label: Domains being blocked
    description: ""
    configuration: {}
  - id: dns_queries_today
    channelTypeUID: http:string
    label: DNS Queries today
    description: ""
    configuration: {}
  - id: ads_blocked_today
    channelTypeUID: http:string
    label: Ads blocked today
    description: ""
    configuration: {}
  - id: ads_percentage_today
    channelTypeUID: http:string
    label: Ads percentage today
    description: ""
    configuration: {}
  - id: unique_domains
    channelTypeUID: http:string
    label: Unique Domains
    description: ""
    configuration: {}
  - id: queries_forwarded
    channelTypeUID: http:string
    label: Queries forwarded
    description: ""
    configuration: {}
  - id: queries_cached
    channelTypeUID: http:string
    label: Queries cached
    description: ""
    configuration: {}
  - id: clients_ever_seen
    channelTypeUID: http:string
    label: Clients ever seen
    description: ""
    configuration: {}
  - id: unique_clients
    channelTypeUID: http:string
    label: Unique Clients
    description: ""
    configuration: {}
  - id: Pihole_Status
    channelTypeUID: http:string
    label: PI-Hole Status
    description: ""
    configuration: {}

And adding an Item looks like this:

3 Likes

Very late to this party, but here’s my working pihole integration in OH3 using configuration files:

Things

//THING
Thing http:url:pihole1 "Pihole1" [
	baseURL = "http://YOUR_IP_HERE/admin/api.php",
	refresh = "120",
	timeout ="5000",
	ignoreSSLErrors = "true"
]
{
	Channels:
		Type number : domains_being_blocked [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.domains_being_blocked"
		]
		Type number : dns_queries_today [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.dns_queries_today"
		]
		Type number : ads_blocked_today [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.ads_blocked_today"
		]
		Type number : ads_percentage_today [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.ads_percentage_today"
		]
		Type number : unique_domains [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.unique_domains"
		]
		Type number : queries_forwarded [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.queries_forwarded"
		]
		Type number : queries_cached [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.queries_cached"
		]
		Type number : clients_ever_seen [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.clients_ever_seen"
		]
		Type number : unique_clients [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.unique_clients"
		]
		Type number : dns_queries_all_types [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.dns_queries_all_types"
		]
		Type number : reply_NODATA [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.reply_NODATA"
		]
		Type number : reply_NXDOMAIN [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.reply_NXDOMAIN"
		]
		Type number : reply_CNAME [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.reply_CNAME"
		]
		Type number : reply_IP [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.reply_IP"
		]
		Type number : privacy_level [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.privacy_level"
		]
		Type switch : status [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.status",
			onValue = "enabled",
			offValue = "disabled"
		]
		Type switch : enable_disable [
			mode = "WRITEONLY",
			commandExtension = "?%2$s&auth=YOUR_API_KEY_HERE",
			onValue = "enable",
			offValue = "disable"		
		]
		//GRAVITY LAST UPDATED
		Type string : file_exists [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.gravity_last_updated.file_exists"
		]
		//ABSOLUTE TIME OF LAST UPDATE
		Type number : absolute [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.gravity_last_updated.absolute"
		]
		//RELATIVE TIME OF LAST UPDATE
		Type number : days [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.gravity_last_updated.relative.days"
		]
		Type number : hours [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.gravity_last_updated.relative.hours"
		]
		Type number : minutes [
			mode = "READONLY",
			stateTransformation = "JSONPATH:$.gravity_last_updated.relative.minutes"
		]
}

Items

Number nPihole1_domains_being_blocked { channel="http:url:pihole1:domains_being_blocked" }
Number nPihole1_dns_queries_today { channel="http:url:pihole1:dns_queries_today" }
Number nPihole1_ads_blocked_today { channel="http:url:pihole1:ads_blocked_today" }
Number nPihole1_ads_percentage_today { channel="http:url:pihole1:ads_percentage_today" }
Number nPihole1_unique_domains { channel="http:url:pihole1:unique_domains" }
Number nPihole1_queries_forwarded { channel="http:url:pihole1:queries_forwarded" }
Number nPihole1_queries_cached { channel="http:url:pihole1:queries_cached" }
Number nPihole1_clients_ever_seen { channel="http:url:pihole1:clients_ever_seen" }
Number nPihole1_unique_clients { channel="http:url:pihole1:unique_clients" }
Number nPihole1_dns_queries_all_types { channel="http:url:pihole1:dns_queries_all_types" }
Number nPihole1_reply_NODATA { channel="http:url:pihole1:reply_NODATA" }
Number nPihole1_reply_NXDOMAIN { channel="http:url:pihole1:reply_NXDOMAIN" }
Number nPihole1_reply_CNAME { channel="http:url:pihole1:reply_CNAME" }
Number nPihole1_reply_IP { channel="http:url:pihole1:reply_IP" }
Number nPihole1_privacy_level { channel="http:url:pihole1:privacy_level" }
Switch sPihole1_status { channel="http:url:pihole1:status" }
Switch sPihole1_enable_disable { channel="http:url:pihole1:enable_disable", channel="http:url:pihole1:status" }
String strPihole1_file_exists { channel="http:url:pihole1:file_exists" }
Number nPihole1_absolute { channel="http:url:pihole1:absolute" }
Number nPihole1_days { channel="http:url:pihole1:days" }
Number nPihole1_hours { channel="http:url:pihole1:hours" }
Number nPihole1_minutes { channel="http:url:pihole1:minutes" }

This configuration provides a Switch Item sPihole1_enable_disable which you can use to enable and disable pihole from within openHAB. This switch Item is also linked to the status Channel, so it will update if pihole is enabled/disabled by another source too.

6 Likes

I cut most of my Pi-Hole integration (I wasn’t ever looking at it) and just kept the switch for enabling/disabling. However, the thing I use most is a browser bookmark to the Pi-Hole API that disables it for five minutes.

http://SERVER/admin/api.php?disable=300&auth=xxxxxxxx
3 Likes

Very helpful. I used this method to create a page to mirror PiHole landing page.

2 Likes

Can you share the images and code as well @kjknauss very appreciated:)

Not much to it. Just a bunch of label cards with F7 icons. Last card is a switch that when toggled fires a rule similar to the one @rkrisi contributed above. Just update with your items…

config:
  label: PiHole
  sidebar: false
blocks:
  - component: oh-block
    config: {}
    slots:
      default:
        - component: oh-grid-row
          config: {}
          slots:
            default:
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        background: green
                        fontSize: 34px
                        fontWeight: bold
                        icon: f7:globe
                        item: 
                        title: Total Queries
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        background: aqua
                        fontSize: 34px
                        fontWeight: bold
                        icon: f7:hand_raised_slash
                        item: 
                        title: Queries Blocked
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        background: orange
                        fontSize: 34px
                        fontWeight: bold
                        icon: f7:chart_pie
                        item: 
                        title: Percent Blocked
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        background: red
                        fontSize: 34px
                        fontWeight: bold
                        icon: f7:list_dash
                        item: 
                        title: Domains on Blocklist
  - component: oh-block
    config: {}
    slots:
      default:
        - component: oh-grid-row
          config: {}
          slots:
            default:
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        action: analyzer
                        actionAnalyzerItems:
                          - 
                        background: green
                        fontSize: 34px
                        fontWeight: bold
                        item: 
                        title: Gravity Update Days
                        trendItem: 
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        action: analyzer
                        actionAnalyzerItems:
                          - 
                        background: aqua
                        fontSize: 34px
                        fontWeight: bold
                        item: 
                        title: Queries Cached
                        trendItem: 
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        action: analyzer
                        actionAnalyzerItems:
                          - 
                        background: orange
                        fontSize: 34px
                        fontWeight: bold
                        item: 
                        title: Unique Domains
                        trendItem: 
              - component: oh-grid-col
                config:
                  width: "25"
                slots:
                  default:
                    - component: oh-label-card
                      config:
                        action: toggle
                        actionCommand: ON
                        actionCommandAlt: OFF
                        background: red
                        fontSize: 34px
                        fontWeight: bold
                        item: 
                        title: PiHole Status
                        actionItem: 
masonry: []
grid: null

Hello all,

just a trackback

2 Likes

with the latest update, its not working anymore for me.

Now you need to change baseURL in thing definition

from

baseURL="http://pihole.IP.address/admin/api.php

to

baseURL="http://pihole.IP.address/admin/api.php?summary&auth=API-TOKEN-goes-here"

You can get the token from Settings/API/Show API token or from /etc/pihole/setupVars.conf (WEBPASSWORD).

3 Likes

Hey @kristofejro i also noticed this and thats why i added a second pihole-cmd thing for the actual changes in the block mode.
For those, who are interested in accessing a pihole instance which is behind cloudflare access, please check out my comment on that topic:

Thanks to this thread and the information provided by the community, I integrated the current Pi-Hole version in OpenHab using the latest HTTP Binding. Preconditions and requirements are:

  • HTTP-Binding installed (Settings → Add-ons → Bindings)
  • JSONPath-Transformation installed (Settings → Add-ons → Transformations)

I just wanted to give back and provide my “Things” configuration here for others to benefit from:

Thing http:url:pihole "PiHole" [baseURL="http://PI-HOLE_IP:PI-HOLE_PORT/admin/api.php", refresh=30, timeout=3000] {
		Channels:
			Type switch : enable_disable "PiHole Command (Enable/Disable)" [
				mode = "WRITEONLY",
				commandExtension = "?%2$s=0&auth=API_KEY_HERE",
				onValue = "enable",
				offValue = "disable"		
			]
			Type switch : status "PiHole State (read-only)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.status",
				onValue = "enabled",
				offValue = "disabled"
			]
			Type number : dns_queries_today "Total Queries (Today)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.dns_queries_today"
			]
			Type number : ads_blocked_today "Queries Blocked (Today)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.ads_blocked_today"
			]
			Type number : ads_percentage_today "Percentage Blocked (Today)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.ads_percentage_today"
			]
			Type number : domains_being_blocked "Domains on Blocklist (Total)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.domains_being_blocked"
			]
			Type number : unique_domains "Domains on Blacklist (Unique)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.unique_domains"
			]
			Type number : queries_forwarded "Queries resolved via upstream DNS Server" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.queries_forwarded"
			]
			Type number : queries_cached "Queries resolved via local DNS Cache/Config" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.queries_cached"
			]
			Type number : clients_ever_seen "Clients seen (Total)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.clients_ever_seen"
			]
			Type number : unique_clients "Clients seen (Unique)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.unique_clients"
			]
			Type number : dns_queries_all_types "DNS Queries (All Types)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.dns_queries_all_types"
			]
			Type number : reply_NODATA "DNS Replies (NODATA)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.reply_NODATA"
			]
			Type number : reply_NXDOMAIN "DNS Replies (NXDOMAIN)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.reply_NXDOMAIN"
			]
			Type number : reply_CNAME "DNS Replies (CNAME)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.reply_CNAME"
			]
			Type number : reply_IP "DNS Replies (IP)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.reply_IP"
			]
			Type number : privacy_level "Statitics Privacy Level (0=Anonymize none,1=no domains,2=no domains/clients,3=Anonymize all)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.privacy_level"
			]
			Type string : file_exists "Gravity updated" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.gravity_last_updated.file_exists"
			]
			Type number : absolute "Gravity update duration" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.gravity_last_updated.absolute"
			]
			Type number : days "Gravity time since last update (days)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.gravity_last_updated.relative.days"
			]
			Type number : hours "Gravity time since last update (days)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.gravity_last_updated.relative.hours"
			]
			Type number : minutes "Gravity time since last update (days)" [
				mode = "READONLY",
				stateExtension = "?summary&auth=API_KEY_HERE",
				stateTransformation = "JSONPATH:$.gravity_last_updated.relative.minutes"
			]
}
1 Like

You can even make the state a single read/write channel (sorry only got the code from MainUI):

channels:
  - id: status
    channelTypeUID: http:switch
    label: Status
    description: ""
    configuration:
      onValue: enabled
      commandTransformation: MAP:piholestatus.map
      offValue: disabled
      stateExtension: /admin/api.php?summaryRaw&auth=AUTHKEY
      commandExtension: /admin/api.php?%2$s&auth= AUTHKEY
      stateTransformation: JSONPATH:$.status

With piholestatus.map:

enabled=enable
disabled=disable
1 Like

Nice, did that by mapping command and the state channel to one item. But your way is more elegant, will try to set this up in text. Thanks for the hint.

Does somebody use the new Beta 6 version of pi-hole?
Need some help with the new api :frowning:

Hello, yes I am using it…which help do you need?

how to configurate with the new api key :sweat_smile:

This is the api doc

https://ftl.pi-hole.net/development-v6/docs/

For the password itself, go to settings and “app password” generate one or use your normal password

  1. Generate a session with
curl -X POST "https://pi.hole:443/api/auth" \
 -H "accept: application/json"\
 -H "content-type: application/json" \
 -d '{"password":"abcdef"}' 
  1. Do what ever you wanna do

It wasn’t possible for me to implement it…but Christmas is coming so maybe I find a little bit of time

Here is the beginning @_tobi

rules.JSRule({
  name: "pihole API rule",
  description: "Access to pihole API",
  triggers: [triggers.GenericCronTrigger("0/10 * * * * ? *")],
  execute: (event) => {
		var resposne;
		var sessionId;
		var responseData;

		response = actions.HTTP.sendHttpGetRequest('http://pi.hole:8080/api/auth', {"accept": "application/json"}, 5000);

                if (response != null) {
 			responseData = JSON.parse(response);
			if (responseData.session.sid == null) {
				actions.Log.logInfo("pihole API rule", "No open session");
		                
				response = actions.HTTP.sendHttpPostRequest('http://pihole.fritz.box:8080/api/auth', 'application/json', '{"password":"{yourpassword"}', {"accept": "application/json"}, 5000);
				if (response != null) {
					responseData = JSON.parse(response);
			                if (responseData.session.sid != null) {
        			                actions.Log.logInfo("pihole API rule", "Session started");
                			} else {
                        			actions.Log.logError("pihole API rule","Error during session start");
						if (response != null) {
		                        		actions.Log.logError("pihole API rule", response);
						}
        	        		}
				} else {
					actions.Log.logError("pihole API rule","Error during API request");
				}
			} else {
				sessionId = responseData.session.sid;
				actions.Log.logInfo("pihole API rule", "There is a session open with ID");
				actions.Log.logInfo("pihole API rule", sessionId);

			}
		} else {
			actions.Log.logError("pihole API rule","Error during API request");
		}
                console.log(response);                      
        },
  tags: ["pihole", "pihole api"],
  id: "pihole API rule"
});