/* * UnifiNetworkChild-USMINI * * Description: * This Hubitat driver provides a spot to put data from USMINI Switches. * * Instructions for using Tile method: * 1) In "Preferences -> Tile Template" enter your template (example below) and click "Save Preferences" * Ex: "[b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/br]" * 2) 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 * 3) Select the Add Tile button and the tile should appear * NOTE1: Put a @ before and after variable names * NOTE2: 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 * Ability to change port names * Ability to start/stop device location signaling * Ability to turn on/off device LEDs * Ability to restart device * Holds a variety of data as attributes for use by other areas of Hubitat hub * * Licensing: * Copyright 2025 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 - Updated state, event, & tile handling, ability to change port names * 0.1.6 - Removed "Driver Status" and added RestartDevice command * 0.1.5 - Added Uptime as an attribute and updates to various functions * 0.1.4 - Correction to Tile Template preference and changes to port status reporting * 0.1.3 - Added refresh capability to get device specific information and revised HTML to Tile Template * 0.1.2 - Added 0 to port numbers <10 for better sorting * 0.1.1 - Updates to attributes and default settings * 0.1.0 - Initial version * * Thank you(s): * @Cobra for inspiration of how I perform driver version checking * @mircolino for original concept of HTML Template method */ // Returns the driver name def DriverName(){ return "UnifiNetworkChild-USMINI" } // Returns the driver version def DriverVersion(){ return "0.1.7" } // Driver Metadata metadata{ definition( name: "UnifiNetworkChild-USMINI", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/UnifiNetworkChild-USMINI.groovy" ) { capability "PresenceSensor" // Adds an attribute "presence" with possible values of "present" or "not present" capability "TemperatureMeasurement" capability "Actuator" capability "Refresh" // Commands command "StartLocateDevice" command "StopLocateDevice" command "LEDOn" command "LEDOff" command "SetPortName", [ [ name: "Port", type: "ENUM", constraints: [ 1, 2, 3, 4, 5 ], description: "Enter Port # (ex: 1 ... 5)" ], [ name: "State", type: "STRING", defaultValue: "Port #", description: "Port's Name" ] ] // Sets the name of the port command "RestartDevice" // Attributes - Driver Related attribute "Driver Name", "string" // Identifies the driver being used for update purposes attribute "Driver Version", "string" // Handles version for driver attribute "Driver Status", "string" // Handles version notices for driver // Attributes - Device Related attribute "LastSeen", "string" // Date/Time the device was last seen by the Unifi controller attribute "Memory Usage", "number" attribute "CPU Usage", "number" attribute "Overheating", "string" attribute "Satisfaction", "number" attribute "Model LTS", "string" attribute "Model EOL", "string" attribute "LED Override", "string" attribute "LED OnOff", "string" attribute "LED Brightness", "number" attribute "Port 01 Status", "string" attribute "Port 02 Status", "string" attribute "Port 03 Status", "string" attribute "Port 04 Status", "string" attribute "Port 05 Status", "string" attribute "Uptime", "string" // Tile attribute attribute "Tile", "string"; // Ex: "[b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/br]" } preferences{ //section{ if( ShowAllPreferences ){ if( state.DeviceName != null ){ input( type: "string", name: "DeviceName", title: "Device Name", description: "If set it will change the device's name on the controller.", defaultValue: "${ state.DeviceName }" ) } else { input( type: "string", name: "DeviceName", title: "Device Name", description: "If set it will change the device's name on the controller.", defaultValue: "" ) } input( name: "TileTemplate", type: "string", title: "Tile Template", description: "Ex: [b]Temperature:[/b] @temperature@°@location.getTemperatureScale()@[/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 ) } //} } } // SetDefaults sets "unknown" as initial value for some attributes of the device def SetDefaults(){ for( int x = 1; x <= 5; x++ ){ ProcessState( "Port 0${ x } Status", [ PortID: x, Name: "unknown", Enabled: "unknown", Media: "unknown", PoE: "unknown", PoE_Status: "unknown", PoE_Usage: "unknown", Speed: 0, Connected: "unknown" ] ) } ProcessState( "Uplink", [ PortID: "unknown", Name: "unknown", Enabled: "unknown", Media: "unknown", Speed: 0, RemotePort: "unknown", UplinkDevice: "unknown" ] ) ProcessEvent( "LastSeen", "Unknown" ) ProcessEvent( "Overheating", "Unknown" ) ProcessEvent( "Model LTS", "Unknown" ) ProcessEvent( "Model EOL", "Unknown" ) ProcessEvent( "LED Override", "Unknown" ) ProcessEvent( "LED OnOff", "Unknown" ) } // updated def updated( boolean NewDevice = false ){ 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() }" ) ProcessEvent( "DriverStatus", null ) if( LogType == null ){ LogType = "Info" } if( NewDevice != true ){ if( ( DeviceName != state.DeviceName ) ){ SendSettings() } } 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" ) Logging( "Updated", 2 ) } // RestartDevice attempts to restart device def RestartDevice(){ if( state.MAC != null ){ parent.RestartDevice( state.MAC ) } else { Logging( "No MAC known for ${ device.getDeviceNetworkId() }, cannot restart.", 5 ) } } // Configure device settings based on Preferences def SendSettings(){ if( state.ID != null ){ def Settings = "" if( DeviceName != null && DeviceName != device.label ){ parent.SendChildSettings( device.getDeviceNetworkId(), state.ID, "{\"name\":\"${ DeviceName }\"${ Settings } }" ) } else { parent.SendChildSettings( device.getDeviceNetworkId(), state.ID, "{\"name\":\"${ device.label }\"${ Settings } }" ) } } else { Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot send settings", 5 ) } } // Refresh the specific device's information def refresh(){ if( state.MAC != null ){ parent.RefreshSpecificUnifiDevice( state.MAC ) } else { Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot refresh.", 5 ) } } // Sets the name of the port def SetPortName( String PortNumber, String Name ){ if( state.ID != null && state.MAC != null ){ parent.SetPortName( device.getDeviceNetworkId(), state.ID, state.MAC, PortNumber as int, Name ) } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot set port name.", 5 ) } } // Starts the device location method def StartLocateDevice(){ if( state.MAC != null ){ parent.LocateDevice( state.MAC, "On" ) } else { Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot locate/identify.", 5 ) } } // Stops the device location method def StopLocateDevice(){ if( state.MAC != null ){ parent.LocateDevice( state.MAC, "Off" ) } else { Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot locate/identify.", 5 ) } } // Turn on the device's status LED(s) def LEDOn(){ if( state.ID != null ){ parent.SetLEDOnOff( device.getDeviceNetworkId(), state.ID, state.MAC, "On" ) } else { Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set the device's LED.", 5 ) } } // Turn off the device's status LED(s) def LEDOff(){ if( state.ID != null ){ parent.SetLEDOnOff( device.getDeviceNetworkId(), state.ID, state.MAC, "Off" ) } else { Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set the device's LED.", 5 ) } } // installed is called when the device is installed, all it really does is run updated def installed(){ Logging( "Installed", 2 ) SetDefaults() updated( true ) } // initialize is called when the device is initialized, all it really does is run updated def initialize(){ Logging( "Initialized", 2 ) updated( true ) } // Return a state value def ReturnState( Variable ){ return state."${ Variable }" } // Tile method to produce HTML formatted string for dashboard use private void UpdateTile( String val ){ if( TileTemplate != null ){ def TempString = "" Parsing = TileTemplate Parsing = Parsing.replaceAll( "\\[", "<" ) Parsing = Parsing.replaceAll( "\\]", ">" ) Count = Parsing.count( "@" ) if( Count >= 1 ){ def x = 1 while( x <= Count ){ TempName = Parsing.split( "@" )[ x ] switch( TempName ){ case "location.latitude": Value = location.latitude break case "location.longitude": Value = location.longitude break case "location.getTemperatureScale()": Value = location.getTemperatureScale() break default: Value = ReturnState( "${ TempName }" ) break } TempString = TempString + Parsing.split( "@" )[ ( x - 1 ) ] + Value x = ( x + 2 ) } if( Parsing.split( "@" ).last() != Parsing.split( "@" )[ Count - 1 ] ){ TempString = TempString + Parsing.split( "@" ).last() } } else if( Count == 1 ){ TempName = Parsing.split( "@" )[ 1 ] switch( TempName ){ case "location.latitude": Value = location.latitude break case "location.longitude": Value = location.longitude break case "location.getTemperatureScale()": Value = location.getTemperatureScale() break default: Value = ReturnState( "${ TempName }" ) break } TempString = TempString + Parsing.split( "@" )[ 0 ] + Value } else { TempString = TileTemplate } Logging( "Tile = ${ TempString }", 4 ) sendEvent( name: "Tile", value: TempString ) } } // 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, Description = null ){ if( ForceEvent ){ sendEvent( name: Variable, value: Value, unit: Unit, isStateChange: true, descriptionText: Description ) } else { sendEvent( name: Variable, value: Value, unit: Unit, descriptionText: Description ) } Logging( "Event: ${ Variable } = ${ Value } Unit = ${ Unit } Forced = ${ ForceEvent }", 4 ) ProcessState( Variable, Value ) UpdateTile( "${ Value }" ) } // Set a state variable to a value def ProcessState( 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( "Driver Name", DriverName() ) ProcessEvent( "Driver Version", 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( "Driver Status", "Driver replaced, please use ${ resp.data."${ state.'Driver Name' }".file }" ) } else if( resp.data."${ DriverName() }".version == "REMOVED" ){ ProcessEvent( "Driver Status", "Driver removed and no longer supported." ) } else { SiteVersion = resp.data."${ DriverName() }".version.split( /\./ ) if( CurrentVersion == SiteVersion ){ Logging( "Driver version up to date", 3 ) ProcessEvent( "Driver Status", "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( "Driver Status", "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( "Driver Status", "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( "Driver Status", "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( "Driver Status", "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( "Driver Status", "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( "Driver Status", "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" ) } } } else { Logging( "${ DriverName() } is not published on drdsnell.com", 2 ) ProcessEvent( "Driver Status", "${ DriverName() } is not published on drdsnell.com" ) } break default: Logging( "Unable to check drdsnell.com for ${ DriverName() } driver updates.", 2 ) break } } }