From 3ad5ac39d3a542257b51f31460926e87662d13a5 Mon Sep 17 00:00:00 2001 From: JoKneeMo Date: Mon, 1 Aug 2022 00:59:15 -0400 Subject: [PATCH] Initial Commit --- adguardhome/README.md | 19 + adguardhome/app/AdGuardHome_App.groovy | 261 +++++++++++++ adguardhome/drivers/AdGuardHome_Client.groovy | 347 +++++++++++++++++ adguardhome/drivers/AdGuardHome_Server.groovy | 352 ++++++++++++++++++ 4 files changed, 979 insertions(+) create mode 100644 adguardhome/README.md create mode 100644 adguardhome/app/AdGuardHome_App.groovy create mode 100644 adguardhome/drivers/AdGuardHome_Client.groovy create mode 100644 adguardhome/drivers/AdGuardHome_Server.groovy diff --git a/adguardhome/README.md b/adguardhome/README.md new file mode 100644 index 0000000..f76ca1e --- /dev/null +++ b/adguardhome/README.md @@ -0,0 +1,19 @@ +# AdGuard Home DNS Manager (Unofficial) # + +This package is not supported nor endorsed by AdGuard! + +This package will allow you to manage the global services of an AdGuard Home DNS server and it's configured clients. +A main application allows creation of multiple servers within Hubitat, and the clients are created as child devices of each server. + + +## Application ## + +- [AdGuardHome_App.groovy](https://github.com/JoKneeMo/hubitat/adguardhome/blob/master/app/AdGuardHome_App.groovy) - Not required but makes managing multiple servers easier + +## Main Driver ## + +- [adGuardHome_Server.groovy](https://github.com/JoKneeMo/hubitat/adguardhome/blob/master/drivers/AdGuardHome_Server.groovy) - Required, this is the server device and handles the authentication and communication for each server. + +## Client Drivers ## + +- [adGuardHome_Client.groovy](https://github.com/JoKneeMo/hubitat/adguardhome/blob/master/drivers/AdGuardHome_Client.groovy) - Not required, this is the client device and is only needed if you want to manage individual client settings. \ No newline at end of file diff --git a/adguardhome/app/AdGuardHome_App.groovy b/adguardhome/app/AdGuardHome_App.groovy new file mode 100644 index 0000000..e566f51 --- /dev/null +++ b/adguardhome/app/AdGuardHome_App.groovy @@ -0,0 +1,261 @@ +/** + * AdGuard Home DNS Server Manager for Hubitat + * Author: JoKneeMo + * Copyright: JoKneeMo + * License: GPL-3.0-only + * Version: 0.1.0 +*/ + +import groovyx.net.http.ContentType +import groovy.transform.Field + +definition( + name: "AdGuard Home Manager", + namespace: "adguard-home", + author: "JoKneeMo", + description: "Manages AdGuard Home servers and allows client control", + category: "Dashboard", + iconUrl: "", + iconX2Url: "", + singleInstance: true +){} + +preferences { + page(name: "mainPage") + page(name: "newServer") + page(name: "addServer") + page(name: "configureServer") + page(name: "createServerChildren") + page(name: "deleteServer") + page(name: "changeName") +} + +def mainPage() { + dynamicPage(name: "mainPage", title: "

Manage Your AdGuard Home DNS Servers

", nextPage: null, uninstall: true, install: true) { + section("

Managed Servers

") { + getChildDevices().sort({ a, b -> a["deviceNetworkId"] <=> b["deviceNetworkId"] }).each { + href "configureServer", title: "$it.label", description: "", params: [did: it.deviceNetworkId] + } + href "newServer", title: "New DNS Server", description: "" + } + } +} + +def newServer() { + dynamicPage(name: "newServer", title: "

New AdGuard Home DNS Server

