/* * UnifiNetworkChild-ClientCheck * * Description: * This Hubitat driver provides a spot to put data from Unifi clients used to regularly monitor their online state. * Clients are only checked hourly and does not take the place of the Presence check. * * Instructions for using Tile Template method (originally based on @mircolino's HTML Templates): * 1) In "Hubitat -> Devices" select the child/sensor (not the parent) you would like to "templetize" * 2) In "Preferences -> Tile Template" enter your template (example below) and click "Save Preferences" * Ex: "[font size='2'][b]Temperature:[/b] ${ temperature }°${ location.getTemperatureScale() }[/br][/font]" * 3) In a Hubitat dashboard, add a new tile, and select the child/sensor, in the center select "Attribute", and on the right select the "Tile" attribute * 4) Select the Add Tile button and the tile should appear * NOTE: Should accept most HTML formatting commands with [] instead of <> * * Features List: * Ability to check a website (mine) to notify user if there is a newer version of the driver available * * Licensing: * Copyright 2024 David Snell * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * * Version Control: * 0.1.7 - Removed "Device Status" * 0.1.6 - Correction to ProcessEvent function and removal of old driver-specific attributes when Preferences are saved * 0.1.5 - Changed attribute names "Driver Name" to "DriverName", "Driver Version" to "DriverVersion", "Driver Status" to "DriverStatus", and mappings for "Last Seen" to "LastSeen", "Connected To MAC" to "ConnectedToMAC", "Connected To Name" to "ConnectedToName", and "Signal Strength" to "SignalStrength" to start removing visual errors on device page. * 0.1.4 - Added Uptime to the ClientCheck map * 0.1.3 - Updates to various functions * 0.1.2 - Changed client check scheduling when there are only 1 or 2 clients * 0.1.1 - Correction to reset clients when preferences are saved and scheduling of client checks * 0.1.0 - Initial version * * Thank you(s): * @Cobra for inspiration of how I perform driver version checking * @mircolino for HTML Template method for dashboard use */ // Returns the driver name def DriverName(){ return "UnifiNetworkChild-ClientCheck" } // Returns the driver version def DriverVersion(){ return "0.1.7" } // Driver Metadata metadata{ definition( name: "UnifiNetworkChild-ClientCheck", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/UnifiNetworkChild-ClientCheck.groovy" ) { capability "Actuator" // Commands //command "DoSomething" // Meant for testing purposes only and should be commented out before publishing command "CheckSpecificClientNumber", [ [ name: "Number", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ], description: "Enter Client # (ex: 0, 1, 2... 20)" ] ] // Sends a command to the parent device to check the specific client //command "CheckSpecificClientMAC", [ // [ name: "MAC", type: "string", description: "Enter Client MAC Address" ] //] // Sends a command to the parent device to check the specific client // Attributes - Driver Related attribute "DriverName", "string" // Identifies the driver being used for update purposes attribute "DriverVersion", "string" // Handles version for driver attribute "DriverStatus", "string" // Handles version notices for driver // Attributes - Device Related attribute "Client 1", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 2", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 3", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 4", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 5", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 6", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 7", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 8", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 9", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 10", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 11", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 12", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 13", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 14", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 15", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 16", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 17", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 18", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 19", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] attribute "Client 20", "map" // Map of client values [ MAC, Status: Online/Offline, Name, LastSeen, ConnectedToMAC, ConnectedToName, Speed, SignalStrength, SSID, Uptime ] // Tile Template attribute attribute "Tile", "string"; // Ex: "[b]Client 1 Status:[/b] \${ state.'Client 1'.Status }[/br]" } preferences{ //section{ if( ShowAllPreferences ){ input( type: "string", name: "ClientList", title: "Client MAC Address(s) to Check (separate with ; and must be 20 or less addresses)", required: false ) input( name: "TileTemplate", type: "string", title: "Tile Template", description: "Ex: [b]Client 1 Status:[/b] \${ state.'Client 1'.Status }[/br]", defaultValue: ""); input( type: "enum", name: "LogType", title: "Enable Logging?", required: false, multiple: false, options: [ "None", "Info", "Debug", "Trace" ], defaultValue: "Info" ) input( type: "bool", name: "ShowAllPreferences", title: "Show All Preferences?", defaultValue: true ) } else { input( type: "bool", name: "ShowAllPreferences", title: "Show All Preferences?", defaultValue: true ) } //} } } // DoSomething is meant for testing purposes only and should be commented out before publishing def DoSomething(){ } // updated def updated(){ if( state."Driver Status" != null ){ state.remove( "Driver Name" ) state.remove( "Driver Version" ) state.remove( "Driver Status" ) device.deleteCurrentState( "Driver Status" ) device.deleteCurrentState( "Driver Name" ) device.deleteCurrentState( "Driver Version" ) } ProcessEvent( "DriverName", "${ DriverName() }" ) ProcessEvent( "DriverVersion", "${ DriverVersion() }" ) if( LogType == null ){ LogType = "Info" } unschedule() def Hour = ( new Date().format( "h" ) as int ) def Minute = ( new Date().format( "m" ) as int ) def Second = ( new Date().format( "s" ) as int ) // Schedule checks that are only performed once a day schedule( "${ Second } ${ Minute } ${ Hour } ? * *", "CheckForUpdate" ) // Setup Client List default values if they do not already exist if( ClientList != null ){ Logging( "Client List size = ${ ClientList.split( ";" ).size() }", 4 ) def SplitList = ClientList.split( ";" ) if( SplitList.size() <= 20 ){ def Count = 1 SplitList.each(){ def TempMap = [ MAC: "${ it.value }", Status: "unknown", Name: "unknown", LastSeen: "unknown", ConnectedToMAC: "unknown", ConnectedToName: "unknown", Speed: 0, SignalStrength: "unknown", SSID: "unknown", Uptime: "unknown" ] ProcessEvent( "Client ${ Count }", TempMap ) Count ++ } // Setup scheduling for Client List checking if any Clients are listed if( ClientList.split( ";" ).size() > 2 ){ schedule( "${ Second } 0/${ ( 60 / ClientList.split( ";" ).size() ) } * * * ?", CheckClient ) } else { schedule( "${ Second } 0/30 * * * ?", CheckClient ) } } else { ProcessEvent( "Status", "Must be 20 or less MAC addresses for Client Checking.", 5 ) Logging( "Too many MAC addresses to check regularly.", 5 ) } } ProcessState( "ClientCounter", 1 ) Logging( "Updated", 2 ) } // Calls for a client check, meant for scheduled jobs def CheckClient(){ if( state.ClientCounter > ClientList.split( ";" ).size() ){ state.ClientCounter = 1 } def ClientData = state."Client ${ state.ClientCounter }" if( ClientData != null ){ parent.CheckClient( ClientData.MAC, state.ClientCounter ) } else { Logging( "Client data for Client ${ state.ClientCounter } missing.", 5 ) } state.ClientCounter ++ } // Calls for a specific client to be checked def CheckSpecificClientNumber( Number ){ if( Number != null ){ def ClientData = state."Client ${ Number }" if( ClientData != null ){ parent.CheckClient( ClientData.MAC, Number ) } else { Logging( "Client data for Client ${ Number } missing.", 5 ) } } } // Refresh def refresh(){ } // installed is called when the device is installed, all it really does is run updated def installed(){ Logging( "Installed", 2 ) updated() } // initialize is called when the device is initialized, all it really does is run updated def initialize(){ Logging( "Initialized", 2 ) updated() } // Return a state value def ReturnState( Variable ){ return state."${ Variable }" } // Tile Template method based on @mircolino's HTML Template method private void UpdateTile( String val ){ if( settings.TileTemplate ){ // Create special compound/html tile val = settings.TileTemplate.toString().replaceAll( "\\[", "<" ) val = val.replaceAll( "\\]", ">" ) val = val.replaceAll( ~/\$\{\s*([A-Za-z][A-Za-z0-9_]*)\s*\}/ ) { java.util.ArrayList m -> device.currentValue("${ m [ 1 ] }").toString() } if( device.currentValue( "Tile" ).toString() != val ){ sendEvent( name: "Tile", value: val ) } } } // Process data to check against current state value and then send an event if it has changed def ProcessEvent( Variable, Value, Unit = null, ForceEvent = false ){ if( ( state."${ Variable }" != Value ) || ( ForceEvent == true ) ){ state."${ Variable }" = Value if( Unit != null ){ Logging( "Event: ${ Variable } = ${ Value }${ Unit }", 4 ) sendEvent( name: "${ Variable }", value: Value, unit: Unit, isStateChange: true ) } else { Logging( "Event: ${ Variable } = ${ Value }", 4 ) sendEvent( name: "${ Variable }", value: Value, isStateChange: true ) } UpdateTile( "${ Value }" ) } } // Process data to check against current state value def ProcessState( Variable, Value ){ if( state."${ Variable }" != Value ){ Logging( "State: ${ Variable } = ${ Value }", 4 ) state."${ Variable }" = Value UpdateTile( "${ Value }" ) } } // Handles whether logging is enabled and thus what to put there. def Logging( LogMessage, LogLevel ){ // Add all messages as info logging if( ( LogLevel == 2 ) && ( LogType != "None" ) ){ log.info( "${ device.displayName } - ${ LogMessage }" ) } else if( ( LogLevel == 3 ) && ( ( LogType == "Debug" ) || ( LogType == "Trace" ) ) ){ log.debug( "${ device.displayName } - ${ LogMessage }" ) } else if( ( LogLevel == 4 ) && ( LogType == "Trace" ) ){ log.trace( "${ device.displayName } - ${ LogMessage }" ) } else if( LogLevel == 5 ){ log.error( "${ device.displayName } - ${ LogMessage }" ) } } // Checks drdsnell.com for the latest version of the driver // Original inspiration from @cobra's version checking def CheckForUpdate(){ ProcessEvent( "DriverName", DriverName() ) ProcessEvent( "DriverVersion", DriverVersion() ) httpGet( uri: "https://www.drdsnell.com/projects/hubitat/drivers/versions.json", contentType: "application/json" ){ resp -> switch( resp.status ){ case 200: if( resp.data."${ DriverName() }" ){ CurrentVersion = DriverVersion().split( /\./ ) if( resp.data."${ DriverName() }".version == "REPLACED" ){ ProcessEvent( "DriverStatus", "Driver replaced, please use ${ resp.data."${ state.DriverName }".file }" ) } else if( resp.data."${ DriverName() }".version == "REMOVED" ){ ProcessEvent( "DriverStatus", "Driver removed and no longer supported." ) } else { SiteVersion = resp.data."${ DriverName() }".version.split( /\./ ) if( CurrentVersion == SiteVersion ){ Logging( "Driver version up to date", 3 ) ProcessEvent( "DriverStatus", "Up to date" ) } else if( ( CurrentVersion[ 0 ] as int ) > ( SiteVersion [ 0 ] as int ) ){ Logging( "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 ) ProcessEvent( "DriverStatus", "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" ) } else if( ( CurrentVersion[ 1 ] as int ) > ( SiteVersion [ 1 ] as int ) ){ Logging( "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 ) ProcessEvent( "DriverStatus", "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" ) } else if( ( CurrentVersion[ 2 ] as int ) > ( SiteVersion [ 2 ] as int ) ){ Logging( "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 ) ProcessEvent( "DriverStatus", "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" ) } else if( ( SiteVersion[ 0 ] as int ) > ( CurrentVersion[ 0 ] as int ) ){ Logging( "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 ) ProcessEvent( "DriverStatus", "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" ) } else if( ( SiteVersion[ 1 ] as int ) > ( CurrentVersion[ 1 ] as int ) ){ Logging( "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 ) ProcessEvent( "DriverStatus", "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" ) } else if( ( SiteVersion[ 2 ] as int ) > ( CurrentVersion[ 2 ] as int ) ){ Logging( "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 ) ProcessEvent( "DriverStatus", "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" ) } } } else { Logging( "${ DriverName() } is not published on drdsnell.com", 2 ) ProcessEvent( "DriverStatus", "${ DriverName() } is not published on drdsnell.com" ) } break default: Logging( "Unable to check drdsnell.com for ${ DriverName() } driver updates.", 2 ) break } } }