/* * UnifiNetworkChild-UP6 * * Description: * This Hubitat driver provides a spot to put data from UP6 device(s). * * 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.8 - Removed "Device Status" and added RestartDevice command * 0.1.7 - Correction to ProcessEvent function and removal of old driver-specific attributes when Preferences are saved * 0.1.6 - Added Uptime as an attribute and updates to various functions * 0.1.5 - Correction to Tile Template preference * 0.1.4 - Added refresh capability to get device specific information and revised HTML to Tile Template * 0.1.2 - Additional commands to toggle outlets and USB ports * 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 HTML Template method for dashboard use * for providing the basis for much of what is possible with the API */ // Returns the driver name def DriverName(){ return "UnifiNetworkChild-UP6" } // Returns the driver version def DriverVersion(){ return "0.1.8" } // Driver Metadata metadata{ definition( name: "UnifiNetworkChild-UP6", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/UnifiNetworkChild-UP6.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 "OutletOn", [ [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ] ] // Power on a specific outlet command "OutletOff", [ [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ] ] // Power off a specific outlet command "ToggleOutlet", [ [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ] ] // Toggle a specific outlet command "PowerCycleOutlet", [ [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ] ] // Power cycles a specific outlet command "USB_On" // Power on USB ports command "USB_Off" // Power off USB ports command "PowerCycleUSB" // Power cycles the USB ports command "ToggleOutlet1" // Toggle power of Outlet1 command "ToggleOutlet2" // Toggle power of Outlet2 command "ToggleOutlet3" // Toggle power of Outlet3 command "ToggleOutlet4" // Toggle power of Outlet4 command "ToggleOutlet5" // Toggle power of Outlet5 command "ToggleOutlet6" // Toggle power of Outlet6 command "ToggleUSBPorts" // Toggle power of USB Ports command "RestartDevice" // 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 "Last Seen", "string" // Date/Time the device was last seen by the Unifi controller attribute "Satisfaction", "number" attribute "Model LTS", "string" attribute "Model EOL", "string" attribute "LED Override", "string" attribute "LED OnOff", "string" attribute "USB Ports", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 1", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 2", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 3", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 4", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 5", "enum", [ "on", "off", "Unknown" ] attribute "Outlet 6", "enum", [ "on", "off", "Unknown" ] attribute "USB Ports Enabled", "string" attribute "Outlet 1 Enabled", "string" attribute "Outlet 2 Enabled", "string" attribute "Outlet 3 Enabled", "string" attribute "Outlet 4 Enabled", "string" attribute "Outlet 5 Enabled", "string" attribute "Outlet 6 Enabled", "string" attribute "Uptime", "string" // Tile Template attribute attribute "Tile", "string"; // Ex: "[font size='2'][b]Temperature:[/b] ${ temperature }°${ location.getTemperatureScale() }[/br][/font]" } 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] \${ state.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 <= 6; x++ ){ ProcessEvent( "Outlet ${ x }", "Unknown" ) } ProcessEvent( "USB Ports", "Unknown" ) ProcessEvent( "Last Seen", "Unknown" ) ProcessEvent( "Model LTS", "Unknown" ) ProcessEvent( "Model EOL", "Unknown" ) ProcessEvent( "LED Override", "Unknown" ) } // updated def updated( boolean NewDevice = false ){ if( state."Driver Name" != 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" ) } ProcessState( "DriverName", "${ DriverName() }" ) ProcessState( "DriverVersion", "${ DriverVersion() }" ) 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 ) } // 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 ) } } // 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 ) } } // 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 ) } } // Turn on a specific outlet def OutletOn( int Outlet ){ if( state.MAC != null && state.ID != null ){ parent.PowerOutlet( device.getDeviceNetworkId(), state.ID, state.MAC, Outlet, "On" ) } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot power on outlet ${ Outlet }.", 5 ) } } //Turn off a specific outlet def OutletOff( int Outlet ){ if( state.MAC != null && state.ID != null ){ parent.PowerOutlet( device.getDeviceNetworkId(), state.ID, state.MAC, Outlet, "Off" ) } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot power off outlet ${ Outlet }.", 5 ) } } // Turn on USB ports def USB_On(){ if( state.MAC != null && state.ID != null ){ parent.PowerOutlet( device.getDeviceNetworkId(), state.ID, state.MAC, 7, "On" ) } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot power on USB ports.", 5 ) } } //Turn off USB ports def USB_Off(){ if( state.MAC != null && state.ID != null ){ parent.PowerOutlet( device.getDeviceNetworkId(), state.ID, state.MAC, 7, "Off" ) } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot power off USB ports.", 5 ) } } // Power cycle a specific Outlet def PowerCyclePort( String Outlet ){ if( state.MAC != null ){ parent.PowerCycleOutlet( state.MAC, Outlet ) } else { Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot power cycle the outlet.", 5 ) } } // Power cycle the USB ports def PowerCycleUSB(){ if( state.MAC != null ){ parent.PowerCycleOutlet( state.MAC, 7 ) } else { Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot power cycle the USB ports.", 5 ) } } // Toggle power of an Outlet def ToggleOutlet( int Outlet ){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet ${ Outlet }' == "off" ){ OutletOn( Outlet ) } else { OutletOff( Outlet ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 1 def ToggleOutlet1(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 1' == "off" ){ OutletOn( 1 ) } else { OutletOff( 1 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 2 def ToggleOutlet2(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 2' == "off" ){ OutletOn( 2 ) } else { OutletOff( 2 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 3 def ToggleOutlet3(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 3' == "off" ){ OutletOn( 3 ) } else { OutletOff( 3 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 4 def ToggleOutlet4(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 4' == "off" ){ OutletOn( 4 ) } else { OutletOff( 4 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 5 def ToggleOutlet5(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 5' == "off" ){ OutletOn( 5 ) } else { OutletOff( 5 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of Outlet 6 def ToggleOutlet6(){ if( state.ID != null && state.MAC != null ){ if( state.'Outlet 6' == "off" ){ OutletOn( 6 ) } else { OutletOff( 6 ) } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the outlet.", 5 ) } } // Toggle power of the USB ports def ToggleUSBPorts(){ if( state.ID != null && state.MAC != null ){ if( state.'USB Ports' == "off" ){ USB_On() } else { USB_Off() } } else { Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot toggle the USB ports.", 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 Template method based on @mircolino's HTML Template method private void UpdateTile( String val ){ if( settings.TileTemplate ){ // Create special compund/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 } } }