/** * Driver: Dynamic ip updater script * Author: lgkahn * * Change Log: * * 2025.01.05 - Initial implementation * v2 add lastupdate and lastupdatecheck attributes, get rid of duplicate check. reorder inputs * v 2.1 add last result attribute, add actuator capability so it can be added to a dashboard to see status attributes. */ public static String version() { return "v2.1"; } // Metadata ------------------------------------------------------------------------------------------------------------------- metadata { definition(name: "Dynamic IP Updater Script", namespace: "lgkahn", author: "lgkahn", importUrl: "https://raw.githubusercontent.com/lgkahn/ecowitt/master/ddnsfree_updater.groovy") { attribute "lastUpdate", "string" attribute "dynamicIPResult","string" attribute "currentIP", "string" attribute "lastUpdate", "string" attribute "lastUpdateCheck", "string" attribute "nextUpdateCheck", "string" attribute "lastResult", "string" capability "Actuator" command "DDNSUpdate" } preferences { input(name: "DDNSName", type: "text", title: "Dynamic DNS Name to use to resolve a changing ip address.", description: "Enter DDNS Name", required: false) input(name: "DDNSRefreshTime", type: "number", title: "How often (in Hours) to check/resolve the DDNS Name to discover if your IP address changed? (Range 1 - 1440, Default 24)?", range: "1..1440", defaultValue: 3, required: false) input(name: "DDNSUsername", type: "String", title: "DDNS Username ?", required: true) input(name: "DDNSPassword", type: "password", title: "DDNS Password ?", required: true) input(name: "DDNSBaseURL", type: "String", title: "DDNS URL ?", required: true) input(name: "logLevel", type: "enum", title: "Log Verbosity", description: "Default: 'Debug' for 30 min and 'Info' thereafter", options: [0:"Error", 1:"Warning", 2:"Info", 3:"Debug", 4:"Trace"], multiple: false, defaultValue: 3, required: true); } } // Preferences ---------------------------------------------------------------------------------------------------------------- import groovy.json.JsonSlurper; void publicIpReq(cookie){ params = [ uri: "https://api.ipify.org?format=json", headers: [ Accept: "application/json" ] ] if(debugEnable)log.debug params asynchttpGet("getPublicIp", params) } @SuppressWarnings('unused') void getPublicIp(resp, data){ try{ if (resp.getStatus() == 200){ if (debugEnable) log.debug resp.data def jSlurp = new JsonSlurper() Map ipData = (Map)jSlurp.parseText((String)resp.data) logDebug( "got ip = ${ipData.ip}") attributeUpdateString(ipData.ip, "currentIP"); } else { if (!warnSuppress) log.warn "Status ${resp.getStatus()} while fetching Public IP" } } catch (Exception ex){ if (!warnSuppress) log.warn ex } } // ------------------------------------------------------------ Integer logGetLevel() { // // Get the log level as an Integer: // // 0) log only Errors // 1) log Errors and Warnings // 2) log Errors, Warnings and Info // 3) log Errors, Warnings, Info and Debug // 4) log Errors, Warnings, Info, Debug and Trace/diagnostic (everything) // // If the level is not yet set in the driver preferences, return a default of 2 (Info) // Declared public because it's being used by the child-devices as well // if (settings.logLevel != null) return (settings.logLevel.toInteger()); return (2); } // ------------------------------------------------------------ private Boolean attributeUpdateString(String val, String attribute) { if ((device.currentValue(attribute) as String) != val) { sendEvent(name: attribute, value: val); return (true); } return (false); } // ------------------------------------------------------------ def DDNSUpdate() { logDebug( "Calling IP_Update()") def now = new Date().format('MM/dd/yyyy h:mm a',location.timeZone) sendEvent(name: "lastUpdateCheck", value: now) IP_Check() } // ------------------------------------------------------------- private String IP_Check() { // // Get the gateway address (either MAC or IP) from the properties and, if valid and not done already, update the driver DNI // Return "error") invalid address entered by the user // null) same address as before // "") new valid address // logDebug("IP_Check()"); // first get current publicip publicIpReq() dni = device.currentValue("currentIP") logDebug( "got public ip = $dni") if (dni) { // now resolv dynmaic ip logDebug("calling dns Update url = $params") def String ddnsname = settings.DDNSName def Number ddnsupdatetime = settings.DDNSRefreshTime logDebug("DDNS Name = $ddnsname") logDebug("DDNS Refresh Time = $ddnsupdatetime") if ((ddnsname != null) && (ddnsname != "")) { logDebug("Got ddns name $ddnsname") Map params = [ uri: "https://8.8.8.8/resolve?name=$ddnsname&type=A", contentType: "text/plain", timeout: 20 ] logDebug("calling dns query url = $params") asynchttpGet("nsCallback", params) } } } // Conversion ----------------------------------------------------------------------------------------------------------------- // Logging -------------------------------------------------------------------------------------------------------------------- void logDebugOff() { // // runIn() callback to disable "Debug" logging after 30 minutes // Cannot be private // if (logGetLevel() > 2) device.updateSetting("logLevel", [type: "enum", value: "2"]); } // ------------------------------------------------------------ private void logError(String str) { log.error(str); } private void logWarning(String str) { if (logGetLevel() > 0) log.warn(str); } private void logInfo(String str) { if (logGetLevel() > 1) log.info(str); } private void logDebug(String str) { if (logGetLevel() > 2) log.debug(str); } private void logTrace(String str) { if (logGetLevel() > 3) log.trace(str); } // ------------------------------------------------------------ void installed() { // // Called once when the driver is created // try { logDebug("installed()"); publicIpReq() } catch (Exception e) { logError("Exception in installed(): ${e}"); } } // ------------------------------------------------------------- def updateCallback(resp, data) { logDebug("in ddns update callback") // test change if (resp == null) logError("Dynamic DNS update failed return was null!") else { def String checkString = "good " + device.currentValue("currentIP") log.debug "check string = $checkString" if ((resp.data == "nochg") || (resp.data == "good") || (resp.data == checkString)) { logInfo("Dynamic DNS succesfully updated!") def now = new Date().format('MM/dd/yyyy h:mm a',location.timeZone) sendEvent(name: "lastUpdate", value: now) sendEvent(name: "lastResult", value: resp.data) } else { logError("Dynamic DNS update failed - return code = ${resp.data}!") sendEvent(name: "lastResult", value: resp.data) } } // not null } // -------------------------------------------------------------- def performUpdate(base, uname, pwd, newIP, host) { logDebug( "in peformUpdate") Map params = [ uri: "${base}hostname=${host}&myip=${newIP}&username=${uname}&pwd=${pwd}", contentType: "text/plain", timeout: 20 ] logDebug("calling dns Update url = $params") asynchttpGet("updateCallback", params) } // ----------------------------------------------------------- def nsCallback(resp, data) { // test change logDebug "in callback" if (resp == null) { logError("Error - dynamic ip name ${settings.DDNSName} does not resolve!") } else { def jSlurp = new JsonSlurper() Map ipData = (Map)jSlurp.parseText((String)resp.data) logDebug( "ipdata = $ipData") if ((ipData == null) || (ipData.Status != 0)) { logError("Error - dynamic ip name ${settings.DDNSName} does not resolve!") } else { def String oldIP = ipData.Answer.data[0] sendEvent(name:"dynamicIPResult", value:ipData.Answer.data[0]) // now compare ip to our own and if different reset and log if ((oldIP != null) && (oldIP != "")) { def String currentIP = device.currentValue("currentIP") logInfo("Comparing resolved IP: $oldIP to $currentIP") if (currentIP != oldIP) { logInfo("IP address has Changed !!! Re-registering !") def String baseURL = device.getSetting("DDNSBaseURL") def String uname = device.getSetting("DDNSUsername") def String pwd = device.getSetting("DDNSPassword") def String host = device.getSetting("DDNSName") performUpdate(baseURL,uname,pwd,currentIP,host) } else logInfo("No change - Update not necessary.") } } } } // -------------------------------------------------- void DNSCheckCallback() { logInfo("Dns Update Check Callback Startup") def now = new Date().format('MM/dd/yyyy h:mm a',location.timeZone) sendEvent(name: "lastUpdateCheck", value: now) updated() } // --------------------------------------------- void updated() { // // Called everytime the user saves the driver preferences // try { logDebug("updated()"); publicIpReq() // Unschedule possible previous runIn() calls unschedule(); // lgk if ddns name resolve this first and do ip check before dniupdatr.. ALSO schedule the re-check. def String ddnsname = settings.DDNSName def Number ddnsupdatetime = settings.DDNSRefreshTime logDebug("DDNS Name = $ddnsname") logDebug("DDNS Refresh Time = $ddnsupdatetime") if ((ddnsname != null) && (ddnsname != "")) { logDebug("Got ddns name $ddnsname") // now resolve Map params = [ uri: "https://8.8.8.8/resolve?name=$ddnsname&type=A", contentType: "text/plain", timeout: 20 ] logDebug("calling dns Update url = $params") asynchttpGet("nsCallback", params) } // now schedule next run of update if ((ddnsupdatetime != null) && (ddnsupdatetime != 00)) { use(groovy.time.TimeCategory) { def thesecs = ddnsupdatetime * 3600 logInfo("Rescheduling IP Address Check to run again in $thesecs seconds.") runIn(thesecs, "DNSCheckCallback"); def Integer ntime = ddnsupdatetime def next = new Date() + ntime.hour def now = next.format('MM/dd/yyyy h:mm a',location.timeZone) sendEvent(name: "nextUpdateCheck", value: now) } } // Turn off debug log in 30 minutes if (logGetLevel() > 2) runIn(1800, logDebugOff); } catch (Exception e) { logError("Exception in updated(): ${e}"); } } // ------------------------------------------------------------ void uninstalled() { // // Called once when the driver is deleted // }