", nextPage: "mainPage", uninstall: false) { + section { + paragraph("Complete the fields below, then press the \"Add Server\" button.\n") + + input name: "serverName", type: "text", title: "New Server Name", required: true, defaultValue: "AdGuard Home DNS Server" + input name: "serverIP", type: "text", title: "Server IP Address", required: true, defaultValue: "192.168.1.2" + input name: "username", type: "text", title: "Username", required: true + input name: "password", type: "password", title: "Password", required: true + input name: "tlsEnable", type: "bool", title: "TLS Connection", defaultValue: false + input name: "logDebug", type: "bool", title: "Enable debug logging", defaultValue: true + + href "addServer", title: "Add Server", description: "Complete the info above and click here to add it" + } + } +} + +def addServer() { + def formattedDNI = AGH_API_DNI + "||" + UUID.randomUUID().toString() + def driver = DEVICE_TYPES[AGH_API_DNI].driver + + try { + d = createDevice(driver, formattedDNI, serverName.toString()) + + d.updateSetting("serverIP", [value: serverIP.toString(), type: "text"]) + d.updateSetting("username", [value: username.toString(), type: "text"]) + d.updateSetting("password", [value: password.toString(), type: "password"]) + d.updateSetting("tlsEnable",[value: tlsEnable.toString(), type: "bool"]) + d.updateSetting("logDebug", [value: logDebug.toString(), type: "bool"]) + + d.initialize() + d.refresh() + + logInfo "${DEVICE_TYPES[AGH_API_DNI].name} with ID ${formattedDNI} created..." + + dynamicPage(name: "addServer", title: "

Add Server Summary

", nextPage: "mainPage") { + section { + paragraph("The device has been created. Press \"Next\" to continue") + } + } + } + catch(e) { + dynamicPage(name: "addServer", title: "

Add Server Summary

", nextPage: "mainPage") { + section { + paragraph("Error: ${(e as String).split(": ")[1]}.") + paragraph("The device could not be created. Press \"Next\" to continue") + } + } + } +} + + + +def configureServer(params) { + if (params?.did || params?.params?.did) { + if (params.did) { + state.currentDeviceId = params.did + state.currentDisplayName = getChildDevice(params.did)?.displayName + } + else { + state.currentDeviceId = params.params.did + state.currentDisplayName = getChildDevice(params.params.did)?.displayName + } + } + if (getChildDevice(state.currentDeviceId) != null) getChildDevice(state.currentDeviceId).configure() + dynamicPage(name: "configureServer", title: "

Configure Existing AdGuard Home DNS Servers

", nextPage: "mainPage") { + if (state.currentDeviceId.startsWith(AGH_API_DNI)) { + section { + paragraph("This is the virtual device that holds the connection for your DNS Server.") + paragraph("The devices attached to the server are listed in the \"Child Devices\" section below.\n" + + "If no devices are listed, you can create them by pressing the \"Create Child Devices\" button.\n" + + "If there is a child device that you don't want, simply delete it after you finish creating all devices.") + } + } + + section("

Rename DNS Server Device

"){ + app.updateSetting("${state.currentDeviceId}_label", getChildDevice(state.currentDeviceId).label) + input "${state.currentDeviceId}_label", "text", title: "Device Name", description: "", required: false + href "changeName", title: "Change Device Name", description: "Edit the name above and click here to change it" + } + + section("

DNS Server Details

") { + paragraph("Connection Status: " + getChildDevice(state.currentDeviceId).currentState("commStatus").value + "\n" + + "IP Address: " + getChildDevice(state.currentDeviceId).getSetting("serverIP").value + "\n" + + "Username: " + getChildDevice(state.currentDeviceId).getSetting("username").value + "\n" + + "Debug Logging: " + getChildDevice(state.currentDeviceId).getSetting("logDebug").value + "\n" + + "Last Activity At: " + getChildDevice(state.currentDeviceId).getLastActivity().toString() + ) + } + + section("

Child Devices

"){ + if (getServerChildren(state.currentDeviceId) != null) { + paragraph("
    \n" + + getServerChildren(state.currentDeviceId).sort({ a, b -> a["deviceNetworkId"] <=> b["deviceNetworkId"] }).collect { + def url = "${getLocalApiServerUrl().replace("/apps/api", "")}/device/edit/${it.id}" + "
  • ${it.label}
  • " + }.join("\n") + + "
" + ) + href "createServerChildren", title: "Refresh Child Devices", description: "" + } + else { + paragraph("DNS Server has no children") + href "createServerChildren", title: "Create Child Devices", description: "" + } + + } + + section("

Delete Server Device

"){ + paragraph('WARNING!! ADVERTENCIA!! ACHTUNG!! AVERTISSEMENT!!') + paragraph("There is not a confirmation for these delete buttons!") + href "deleteServer", title: "Delete $state.currentDisplayName", description: "" + } + } +} + +def deleteServer() { + try { + unsubscribe() + deleteChildDevice(state.currentDeviceId) + dynamicPage(name: "deleteServer", title: "

AdGuard Home DNS Server Deletion Result

", nextPage: "mainPage") { + section { + paragraph("The device has been deleted. Press \"Next\" to continue.") + } + } + } + catch (e) { + dynamicPage(name: "deleteServer", title: "

AdGuard Home DNS Server Deletion Result

", nextPage: "mainPage") { + section { + paragraph("Error: ${(e as String).split(": ")[1]}.") + } + } + } +} + +def changeName() { + def thisDevice = getChildDevice(state.currentDeviceId) + thisDevice.label = settings["${state.currentDeviceId}_label"] + + dynamicPage(name: "changeName", title: "

AdGuard Home DNS Server Change Name Result

", nextPage: "mainPage") { + section { + paragraph("The device has been renamed. Press \"Next\" to continue") + } + } +} + +def createServerChildren() { + def thisDevice = getChildDevice(state.currentDeviceId) + def connectionStatus = thisDevice.currentState("commStatus").value + + if ("${connectionStatus}" == "good") { + try { + thisDevice.createChildDevices() + dynamicPage(name: "createServerChildren", title: "

Create Child Devices

", nextPage: "configureServer") { + section { + paragraph("The child devices have been created. Press \"Next\" to continue") + } + } + } + catch (e) { + dynamicPage(name: "createServerChildren", title: "

Failed to Create Child Devices

", nextPage: "configureServer") { + section { + paragraph("The child devices could not be created.") + paragraph("Error: ${(e as String).split(": ")[1]}.") + } + } + } + + } + else { + dynamicPage(name: "createServerChildren", title: "

DNS Server is not connected

", nextPage: "configureServer") { + section { + paragraph("The server is not connected. Correct the connection between Hubitat and the AdGuard Home DNS server, then try again.") + } + } + } +} + +// General app functions + +def String getFormattedDNI(id) { + return "AGH-${id}" +} + +def getServerChildren(server) { + def thisDevice = getChildDevice(server) + def children = thisDevice.getChildDevices() + + return children +} + +def createDevice(driver, id, label) { + return addChildDevice("JoKneeMo", driver, id, null, ["label": label]) +} + +//logging help methods +private logInfo(msg) { + if (descriptionTextEnable) log.info msg +} + +def logDebug(msg) { + if (logDebug) log.debug msg +} + +def logTrace(msg) { + if (traceLogEnable) log.trace msg +} + + +// Constants +@Field static def AGH_API_DNI = "AGH_API_DNI" +@Field static def GET = "httpGet" +@Field static def POST = "httpPost" + +@Field static def DEVICE_TYPES = [ + "AGH_API_DNI": [name: "AdGuard Home DNS Server", driver: "AdGuard Home DNS Server"], + "client": [name: "AdGuard Home DNS Client", driver: "AdGuard Home DNS Client"] +] \ No newline at end of file diff --git a/adguardhome/drivers/AdGuardHome_Client.groovy b/adguardhome/drivers/AdGuardHome_Client.groovy new file mode 100644 index 0000000..a07e7d4 --- /dev/null +++ b/adguardhome/drivers/AdGuardHome_Client.groovy @@ -0,0 +1,347 @@ +/* + * AdGuard Home DNS Client for Hubitat + * Author: JoKneeMo + * Copyright: JoKneeMo + * License: GPL-3.0-only + * Version: 0.1.0 +*/ + +metadata { + definition(name: "AdGuard Home DNS Client", namespace: "JoKneeMo", author: "JoKneeMo", importUrl: "") { + capability "Initialize" + capability "Refresh" + command "initialize" + command "refresh" + command "ProtectionOn" + command "ProtectionOff" + command "FilteringOn" + command "FilteringOff" + command "ParentalControlOn" + command "ParentalControlOff" + command "SafeBrowsingOn" + command "SafeBrowsingOff" + command "SafeSearchOn" + command "SafeSearchOff" + command "blockService", ["String"] + command "unblockService", ["String"] + attribute "filtering", "bool" + attribute "parental", "bool" + attribute "safeBrowsing", "bool" + attribute "safeSearch", "bool" + attribute "blockedServices", "string" + attribute "clientIds", "string" + attribute "clientTags", "string" + } +} + +preferences { + section { + input name: "serverIP", type: "text", title: "Server IP Address", required: true + input name: "username", type: "text", title: "Username", required: true + input name: "password", type: "password", title: "Password", required: true + input name: "tlsEnable", type: "bool", title: "TLS Connection", defaultValue: false + } + section { + input name: "logDebug", type: "bool", title: "Enable debug logging", defaultValue: false + input name: "logTrace", type: "bool", title: "Enable trace logging", defaultValue: false + } +} + +def initialize() { + refresh() + //runEvery5Minutes("refresh") +} + +def refresh() { + logDebug "DNS Server Polling..." + if (serverIP == null) { + logDebug "Server IP/FQDN not entered in preferences" + return + } + getStatus() + getFiltering() + getDHCP() + getSafeBrowsing() + getSafeSearch() + getParental() + getBlockedServices() +} + +// State Collectors +def getStatus() { + logDebug "Getting Status...." + def respValues = doHttpGet("/status", null) + sendEvent(name: "version", value: respValues.version) + sendEvent(name: "protection", value: respValues.protection_enabled) +} + +def getFiltering() { + logDebug "Getting Filtering...." + def respValues = doHttpGet("/filtering/status", null) + sendEvent(name: "filtering", value: respValues.enabled) +} + +def getDHCP() { + logDebug "Getting DHCP...." + def respValues = doHttpGet("/dhcp/status", null) + sendEvent(name: "dhcp", value: respValues.enabled) +} + +def getSafeBrowsing() { + logDebug "Getting SafeBrowsing...." + def respValues = doHttpGet("/safebrowsing/status", null) + sendEvent(name: "safeBrowsing", value: respValues.enabled) +} + +def getSafeSearch() { + logDebug "Getting SafeSearch...." + def respValues = doHttpGet("/safesearch/status", null) + sendEvent(name: "safeSearch", value: respValues.enabled) +} + +def getParental() { + logDebug "Getting Parental...." + def respValues = doHttpGet("/parental/status", null) + sendEvent(name: "parental", value: respValues.enabled) +} + +def getBlockedServices() { + logDebug "Getting Blocked Services...." + def respValues = doHttpGet("/blocked_services/list", null) + sendEvent(name: "blockedServices", value: respValues) +} + + +// State Controls +def ProtectionOn() { + logDebug "Enabling Protection...." + httpPayload = [protection_enabled: true] + doHttpPostJson("/dns_config", httpPayload) + getStatus() +} + +def ProtectionOff() { + logDebug "Disabling Protection...." + httpPayload = [protection_enabled: false] + doHttpPostJson("/dns_config", httpPayload) + getStatus() +} + +def FilteringOn() { + logDebug "Enabling Filtering...." + httpPayload = [enabled: true, interval: 24] + doHttpPostJson("/filtering/config", httpPayload) + getFiltering() +} + +def FilteringOff() { + logDebug "Disabling Filtering...." + httpPayload = [enabled: false, interval: 24] + doHttpPostJson("/filtering/config", httpPayload) + getFiltering() +} + +def DhcpOn() { + logDebug "Enabling DHCP...." + httpPayload = [enabled: true] + doHttpPostJson("/dhcp/set_config", httpPayload) + getDHCP() +} + +def DhcpOff() { + logDebug "Disabling DHCP...." + httpPayload = [enabled: false] + doHttpPostJson("/dhcp/set_config", httpPayload) + getDHCP() +} + +def ParentalControlOn() { + logDebug "Enabling Parental Control...." + httpPayload = [sensitivity: "TEEN"] + doHttpPostJson("/parental/enable", httpPayload) + getParental() +} + +def ParentalControlOff() { + logDebug "Disabling Parental Control...." + doHttpPostJson("/parental/disable", "") + getParental() +} + +def SafeBrowsingOn() { + logDebug "Enabling SafeBrowsing...." + doHttpPostJson("/safebrowsing/enable", "") + getSafeBrowsing() +} + +def SafeBrowsingOff() { + logDebug "Disabling SafeBrowsing...." + doHttpPostJson("/safebrowsing/disable", "") + getSafeBrowsing() +} + +def SafeSearchOn() { + logDebug "Enabling SafeSearch...." + doHttpPostJson("/safesearch/enable", "") + getSafeSearch() +} + +def SafeSearchOff() { + logDebug "Disabling SafeSearch...." + doHttpPostJson("/safesearch/disable", "") + getSafeSearch() +} + +def blockService(services_string) { + List services_list = services_string.split("\\s*,\\s*") + logDebug "Blocking ${services_list.size()} Service(s): ${services_list}..." + List currentBlocks_list = device.currentValue("blockedServices").toString().replaceAll("\\[|\\]", "").split("\\s*,\\s*") + logDebug "Currently Blocking ${currentBlocks_list.size()} Services: ${currentBlocks_list}" + + def postBlockList = currentBlocks_list + services_list + logDebug "Setting ${postBlockList.size} Blocked Services: ${postBlockList}" + + doHttpPostJson("/blocked_services/set", postBlockList) + getBlockedServices() +} + +def unblockService(services_string) { + List services_list = services_string.split("\\s*,\\s*") + logDebug "Unblocking ${services_list.size()} Service(s): ${services_list}..." + List currentBlocks_list = device.currentValue("blockedServices").toString().replaceAll("\\[|\\]", "").split("\\s*,\\s*") + logDebug "Currently Blocking ${currentBlocks_list.size()} Services: ${currentBlocks_list}" + + def postBlockList = currentBlocks_list - services_list + logDebug "Setting ${postBlockList.size} Blocked Services: ${postBlockList}" + + doHttpPostJson("/blocked_services/set", postBlockList) + getBlockedServices() +} + + +// Client Controls +def getClients() { + logDebug "Getting Clients...." + def respValues = doHttpGet("/clients", null) + return respValues +} + +def createChildDevices() { + getClients().clients?.each { + if((it.name && it.ids[0]) && !findChildDevice(it.id[0], "client")) { + createChildDevice(it.name, it.id, "client") + } + } +} + +def createChildDevice(name, id, deviceType) { + def child + try { + switch(deviceType.toString()) { + case "client": + addChildDevice("AdGuard Home DNS Client", childDni(id, deviceType), [name: childName(name, deviceType), label: childName(name, deviceType), isComponent: false]) + break + default: + logDebug("deviceType is not specified or is unsupported") + } + } + catch (Exception e) { + logDebug("Child Device Creation Failed: ${e.message}") + } +} + +def deleteChildDevices() { + for(child in getChildDevices()) { + deleteChildDevice(child.deviceNetworkId) + } +} + + +// General Functions +def doHttpGet(endpoint,bodyData) { + logDebug "Getting ${endpoint}...." + def httpParams = [ + uri: "${getBaseURI()}${endpoint}", + ignoreSSLIssues: true, + headers: getDefaultHeaders(), + body : "${bodyData}" + ] + try { + logTrace "HTTP Params: ${httpParams}" + httpGet(httpParams) { resp -> + logTrace "Response Data: ${resp.getData().toString()}" + def respValues = resp.getData() + return respValues + } + } catch(Exception e) { + logError "HTTP Error: ${e}" + } +} + +def doHttpPostJson(endpoint,bodyData) { + logDebug "Posting to ${endpoint}...." + def httpParams = [ + uri: "${getBaseURI()}${endpoint}", + ignoreSSLIssues: true, + headers: getDefaultHeaders(), + body : bodyData + ] + try { + logTrace "HTTP Params: ${httpParams}" + httpPostJson(httpParams) { resp -> + logTrace "Response Data: ${resp.getData().toString()}" + def respValues = resp.getData() + return respValues + } + } catch(Exception e) { + logError "HTTP Error: ${e}" + } +} + +def getBaseURI() { + if (tlsEnable) { + baseProtocol = "https://" + } else { + baseProtocol = "http://" + } + + baseURI = "${baseProtocol}${serverIP}/control" + + logTrace "Base URI: ${baseURI.toString()}" + return baseURI.toString() +} + +def getDefaultHeaders() { + def headers = [:] + headers.put("Accept-Encoding", "gzip, deflate, br") + headers.put("Connection", "keep-alive") + headers.put("Accept", "application/json, text/plain, */*") + headers.put("Content-type", "application/json; charset=UTF-8") + headers.put("User-Agent", "Hubitat") + authString="${username}:${password}" + headers.put("Authorization", "Basic ${authString.bytes.encodeBase64().toString()}") + + logTrace "Headers: ${headers}" + return headers +} + +//logging help methods +private logError(msg) { + log.error msg +} + +private logWarn(msg) { + log.warn msg +} + +private logInfo(msg) { + if (descriptionTextEnable) log.info msg +} + +private logDebug(msg) { + if (logDebug) log.debug msg +} + +private logTrace(msg) { + if (logTrace) log.trace msg +} diff --git a/adguardhome/drivers/AdGuardHome_Server.groovy b/adguardhome/drivers/AdGuardHome_Server.groovy new file mode 100644 index 0000000..a998461 --- /dev/null +++ b/adguardhome/drivers/AdGuardHome_Server.groovy @@ -0,0 +1,352 @@ +/* + * AdGuard Home DNS Server Driver for Hubitat + * Author: JoKneeMo + * Copyright: JoKneeMo + * License: GPL-3.0-only + * Version: 0.1.0 +*/ + +metadata { + definition(name: "AdGuard Home DNS Server", namespace: "JoKneeMo", author: "JoKneeMo", importUrl: "") { + capability "Initialize" + capability "Refresh" + command "initialize" + command "refresh" + command "createChildDevices" + command "deleteChildDevices" + command "ProtectionOn" + command "ProtectionOff" + command "FilteringOn" + command "FilteringOff" + command "DhcpOn" + command "DhcpOff" + command "ParentalControlOn" + command "ParentalControlOff" + command "SafeBrowsingOn" + command "SafeBrowsingOff" + command "SafeSearchOn" + command "SafeSearchOff" + command "blockService", ["String"] + command "unblockService", ["String"] + attribute "version", "string" + attribute "protection", "bool" + attribute "filtering", "bool" + attribute "dhcp", "bool" + attribute "parental", "bool" + attribute "safeBrowsing", "bool" + attribute "safeSearch", "bool" + attribute "blockedServices", "string" + } +} + +preferences { + section { + input name: "serverIP", type: "text", title: "Server IP Address", required: true + input name: "username", type: "text", title: "Username", required: true + input name: "password", type: "password", title: "Password", required: true + input name: "tlsEnable", type: "bool", title: "TLS Connection", defaultValue: false + } + section { + input name: "logDebug", type: "bool", title: "Enable debug logging", defaultValue: false + input name: "logTrace", type: "bool", title: "Enable trace logging", defaultValue: false + } +} + +def initialize() { + refresh() + //runEvery5Minutes("refresh") +} + +def refresh() { + logDebug "DNS Server Polling..." + if (serverIP == null) { + logDebug "Server IP/FQDN not entered in preferences" + return + } + getStatus() + getFiltering() + getDHCP() + getSafeBrowsing() + getSafeSearch() + getParental() + getBlockedServices() +} + +// State Collectors +def getStatus() { + logDebug "Getting Status...." + def respValues = doHttpGet("/status", null) + sendEvent(name: "version", value: respValues.version) + sendEvent(name: "protection", value: respValues.protection_enabled) +} + +def getFiltering() { + logDebug "Getting Filtering...." + def respValues = doHttpGet("/filtering/status", null) + sendEvent(name: "filtering", value: respValues.enabled) +} + +def getDHCP() { + logDebug "Getting DHCP...." + def respValues = doHttpGet("/dhcp/status", null) + sendEvent(name: "dhcp", value: respValues.enabled) +} + +def getSafeBrowsing() { + logDebug "Getting SafeBrowsing...." + def respValues = doHttpGet("/safebrowsing/status", null) + sendEvent(name: "safeBrowsing", value: respValues.enabled) +} + +def getSafeSearch() { + logDebug "Getting SafeSearch...." + def respValues = doHttpGet("/safesearch/status", null) + sendEvent(name: "safeSearch", value: respValues.enabled) +} + +def getParental() { + logDebug "Getting Parental...." + def respValues = doHttpGet("/parental/status", null) + sendEvent(name: "parental", value: respValues.enabled) +} + +def getBlockedServices() { + logDebug "Getting Blocked Services...." + def respValues = doHttpGet("/blocked_services/list", null) + sendEvent(name: "blockedServices", value: respValues) +} + + +// State Controls +def ProtectionOn() { + logDebug "Enabling Protection...." + httpPayload = [protection_enabled: true] + doHttpPostJson("/dns_config", httpPayload) + getStatus() +} + +def ProtectionOff() { + logDebug "Disabling Protection...." + httpPayload = [protection_enabled: false] + doHttpPostJson("/dns_config", httpPayload) + getStatus() +} + +def FilteringOn() { + logDebug "Enabling Filtering...." + httpPayload = [enabled: true, interval: 24] + doHttpPostJson("/filtering/config", httpPayload) + getFiltering() +} + +def FilteringOff() { + logDebug "Disabling Filtering...." + httpPayload = [enabled: false, interval: 24] + doHttpPostJson("/filtering/config", httpPayload) + getFiltering() +} + +def DhcpOn() { + logDebug "Enabling DHCP...." + httpPayload = [enabled: true] + doHttpPostJson("/dhcp/set_config", httpPayload) + getDHCP() +} + +def DhcpOff() { + logDebug "Disabling DHCP...." + httpPayload = [enabled: false] + doHttpPostJson("/dhcp/set_config", httpPayload) + getDHCP() +} + +def ParentalControlOn() { + logDebug "Enabling Parental Control...." + httpPayload = [sensitivity: "TEEN"] + doHttpPostJson("/parental/enable", httpPayload) + getParental() +} + +def ParentalControlOff() { + logDebug "Disabling Parental Control...." + doHttpPostJson("/parental/disable", "") + getParental() +} + +def SafeBrowsingOn() { + logDebug "Enabling SafeBrowsing...." + doHttpPostJson("/safebrowsing/enable", "") + getSafeBrowsing() +} + +def SafeBrowsingOff() { + logDebug "Disabling SafeBrowsing...." + doHttpPostJson("/safebrowsing/disable", "") + getSafeBrowsing() +} + +def SafeSearchOn() { + logDebug "Enabling SafeSearch...." + doHttpPostJson("/safesearch/enable", "") + getSafeSearch() +} + +def SafeSearchOff() { + logDebug "Disabling SafeSearch...." + doHttpPostJson("/safesearch/disable", "") + getSafeSearch() +} + +def blockService(services_string) { + List services_list = services_string.split("\\s*,\\s*") + logDebug "Blocking ${services_list.size()} Service(s): ${services_list}..." + List currentBlocks_list = device.currentValue("blockedServices").toString().replaceAll("\\[|\\]", "").split("\\s*,\\s*") + logDebug "Currently Blocking ${currentBlocks_list.size()} Services: ${currentBlocks_list}" + + def postBlockList = currentBlocks_list + services_list + logDebug "Setting ${postBlockList.size} Blocked Services: ${postBlockList}" + + doHttpPostJson("/blocked_services/set", postBlockList) + getBlockedServices() +} + +def unblockService(services_string) { + List services_list = services_string.split("\\s*,\\s*") + logDebug "Unblocking ${services_list.size()} Service(s): ${services_list}..." + List currentBlocks_list = device.currentValue("blockedServices").toString().replaceAll("\\[|\\]", "").split("\\s*,\\s*") + logDebug "Currently Blocking ${currentBlocks_list.size()} Services: ${currentBlocks_list}" + + def postBlockList = currentBlocks_list - services_list + logDebug "Setting ${postBlockList.size} Blocked Services: ${postBlockList}" + + doHttpPostJson("/blocked_services/set", postBlockList) + getBlockedServices() +} + + +// Client Controls +def getClients() { + logDebug "Getting Clients...." + def respValues = doHttpGet("/clients", null) + return respValues +} + +def createChildDevices() { + getClients().clients?.each { + if((it.name && it.ids[0]) && !findChildDevice(it.id[0], "client")) { + createChildDevice(it.name, it.id, "client") + } + } +} + +def createChildDevice(name, id, deviceType) { + def child + try { + switch(deviceType.toString()) { + case "client": + addChildDevice("AdGuard Home DNS Client", childDni(id, deviceType), [name: childName(name, deviceType), label: childName(name, deviceType), isComponent: false]) + break + default: + logDebug("deviceType is not specified or is unsupported") + } + } + catch (Exception e) { + logDebug("Child Device Creation Failed: ${e.message}") + } +} + +def deleteChildDevices() { + for(child in getChildDevices()) { + deleteChildDevice(child.deviceNetworkId) + } +} + + +// General Functions +def doHttpGet(endpoint,bodyData) { + logDebug "Getting ${endpoint}...." + def httpParams = [ + uri: "${getBaseURI()}${endpoint}", + ignoreSSLIssues: true, + headers: getDefaultHeaders(), + body : "${bodyData}" + ] + try { + logTrace "HTTP Params: ${httpParams}" + httpGet(httpParams) { resp -> + logTrace "Response Data: ${resp.getData().toString()}" + def respValues = resp.getData() + return respValues + } + } catch(Exception e) { + logError "HTTP Error: ${e}" + } +} + +def doHttpPostJson(endpoint,bodyData) { + logDebug "Posting to ${endpoint}...." + def httpParams = [ + uri: "${getBaseURI()}${endpoint}", + ignoreSSLIssues: true, + headers: getDefaultHeaders(), + body : bodyData + ] + try { + logTrace "HTTP Params: ${httpParams}" + httpPostJson(httpParams) { resp -> + logTrace "Response Data: ${resp.getData().toString()}" + def respValues = resp.getData() + return respValues + } + } catch(Exception e) { + logError "HTTP Error: ${e}" + } +} + +def getBaseURI() { + if (tlsEnable) { + baseProtocol = "https://" + } else { + baseProtocol = "http://" + } + + baseURI = "${baseProtocol}${serverIP}/control" + + logTrace "Base URI: ${baseURI.toString()}" + return baseURI.toString() +} + +def getDefaultHeaders() { + def headers = [:] + headers.put("Accept-Encoding", "gzip, deflate, br") + headers.put("Connection", "keep-alive") + headers.put("Accept", "application/json, text/plain, */*") + headers.put("Content-type", "application/json; charset=UTF-8") + headers.put("User-Agent", "Hubitat") + authString="${username}:${password}" + headers.put("Authorization", "Basic ${authString.bytes.encodeBase64().toString()}") + + logTrace "Headers: ${headers}" + return headers +} + +//logging help methods +private logError(msg) { + log.error msg +} + +private logWarn(msg) { + log.warn msg +} + +private logInfo(msg) { + if (descriptionTextEnable) log.info msg +} + +private logDebug(msg) { + if (logDebug) log.debug msg +} + +private logTrace(msg) { + if (logTrace) log.trace msg +}