Skip to content

Commit 4f322cc

Browse files
authored
[pihole] New binding PiHole (openhab#16627)
* Init Pi-hole binding Signed-off-by: Martin Grześlowski <[email protected]>
1 parent 7755681 commit 4f322cc

File tree

22 files changed

+1506
-0
lines changed

22 files changed

+1506
-0
lines changed

CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
/bundles/org.openhab.binding.pegelonline/ @weymann
283283
/bundles/org.openhab.binding.pentair/ @jsjames
284284
/bundles/org.openhab.binding.phc/ @gnlpfjh
285+
/bundles/org.openhab.binding.pihole/ @magx2
285286
/bundles/org.openhab.binding.pilight/ @stefanroellin @niklasdoerfler
286287
/bundles/org.openhab.binding.pioneeravr/ @Stratehm
287288
/bundles/org.openhab.binding.pixometer/ @Confectrician

bom/openhab-addons/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,11 @@
14011401
<artifactId>org.openhab.binding.phc</artifactId>
14021402
<version>${project.version}</version>
14031403
</dependency>
1404+
<dependency>
1405+
<groupId>org.openhab.addons.bundles</groupId>
1406+
<artifactId>org.openhab.binding.pihole</artifactId>
1407+
<version>${project.version}</version>
1408+
</dependency>
14041409
<dependency>
14051410
<groupId>org.openhab.addons.bundles</groupId>
14061411
<artifactId>org.openhab.binding.pilight</artifactId>
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
This content is produced and maintained by the openHAB project.
2+
3+
* Project home: https://www.openhab.org
4+
5+
== Declared Project Licenses
6+
7+
This program and the accompanying materials are made available under the terms
8+
of the Eclipse Public License 2.0 which is available at
9+
https://www.eclipse.org/legal/epl-2.0/.
10+
11+
== Source Code
12+
13+
https://github.com/openhab/openhab-addons
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Pi-hole Binding
2+
3+
The Pi-hole Binding is a bridge between openHAB and Pi-hole, enabling users to integrate Pi-hole statistics and controls into their home automation setup. Pi-hole is a DNS-based ad blocker that can run on a variety of platforms, including Raspberry Pi.
4+
5+
Pi-hole is a powerful network-level advertisement and internet tracker blocking application.
6+
By intercepting DNS requests, it can prevent unwanted content from being displayed on devices connected to your network.
7+
The Pi-hole Binding allows you to monitor Pi-hole statistics and control its functionality directly from your openHAB setup.
8+
9+
### Features
10+
11+
- Real-time Statistics: Monitor key metrics such as the number of domains being blocked, DNS queries made today, ads blocked today, and more.
12+
- Control: Enable or disable Pi-hole's blocking functionality, configure blocking options, and adjust privacy settings directly from openHAB.
13+
- Integration: Seamlessly integrate Pi-hole data and controls with other openHAB items and rules to create advanced automation scenarios.
14+
15+
## Supported Things
16+
17+
- `server`: Pi-hole server
18+
19+
## Thing Configuration
20+
21+
### `server` Thing Configuration
22+
23+
| Name | Type | Description | Default | Required | Advanced |
24+
|-----------------|---------|-------------------------------------------------------------------------------------------|---------|----------|----------|
25+
| hostname | text | Hostname or IP address of the device | N/A | yes | no |
26+
| token | text | Token to access the device. To generate token go to `settings` > `API` > `Show API token` | N/A | yes | no |
27+
| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes |
28+
29+
## Channels
30+
31+
| Channel | Type | Read/Write | Description |
32+
|-------------------------|--------|------------|------------------------------------------------------------|
33+
| domains-being-blocked | Number | RO | The total number of domains currently being blocked. |
34+
| dns-queries-today | Number | RO | The count of DNS queries made today. |
35+
| ads-blocked-today | Number | RO | The number of ads blocked today. |
36+
| ads-percentage-today | Number | RO | The percentage of ads blocked today. |
37+
| unique-domains | Number | RO | The count of unique domains queried. |
38+
| queries-forwarded | Number | RO | The number of queries forwarded to an external DNS server. |
39+
| queries-cached | Number | RO | The number of queries served from the cache. |
40+
| clients-ever-seen | Number | RO | The total number of unique clients ever seen. |
41+
| unique-clients | Number | RO | The current count of unique clients. |
42+
| dns-queries-all-types | Number | RO | The total number of DNS queries of all types. |
43+
| reply-unknown | Number | RO | DNS replies with an unknown status. |
44+
| reply-nodata | Number | RO | DNS replies indicating no data. |
45+
| reply-nxdomain | Number | RO | DNS replies indicating non-existent domain. |
46+
| reply-cname | Number | RO | DNS replies with a CNAME record. |
47+
| reply-ip | Number | RO | DNS replies with an IP address. |
48+
| reply-domain | Number | RO | DNS replies with a domain name. |
49+
| reply-rrname | Number | RO | DNS replies with a resource record name. |
50+
| reply-servfail | Number | RO | DNS replies indicating a server failure. |
51+
| reply-refused | Number | RO | DNS replies indicating refusal. |
52+
| reply-notimp | Number | RO | DNS replies indicating not implemented. |
53+
| reply-other | Number | RO | DNS replies with other statuses. |
54+
| reply-dnssec | Number | RO | DNS replies with DNSSEC information. |
55+
| reply-none | Number | RO | DNS replies with no data. |
56+
| reply-blob | Number | RO | DNS replies with a BLOB (binary large object). |
57+
| dns-queries-all-replies | Number | RO | The total number of DNS queries with all reply types. |
58+
| privacy-level | Number | RO | The privacy level setting. |
59+
| enabled | Switch | RO | The current status of blocking |
60+
| disable-enable | String | RW | Is blocking enabled/disabled |
61+
62+
## Full Example
63+
64+
### Thing Configuration
65+
66+
```java
67+
Thing pihole:server:a4a077edb8 "Pi-hole" @ "Location"
68+
[
69+
refreshIntervalSeconds=600,
70+
hostname="http://123.456.7.89",
71+
token="as654gadf3h1dsfh654dfh6fh7et654asd3g21fh654eth8t4swd4g3s1g65sfg5"
72+
] {
73+
Channels:
74+
Type number : domains_being_blocked "Domains Blocked" [ ]
75+
Type number : dns_queries_today "DNS Queries Today" [ ]
76+
Type number : ads_blocked_today "Ads Blocked Today" [ ]
77+
Type number : ads_percentage_today "Ads Percentage Today" [ ]
78+
Type number : unique_domains "Unique Domains" [ ]
79+
Type number : queries_forwarded "Queries Forwarded" [ ]
80+
Type number : queries_cached "Queries Cached" [ ]
81+
Type number : clients_ever_seen "Clients Ever Seen" [ ]
82+
Type number : unique_clients "Unique Clients" [ ]
83+
Type number : dns_queries_all_types "DNS Queries (All Types)" [ ]
84+
Type number : reply_UNKNOWN "Reply UNKNOWN" [ ]
85+
Type number : reply_NODATA "Reply NODATA" [ ]
86+
Type number : reply_NXDOMAIN "Reply NXDOMAIN" [ ]
87+
Type number : reply_CNAME "Reply CNAME" [ ]
88+
Type number : reply_IP "Reply IP" [ ]
89+
Type number : reply_DOMAIN "Reply DOMAIN" [ ]
90+
Type number : reply_RRNAME "Reply RRNAME" [ ]
91+
Type number : reply_SERVFAIL "Reply SERVFAIL" [ ]
92+
Type number : reply_REFUSED "Reply REFUSED" [ ]
93+
Type number : reply_NOTIMP "Reply NOTIMP" [ ]
94+
Type number : reply_OTHER "Reply OTHER" [ ]
95+
Type number : reply_DNSSEC "Reply DNSSEC" [ ]
96+
Type number : reply_NONE "Reply NONE" [ ]
97+
Type number : reply_BLOB "Reply BLOB" [ ]
98+
Type number : dns_queries_all_replies "DNS Queries (All Replies)" [ ]
99+
Type number : privacy_level "Privacy Level" [ ]
100+
Type switch : enabled "Status" [ ]
101+
Type string : disable-enable "Disable Blocking" [ ]
102+
}
103+
```
104+
105+
### Item Configuration
106+
107+
```java
108+
Number domains_being_blocked "Domains Blocked" { channel="pihole:server:a4a077edb8:domains_being_blocked" }
109+
Number dns_queries_today "DNS Queries Today" { channel="pihole:server:a4a077edb8:dns_queries_today" }
110+
Number ads_blocked_today "Ads Blocked Today" { channel="pihole:server:a4a077edb8:ads_blocked_today" }
111+
Number ads_percentage_today "Ads Percentage Today" { channel="pihole:server:a4a077edb8:ads_percentage_today" }
112+
Number unique_domains "Unique Domains" { channel="pihole:server:a4a077edb8:unique_domains" }
113+
Number queries_forwarded "Queries Forwarded" { channel="pihole:server:a4a077edb8:queries_forwarded" }
114+
Number queries_cached "Queries Cached" { channel="pihole:server:a4a077edb8:queries_cached" }
115+
Number clients_ever_seen "Clients Ever Seen" { channel="pihole:server:a4a077edb8:clients_ever_seen" }
116+
Number unique_clients "Unique Clients" { channel="pihole:server:a4a077edb8:unique_clients" }
117+
Number dns_queries_all_types "DNS Queries (All Types)" { channel="pihole:server:a4a077edb8:dns_queries_all_types" }
118+
Number reply_UNKNOWN "Reply UNKNOWN" { channel="pihole:server:a4a077edb8:reply_UNKNOWN" }
119+
Number reply_NODATA "Reply NODATA" { channel="pihole:server:a4a077edb8:reply_NODATA" }
120+
Number reply_NXDOMAIN "Reply NXDOMAIN" { channel="pihole:server:a4a077edb8:reply_NXDOMAIN" }
121+
Number reply_CNAME "Reply CNAME" { channel="pihole:server:a4a077edb8:reply_CNAME" }
122+
Number reply_IP "Reply IP" { channel="pihole:server:a4a077edb8:reply_IP" }
123+
Number reply_DOMAIN "Reply DOMAIN" { channel="pihole:server:a4a077edb8:reply_DOMAIN" }
124+
Number reply_RRNAME "Reply RRNAME" { channel="pihole:server:a4a077edb8:reply_RRNAME" }
125+
Number reply_SERVFAIL "Reply SERVFAIL" { channel="pihole:server:a4a077edb8:reply_SERVFAIL" }
126+
Number reply_REFUSED "Reply REFUSED" { channel="pihole:server:a4a077edb8:reply_REFUSED" }
127+
Number reply_NOTIMP "Reply NOTIMP" { channel="pihole:server:a4a077edb8:reply_NOTIMP" }
128+
Number reply_OTHER "Reply OTHER" { channel="pihole:server:a4a077edb8:reply_OTHER" }
129+
Number reply_DNSSEC "Reply DNSSEC" { channel="pihole:server:a4a077edb8:reply_DNSSEC" }
130+
Number reply_NONE "Reply NONE" { channel="pihole:server:a4a077edb8:reply_NONE" }
131+
Number reply_BLOB "Reply BLOB" { channel="pihole:server:a4a077edb8:reply_BLOB" }
132+
Number dns_queries_all_replies "DNS Queries (All Replies)" { channel="pihole:server:a4a077edb8:dns_queries_all_replies" }
133+
Number privacy_level "Privacy Level" { channel="pihole:server:a4a077edb8:privacy_level" }
134+
Switch enabled "Status" { channel="pihole:server:a4a077edb8:enabled" }
135+
String disable_enable "Disable Blocking" { channel="pihole:server:a4a077edb8:disable-enable" }
136+
```
137+
138+
### Actions
139+
140+
Pi-hole binding provides actions to use in rules:
141+
142+
```java
143+
import java.util.concurrent.TimeUnit
144+
145+
rule "test"
146+
when
147+
/* when */
148+
then
149+
val actions = getActions("pihole", "pihole:server:as8af03m38")
150+
if (actions !== null) {
151+
// disable blocking for 5 * 60 seconds (5 minutes)
152+
actions.disableBlocking(5 * 60)
153+
154+
// disable blocking for 5 minutes
155+
actions.disableBlocking(5, TimeUnit.MINUTES)
156+
157+
// disable blocking for infinity
158+
actions.disableBlocking(0)
159+
actions.disableBlocking()
160+
161+
// enable blocking
162+
actions.enableBlocking()
163+
}
164+
end
165+
```
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.openhab.addons.bundles</groupId>
9+
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
10+
<version>4.3.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>org.openhab.binding.pihole</artifactId>
14+
15+
<name>openHAB Add-ons :: Bundles :: Pi-hole Binding</name>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.assertj</groupId>
20+
<artifactId>assertj-core</artifactId>
21+
<version>3.25.3</version>
22+
<scope>test</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.mockito</groupId>
26+
<artifactId>mockito-core</artifactId>
27+
<version>5.11.0</version>
28+
<scope>test</scope>
29+
</dependency>
30+
</dependencies>
31+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<features name="org.openhab.binding.pihole-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
3+
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
4+
5+
<feature name="openhab-binding-pihole" description="Pi-hole Binding" version="${project.version}">
6+
<feature>openhab-runtime-base</feature>
7+
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pihole/${project.version}</bundle>
8+
</feature>
9+
</features>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.pihole.internal;
14+
15+
import static java.util.Objects.requireNonNull;
16+
import static java.util.concurrent.TimeUnit.SECONDS;
17+
import static org.openhab.binding.pihole.internal.PiHoleBindingConstants.BINDING_ID;
18+
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.eclipse.jdt.annotation.NonNullByDefault;
22+
import org.eclipse.jdt.annotation.Nullable;
23+
import org.openhab.core.automation.annotation.ActionInput;
24+
import org.openhab.core.automation.annotation.RuleAction;
25+
import org.openhab.core.thing.binding.ThingActions;
26+
import org.openhab.core.thing.binding.ThingActionsScope;
27+
import org.openhab.core.thing.binding.ThingHandler;
28+
29+
/**
30+
* @author Martin Grzeslowski - Initial contribution
31+
*/
32+
@ThingActionsScope(name = BINDING_ID)
33+
@NonNullByDefault
34+
public class PiHoleActions implements ThingActions {
35+
private @Nullable PiHoleHandler handler;
36+
37+
@Override
38+
public void setThingHandler(@Nullable ThingHandler handler) {
39+
this.handler = (PiHoleHandler) handler;
40+
}
41+
42+
@Override
43+
public @Nullable ThingHandler getThingHandler() {
44+
return handler;
45+
}
46+
47+
@RuleAction(label = "@text/action.disable.label", description = "@text/action.disable.description")
48+
public void disableBlocking(
49+
@ActionInput(name = "time", label = "@text/action.disable.timeLabel", description = "@text/action.disable.timeDescription") long time,
50+
@ActionInput(name = "timeUnit", label = "@text/action.disable.timeUnitLabel", description = "@text/action.disable.timeUnitDescription") @Nullable TimeUnit timeUnit)
51+
throws PiHoleException {
52+
if (time < 0) {
53+
return;
54+
}
55+
56+
if (timeUnit == null) {
57+
timeUnit = SECONDS;
58+
}
59+
60+
var local = handler;
61+
if (local == null) {
62+
return;
63+
}
64+
local.disableBlocking(timeUnit.toSeconds(time));
65+
}
66+
67+
public static void disableBlocking(@Nullable ThingActions actions, long time, @Nullable TimeUnit timeUnit)
68+
throws PiHoleException {
69+
((PiHoleActions) requireNonNull(actions)).disableBlocking(time, timeUnit);
70+
}
71+
72+
@RuleAction(label = "@text/action.disable.label", description = "@text/action.disable.description")
73+
public void disableBlocking(
74+
@ActionInput(name = "time", label = "@text/action.disable.timeLabel", description = "@text/action.disable.timeDescription") long time)
75+
throws PiHoleException {
76+
disableBlocking(time, null);
77+
}
78+
79+
public static void disableBlocking(@Nullable ThingActions actions, long time) throws PiHoleException {
80+
((PiHoleActions) requireNonNull(actions)).disableBlocking(time);
81+
}
82+
83+
@RuleAction(label = "@text/action.disableInf.label", description = "@text/action.disableInf.description")
84+
public void disableBlocking() throws PiHoleException {
85+
disableBlocking(0, null);
86+
}
87+
88+
public static void disableBlocking(@Nullable ThingActions actions) throws PiHoleException {
89+
((PiHoleActions) requireNonNull(actions)).disableBlocking(0);
90+
}
91+
92+
@RuleAction(label = "@text/action.enable.label", description = "@text/action.enable.description")
93+
public void enableBlocking() throws PiHoleException {
94+
var local = handler;
95+
if (local == null) {
96+
return;
97+
}
98+
local.enableBlocking();
99+
}
100+
101+
public static void enableBlocking(@Nullable ThingActions actions) throws PiHoleException {
102+
((PiHoleActions) requireNonNull(actions)).enableBlocking();
103+
}
104+
}

0 commit comments

Comments
 (0)