diff --git a/.homeycompose/app.json b/.homeycompose/app.json
index a8a10da..ee44892 100644
--- a/.homeycompose/app.json
+++ b/.homeycompose/app.json
@@ -55,6 +55,11 @@
"name": "Ronny Troxler",
"email": "ronny.troxler@bluewin.ch",
"website": "https://place4fun.ch"
+ },
+ {
+ "name": "Bert Marcelis",
+ "email": "homey@bertmarcelis.be",
+ "website": "https://bertmarcelis.be"
}
],
"translators": [
diff --git a/app.json b/app.json
index 421edbb..c9b432c 100644
--- a/app.json
+++ b/app.json
@@ -1929,6 +1929,7 @@
},
"class": "sensor",
"capabilities": [
+ "onoff",
"alarm_communication_error",
"alarm_filter_state",
"last_sync",
@@ -1941,6 +1942,22 @@
"web_update_available",
"ftl_update_available"
],
+ "capabilitiesOptions": {
+ "onoff": {
+ "titleTrue": {
+ "en": "Adblocking enabled"
+ },
+ "titleFalse": {
+ "en": "Adblocking disabled"
+ },
+ "insightsTitleTrue": {
+ "en": "Adblocking has been enabled"
+ },
+ "insightsTitleFalse": {
+ "en": "Adblocking has been disabled"
+ }
+ }
+ },
"images": {
"small": "/drivers/pihole/assets/images/small.png",
"large": "/drivers/pihole/assets/images/large.png",
@@ -1954,7 +1971,7 @@
{
"id": "manual_input",
"prev": "welcome",
- "next": "list_devices"
+ "next": "done"
}
],
"settings": [
diff --git a/drivers/pihole/device.js b/drivers/pihole/device.js
index 6677502..ef8727f 100644
--- a/drivers/pihole/device.js
+++ b/drivers/pihole/device.js
@@ -1,9 +1,4 @@
const Homey = require('homey');
-const PiholeDevice = require('./device'); // Stellen Sie sicher, dass der Pfad korrekt ist.
-const CAPABILITY_DEBOUNCE = 500;
-
-//Festlegen einer Map für die Task-Verwaltung
-let intervalIds = new Map();
//Allgemeine Parameter
let absoluteTimestamp;
@@ -14,13 +9,15 @@ let formated_blocked_adds_today_percent;
class PiHoleDevice extends Homey.Device {
+ intervalId = undefined
+
async onInit() {
this.log('Gerät wurde initialisiert');
-
+
//Ansprechen der Einstellungen, damit Zugriff darauf gewähleistet ist
const deviceSettings = this.getSettings();
-
+
// Prüfen, ob Geräteeinstellungen vorhanden sind
if (deviceSettings) {
@@ -29,7 +26,7 @@ class PiHoleDevice extends Homey.Device {
const device_port = deviceSettings.port
const device_api = deviceSettings.api
const device_interval = deviceSettings.interval
-
+
//Schreiben ins Log File
this.log('*******************************************************')
this.log('URL ->', device_url);
@@ -40,12 +37,12 @@ class PiHoleDevice extends Homey.Device {
//Herausfinden welches Gerät das ist
this.log('Identifiziert und initialisiert ..')
-
+
//Bereitstellen der nötigen URLs für Aktionen / Abfragen
const disable_url = `${device_url}:${device_port}/admin/api.php?disable&auth=${device_api}`;
const enable_url = `${device_url}:${device_port}/admin/api.php?enable&auth=${device_api}`;
const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`;
-
+
//Schreiben ins Log File
this.log('URL Disable ->', disable_url);
this.log('URL Enable ->', enable_url);
@@ -53,58 +50,40 @@ class PiHoleDevice extends Homey.Device {
//Capabilities Updaten (Danke Ronny Winkler)
await this._updateCapabilities();
+ //Task neu erstellen
+ this.createTask()
+
+
+ //Schreibt den Status, bei Veränderung, ins Log File
+ this.registerCapabilityListener('onoff', async (value) => {
+ this.log('Eingeschaltet:', value);
+
+ if (value) {
+ this.log('Eingeschaltet:', value);
+ this._makeAPICall(enable_url).then(
+ // update data so it reflects the correct status. Little delay so pihole has time to actually enable
+ () => setTimeout(() => this._updateDeviceData(), 600)
+ )
+ } else {
+ this.log('Ausgeschaltet:', value);
+ this._makeAPICall(disable_url).then(
+ // update data so it reflects the correct status. Little delay so pihole has time to actually enable
+ () => setTimeout(() => this._updateDeviceData(), 600)
+ )
+ }
+ }
+ )
+
+ this.registerCapabilityListener('data_refresh', async (value) => {
+ const api_key = deviceSettings.api
+ const status_url = `${this.getHost()}/admin/api.php?summaryRaw&auth=${api_key}`;
+ this._updateDeviceData(status_url);
+ }
+ )
+ } else {
+ this.log('Keine Geräteeinstellungen gefunden. Aktionen werden nicht ausgeführt.');
+ }
- //Capabilities Listener
- this.registerMultipleCapabilityListener(this.getCapabilities(), async (capabilityValues, capabilityOptions) => {
- // try{
- await this._onCapability( capabilityValues, capabilityOptions);
- // }
- // catch(error){
- // this.log("_onCapability() Error: ",error);
- // }
- },CAPABILITY_DEBOUNCE);
-
- let deviceSettingsArray = [
- {
- url: device_url,
- port: device_port,
- api: device_api,
- interval: device_interval
- },
- ];
-
- deviceSettingsArray.forEach(deviceSettings => {
- const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`;
-
- // Erstellen des wiederkehrenden Tasks
- const deviceId = this.getId();
-
- //Task neu erstellen
- this.createTask(deviceId)
- });
-
-
- //Schreibt den Status, bei Veränderung, ins Log File
- this.registerCapabilityListener('onoff', async (value) => {
- this.log('Eingeschaltet:' ,value);
- const deviceId = this.getData().id;
-
- //Reagiert darauf, wenn das Gerät nicht erreichbar ist
- this.setUnavailable(this.homey.__('device.unavailable')).catch(this.error);
-
- if (value) {
- this.log('Eingeschaltet:' ,value);
- this._makeAPICall(enable_url)
- } else {
- this.log('Ausgeschaltet:' ,value);
- this._makeAPICall(disable_url)
- }
- }
- )
- } else {
- this.log('Keine Geräteeinstellungen gefunden. Aktionen werden nicht ausgeführt.');
- }
-
}
/**
@@ -211,7 +190,7 @@ async onDeleted() {
const deviceId = this.getId();
//Task löschen
- this.deleteTask(deviceId)
+ this.deleteTask()
}
async _updateCapabilities(){
@@ -244,50 +223,19 @@ async _updateCapabilities(){
}
}
-// CAPABILITIES =======================================================================================
-
-async _onCapability( capabilityValues, capabilityOptions){
-
- //Ansprechen der Einstellungen, damit Zugriff darauf gewähleistet ist
- const deviceSettings = this.getSettings();
-
- //Die nötigen Einstellungen holen und bereitstellen
- const device_url = deviceSettings.url
- const device_port = deviceSettings.port
- const device_api = deviceSettings.api
- const device_Name = this.getName()
-
-
- //Schreiben ins Log File
- this.log('*******************************************************')
- this.log('ID ->', device_Name);
- this.log('URL ->', device_url);
- this.log('Port ->', device_port);
- this.log('Key ->', device_api);
- this.log('*******************************************************')
-
- //Bereitstellen der nötigen URLs für Aktionen / Abfragen
- const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`;
- const update_url = `${device_url}:${device_port}/admin/api.php??versions`;
-
-
- if( capabilityValues["data_refresh"] != undefined){
- this._updateDeviceData(status_url);
- }
-}
// Helpers =======================================================================================
async _makeAPICall(url) {
try {
const response = await fetch(url);
-
+
if (!response.ok) {
throw new Error(response.statusText);
}
this.log('API Aufruf erfolgreich');
return { success: true }; // Erfolgsstatus zurückgeben
-
+
} catch (error) {
this.log('API Aufruf fehlgeschlagen:');
this.log(errorMessage);
@@ -295,25 +243,23 @@ async _makeAPICall(url) {
}
}
-async _updateDeviceData(url) {
+async _updateDeviceData() {
+ const deviceSettings = this.getSettings();
+ // Die nötigen Einstellungen holen und bereitstellen
+ const api_key = deviceSettings.api;
+ const url = `${this.getHost()}/admin/api.php?summaryRaw&auth=${api_key}`;
try {
- const response = await fetch(url);
- // Überprüft, ob der Statuscode im Erfolgsbereich liegt (200-299) oder 403 ist
- if (!response.ok && response.status !== 403) {
- throw new Error(`Status Filter: ${response.status}`);
- } else {
+ fetch(url).then(response => {
+ if (!response.ok && response.status !== 403) {
+ throw new Error(`Status Filter: ${response.status}`);
}
+ response.json().then(data => {
- const data = await response.json();
-
- fetch(url).then(response => response.json())
- .then(data => {
-
//Fülle Variable mit Gerätename ab
const deviceName = this.getName()
- //Saubere Formatierung des Status
+ //Saubere Formatierung des Status
let PiHoleState = false
if(data.status === 'enabled') {
@@ -323,14 +269,14 @@ async _updateDeviceData(url) {
}
//Datum sauber formatieren
let syncDate = new Date();
- let day = syncDate.toLocaleDateString('de-DE', {
- day: '2-digit',
- month: '2-digit',
- year: '2-digit',
- timeZone: this.homey.clock.getTimezone()
+ let day = syncDate.toLocaleDateString('de-DE', {
+ day: '2-digit',
+ month: '2-digit',
+ year: '2-digit',
+ timeZone: this.homey.clock.getTimezone()
});
- let time = syncDate.toLocaleTimeString('de-DE', {
- hour: '2-digit',
+ let time = syncDate.toLocaleTimeString('de-DE', {
+ hour: '2-digit',
minute: '2-digit',
timeZone: this.homey.clock.getTimezone()
});
@@ -349,15 +295,7 @@ async _updateDeviceData(url) {
}
// Update Prüfung
- //Ansprechen der Einstellungen, damit Zugriff darauf gewährleistet ist
- const deviceSettings = this.getSettings();
- const device_Name = this.getName()
-
- //Die nötigen Einstellungen holen und bereitstellen
- const device_url = deviceSettings.url
- const device_port = deviceSettings.port
-
- const update_url = `${device_url}:${device_port}/admin/api.php?versions`;
+ const update_url = `${this.getHost()}/admin/api.php?versions`;
this.checkUpdateAvailable(deviceName, update_url)
// Geblockte ADDs pro Tag
@@ -392,7 +330,7 @@ async _updateDeviceData(url) {
const gravity_update_hours = Math.floor((timeDifference % (24 * 3600)) / 3600);
const gravity_update_minutes = Math.floor((timeDifference % 3600) / 60);
- let gravity_update_string = gravity_update_days + ' ' + this.homey.__('capabilities.gravity_days') + ' ' + gravity_update_hours + ' ' + this.homey.__('capabilities.gravity_hours') + ' ' + gravity_update_minutes + ' ' + this.homey.__('capabilities.gravity_minutes');
+ let gravity_update_string = gravity_update_days + ' ' + this.homey.__('capabilities.gravity_days') + ' ' + gravity_update_hours + ' ' + this.homey.__('capabilities.gravity_hours') + ' ' + gravity_update_minutes + ' ' + this.homey.__('capabilities.gravity_minutes');
// Loggen der Werte zwecks Diagnose
this.log('');
@@ -444,72 +382,54 @@ async _updateDeviceData(url) {
this.log('Fehler --> gravity_last_update ist nicht definiert');
}
- // Capabilities für den Rest setzen
- this.setCapabilityValue('alarm_communication_error', false);
- this.setCapabilityValue('alarm_filter_state', PiHoleState);
+ // Capabilities für den Rest setzen
+ this.setCapabilityValue('alarm_communication_error', false);
+ this.setCapabilityValue('alarm_filter_state', PiHoleState);
+ this.setCapabilityValue('onoff', data.status === 'enabled');
+ })
+ });
+ } catch (error) {
+ this.log('Ein Fehler ist aufgetreten ->', error.message);
-});
-} catch (error) {
- this.log('Ein Fehler ist aufgetreten ->', error.message);
-
// Jetzt können Sie Capabilities für dieses Gerät setzen
- this.setCapabilityValue('alarm_communication_error', true);
+ this.setCapabilityValue('alarm_communication_error', true);
+}
}
-}
//TASK Verwaltung
-async deleteTask(deviceId) {
- // Überprüfen, ob für die deviceId ein Task existiert
- if (intervalIds.has(deviceId)) {
- // Hole die intervalId und stoppe das Intervall
- clearInterval(intervalIds.get(deviceId));
- this.log('Task für Gerät' ,deviceId, 'gestoppt.');
-
- // Entferne die intervalId aus der Map
- intervalIds.delete(deviceId);
- this.log('Task für Gerät' ,deviceId, 'gelöscht.');
-
- } else {
- this.log('Kein Task gefunden für Gerät' ,deviceId,);
+ async deleteTask() {
+ // Überprüfen, ob für die deviceId ein Task existiert
+ if (this.intervalId) {
+ // Hole die intervalId und stoppe das Intervall
+ clearInterval(this.intervalId);
+ this.log('Task für Gerät', this.getId(), 'gestoppt.');
+ this.intervalId = undefined
+ this.log('Task für Gerät', this.getId(), 'gelöscht.');
+ } else {
+ this.log('Kein Task gefunden für Gerät', this.getId(),);
+ }
}
-}
-async createTask(deviceId) {
-
+async createTask() {
+
// Vorhandenes Intervall beenden, falls es bereits existiert
- const existingIntervalId = intervalIds.get(deviceId);
- if (existingIntervalId) {
- clearInterval(existingIntervalId);
- intervalIds.delete(deviceId); // Entferne das Intervall aus der Map
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
}
-
+
// Ansprechen der Einstellungen, um darauf zugreifen zu können
const deviceSettings = this.getSettings();
-
- // Die nötigen Einstellungen holen und bereitstellen
- const device_url = deviceSettings.url;
- const device_port = deviceSettings.port;
- const device_api = deviceSettings.api;
-
+
// Bereitstellen der nötigen URLs für Aktionen / Abfragen
- const status_url = `${device_url}:${device_port}/admin/api.php?summaryRaw&auth=${device_api}`;
-
- // Die nötigen Einstellungen holen und bereitstellen
- const device_interval = deviceSettings.interval;
-
- // Umrechnen der Interval ID in Millisekunden
- const device_interval_minutes = device_interval; // Der Wert von device_interval in Minuten
- const device_interval_milliseconds = device_interval_minutes * 60000;
-
+ const device_interval_milliseconds = deviceSettings.interval * 60_000;
+
// Erstelle einen neuen Task (z.B. eine Funktion, die regelmäßig ausgeführt wird)
- const intervalId = setInterval(() => {
- this._updateDeviceData(status_url);
+ this.intervalId = setInterval(() => {
+ this._updateDeviceData();
}, device_interval_milliseconds); // gem. eingestelltem Intervall
-
- // Speichere die neue intervalId in der Map
- intervalIds.set(deviceId, intervalId);
- this.log('Neuer Task erstellt für Gerät:', deviceId);
+
+ this.log('Neuer Task erstellt für Gerät:', this.getId());
}
async checkUpdateAvailable(device, url) {
@@ -517,7 +437,7 @@ async checkUpdateAvailable(device, url) {
const response = await fetch(url);
const data = await response.json();
-
+
this.log('Update Prüfung beginnt');
// Überprüfe ob ein Core Update vorhanden ist
@@ -526,7 +446,7 @@ async checkUpdateAvailable(device, url) {
this.setCapabilityValue('core_update_available', true);
} else {
this.log(device,': Kein Core Update verfügbar.');
- this.setCapabilityValue('core_update_available', false);
+ this.setCapabilityValue('core_update_available', false);
}
// Überprüfe ob ein Core Update vorhanden ist
@@ -536,7 +456,7 @@ async checkUpdateAvailable(device, url) {
} else {
this.log(device,': Kein Web Update verfügbar.');
- this.setCapabilityValue('web_update_available', false);
+ this.setCapabilityValue('web_update_available', false);
}
// Überprüfe ob ein Core Update vorhanden ist
@@ -545,7 +465,7 @@ async checkUpdateAvailable(device, url) {
this.setCapabilityValue('ftl_update_available', true);
} else {
this.log(device,': Kein FTL Update verfügbar.');
- this.setCapabilityValue('ftl_update_available', false);
+ this.setCapabilityValue('ftl_update_available', false);
}
} catch (error) {
@@ -555,5 +475,12 @@ async checkUpdateAvailable(device, url) {
this.log('Update Prüfung beendet');
}
+ getHost() {
+ const deviceSettings = this.getSettings();
+ const device_url = deviceSettings.url
+ const device_port = deviceSettings.port || 80
+ return `${device_url}:${device_port}`
+ }
+
}
module.exports = PiHoleDevice;
\ No newline at end of file
diff --git a/drivers/pihole/driver.compose.json b/drivers/pihole/driver.compose.json
index 4f9030c..b6a3439 100644
--- a/drivers/pihole/driver.compose.json
+++ b/drivers/pihole/driver.compose.json
@@ -1,19 +1,20 @@
{
"id": "pihole",
"name": {
- "en": "PiHole",
- "da": "PiHole",
- "de": "PiHole",
- "es": "PiHole",
- "fr": "PiHole",
- "nl": "PiHole",
- "no": "PiHole",
- "pl": "PiHole",
- "ru": "PiHole",
- "sv": "PiHole"
- },
+ "en": "PiHole v5.x",
+ "da": "PiHole v5.x",
+ "de": "PiHole v5.x",
+ "es": "PiHole v5.x",
+ "fr": "PiHole v5.x",
+ "nl": "PiHole v5.x",
+ "no": "PiHole v5.x",
+ "pl": "PiHole v5.x",
+ "ru": "PiHole v5.x",
+ "sv": "PiHole v5.x"
+ },
"class": "sensor",
"capabilities": [
+ "onoff",
"alarm_communication_error",
"alarm_filter_state",
"last_sync",
@@ -26,20 +27,47 @@
"web_update_available",
"ftl_update_available"
],
- "images": {
+ "capabilitiesOptions": {
+ "onoff": {
+ "titleTrue": {
+ "en": "Adblocking enabled"
+ },
+ "titleFalse": {
+ "en": "Adblocking disabled"
+ },
+ "insightsTitleTrue": {
+ "en": "Adblocking has been enabled"
+ },
+ "insightsTitleFalse": {
+ "en": "Adblocking has been disabled"
+ }
+ }
+ },
+ "images": {
"small": "{{driverAssetsPath}}/images/small.png",
"large": "{{driverAssetsPath}}/images/large.png",
"xlarge": "{{driverAssetsPath}}/images/xlarge.png"
- },
+ },
"pair": [
{
- "id": "welcome",
- "next": "manual_input"
+ "id": "list_devices",
+ "template": "list_devices",
+ "options": {
+ "singular": true
+ },
+ "navigation": {
+ "next": "configure_device"
+ }
+ },
+ {
+ "id": "configure_device",
+ "navigation": {
+ "prev": "list_devices"
+ }
},
{
- "id": "manual_input",
- "prev": "welcome",
- "next": "list_devices"
+ "id": "add_device",
+ "template": "add_devices"
}
- ]
+ ]
}
diff --git a/drivers/pihole/driver.js b/drivers/pihole/driver.js
index a3b2c8e..70c392c 100644
--- a/drivers/pihole/driver.js
+++ b/drivers/pihole/driver.js
@@ -1,9 +1,187 @@
const Homey = require('homey');
-const CAPABILITY_DEBOUNCE = 500;
-const PiholeDevice = require('./device');
+const crypto = require("crypto");
+const net = require("net");
class PiholeDriver extends Homey.Driver {
async onInit() {
+ this.log("PiholeDriver initialized");
}
+
+ async onPair(session) {
+ this.log("onPair()");
+
+ // default device when automatic detection fails
+ let devices = [{
+ name: this.homey.__("pairing.add_manually"),
+ data: {
+ id: 'pihole-' + crypto.randomUUID()
+ },
+ settings: {
+ url: "",
+ port: 80,
+ api: "",
+ interval: 1
+ },
+ }]
+
+ let autoDiscoverStarted = false
+
+ session.setHandler("list_devices", async () => {
+ this.log("list_devices");
+ if (!autoDiscoverStarted){
+ // Prevent multiple searches when navigating back and forth in the pairing process
+ this.emitAutodetectedPiHoles(session, devices);
+ autoDiscoverStarted = true;
+ }
+ return devices;
+ });
+
+ session.setHandler("list_devices_selection", async (devicea) => {
+ this.selectedDevice = devicea[0];
+ this.log("Set selected device, name " + this.selectedDevice.name)
+ });
+
+ session.setHandler("get_selected_device", async (device) => {
+ this.log("Get selected device, name " + this.selectedDevice.name)
+ return this.selectedDevice;
+ });
+
+ session.setHandler("test_device", async (device) => {
+ this.log("test_device, received settings: " + device.settings);
+ if ((await this.valid_host(device.settings.url, device.settings.port)) === false) {
+ return this.homey.__("pairing.test_failed.invalid_host");
+ }
+ if ((await this.valid_key(device)) === false) {
+ return this.homey.__("pairing.test_failed.invalid_key");
+ }
+ return "ok"
+ });
+ }
+
+ createDeviceStub(ipAddress) {
+ return {
+ name: ipAddress,
+ data: {
+ id: 'pihole-' + crypto.randomUUID()
+ },
+ iconObj: {
+ // Reverse-engineered from list-devices template, icons as documented do not work correctly
+ url: "/app/ch.place4fun.pihole/drivers/pihole/assets/icon.svg",
+ },
+ settings: {
+ url: "http://" + ipAddress,
+ port: 80,
+ api: "",
+ interval: 1
+ },
+ };
+ }
+
+ async valid_host(url, port, timeout = 5_000) {
+ let versionUrl = url + ":" + port + "/admin/api.php?versions";
+ return fetch(versionUrl, {
+ signal: AbortSignal.timeout(timeout),
+ })
+ .then(response => {
+ this.log("Response " + response.status)
+ return response.json()
+ })
+ .then(json => json["FTL_update"] !== undefined)
+ .catch(e => {
+ // this.log("Failed to get a valid response: " + e)
+ return false;
+ });
+ }
+
+
+ async valid_key(device) {
+ try {
+ // This page always returns HTTP 200, but with an invalid key it returns an empty array instead of an object
+ let summaryUrl = device.settings.url + ":" + device.settings.port
+ + "/admin/api.php?summaryRaw&auth=" + device.settings.api;
+ this.log("Testing key through " + summaryUrl)
+ return await fetch(summaryUrl)
+ .then(response => response.json())
+ .then(json => !Array.isArray(json))
+ } catch (e) {
+ this.log("Failed to get a valid response: " + e)
+ return false;
+ }
+ }
+
+
+ async getIpsInSubnet() {
+ // There are better ways to do this, such as obtaining the DNS server through DHCP or getting hosts from ARP
+ // Homey blocks them all :(
+ // The dns server is also known in homeys dev tools, but not exposed to apps.
+ // When getting DNS servers through nodejs homeys hardcoded servers are returned instead of dhcp values
+ const {networkInterfaces} = require('os');
+ const nets = networkInterfaces();
+ let localIpAddresses = [];
+ for (const name of Object.keys(nets)) {
+ for (const net of nets[name]) {
+ // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
+ // 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6
+ const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4
+ if (net.family === familyV4Value && !net.internal) {
+ let ips = this.getIPsInSubnet(net.address, net.netmask);
+ // Check 254 addresses at most, so we don't flood the network on large subnets
+ ips = ips.splice(0, 254)
+ localIpAddresses.push(...ips);
+ }
+ }
+ }
+ return localIpAddresses;
}
+
+ async emitAutodetectedPiHoles(session, devices) {
+ return new Promise(async (resolve, reject) => {
+ let addresses = await this.getIpsInSubnet();
+ let result = [];
+ this.log("Testing " + addresses.length + " ip addresses...")
+ addresses = ["pi.hole", "pihole.local", ... addresses] // also search two DNS names which are likely matches
+ for (const address of addresses) {
+ // this.homey.arp.getMAC(ipAddress) is horribly slow and unusable as help, so we have to brute-force discovery
+ let isValid = (await this.valid_host("http://" + address, 80, 50));
+ if (isValid) {
+ devices.push(this.createDeviceStub(address));
+ session.emit("list_devices", devices); // emit while discovering for faster feedback in the UI
+ }
+ }
+ resolve(result);
+ });
+ }
+
+
+// Get the integer representation of the IP
+ ipToInteger(ip) {
+ let [a, b, c, d] = ip.split('.').map(Number);
+ return (((((a << 8) + b) << 8) + c) << 8) + d;
+ };
+
+// Get the string representation of the IP
+ integerToIP(i) {
+ return [(i >> 24) & 255, (i >> 16) & 255, (i >> 8) & 255, i & 255].join('.');
+ };
+
+// Returns all IP addresses in a subnet
+ getIPsInSubnet(ip, mask) {
+ const baseIPInteger = this.ipToInteger(ip);
+ const maskInteger = this.ipToInteger(mask);
+ let startIPInteger = baseIPInteger & maskInteger;
+ let endIPInteger = startIPInteger | ~maskInteger;
+
+ let addressesInSubnet = [];
+ for (let i = startIPInteger; i <= endIPInteger; i++) {
+ if ((i & 255) === 255 || (i & 255) === 0) {
+ // skip broadcast address .255 and .0
+ continue
+ }
+ addressesInSubnet.push(this.integerToIP(i));
+ }
+
+ return addressesInSubnet;
+ };
+}
+
module.exports = PiholeDriver;
\ No newline at end of file
diff --git a/drivers/pihole/pair/configure_device.html b/drivers/pihole/pair/configure_device.html
new file mode 100644
index 0000000..ea96495
--- /dev/null
+++ b/drivers/pihole/pair/configure_device.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/drivers/pihole/pair/manual_input.html b/drivers/pihole/pair/manual_input.html
deleted file mode 100644
index cd0138d..0000000
--- a/drivers/pihole/pair/manual_input.html
+++ /dev/null
@@ -1,161 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/drivers/pihole/pair/welcome.html b/drivers/pihole/pair/welcome.html
deleted file mode 100644
index c005cae..0000000
--- a/drivers/pihole/pair/welcome.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/locales/en.json b/locales/en.json
index 569d2b2..92f7751 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -1,7 +1,4 @@
{
- "settings": {
- "information_about_pihole": "IMPORTANT INFORMATION: The application has undergone a major update. The settings are no longer necessary. Existing devices will continue to run for a while. The old driver will be removed at some point. In order to use the new functions, the new driver MUST be used."
- },
"warnings": {
"deprecated1": "WARNING: The old driver has been removed in version 1.5.0, as announced!"
},
@@ -51,5 +48,32 @@
"buttons": {
"next": "Add device"
}
+ },
+ "settings": {
+ "title": "Device Settings",
+ "test_connection": "Test connection",
+ "name": {
+ "title": "Device name",
+ "hint": "How the device should be named in Homey"
+ },
+ "host": {
+ "title": "Device host",
+ "hint": "Please use FQDN or IP address with prepended HTTP(s)"
+ },
+ "port": {
+ "title": "Port",
+ "hint": "Default is 80"
+ },
+ "api_key": {
+ "title": "API key",
+ "hint": "Please enter the PiHole API key here. It can be obtained from the PiHole settings page"
+ }
+ },
+ "pairing":{
+ "add_manually": "Add manually",
+ "test_failed":{
+ "invalid_host": "The hostname or port are incorrect.",
+ "invalid_key": "The API key is invalid."
+ }
}
}
\ No newline at end of file