/*
* UnifiNetworkChild-USPM16P
*
* Description:
* This Hubitat driver provides a spot to put data for 16 port Pro Max PoE switches.
*
* 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.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-USPM16P"
}
// Returns the driver version
def DriverVersion(){
return "0.1.0"
}
// Driver Metadata
metadata{
definition( name: "UnifiNetworkChild-USPM16P", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/UnifiNetworkChild-USPM16P.groovy" ) {
capability "PresenceSensor" // Adds an attribute "presence" with possible values of "present" or "not present"
capability "TemperatureMeasurement"
capability "Actuator"
capability "Refresh"
capability "PowerMeter"
capability "VoltageMeasurement"
// Commands
command "SetEtherBrightness", [ [ name: "Brightness*", type: "NUMBER", constraints: [ 0 .. 100 ], description: "Brightness from 0 to 100" ] ]
command "SetEtherBehavior", [ [ name: "Mode*", type: "ENUM", defaultValue: "breath", constraints: [ "breath", "steady" ], description: "Etherlighting Behavior" ] ]
command "SetEtherMode", [ [ name: "Mode*", type: "ENUM", defaultValue: "speed", constraints: [ "speed", "network" ], description: "Etherlighting Mode" ] ]
command "OverrideEtherSpeed", [ [ name: "Speed*", type: "ENUM", defaultValue: "GbE", constraints: [ "FE", "GbE", "2.5GbE", "10GbE" ], description: "Port Speed" ],
[ name: "RGB*", type: "STRING", description: "RGB Color (HTML: 000000 to FFFFFF format)" ] ]
command "StartLocateDevice"
command "StopLocateDevice"
command "LEDOn"
command "LEDOff"
command "SetPoePortState", [
[ name: "Port", type: "ENUM", constraints: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], description: "Enter PoE Port # (ex: 1 ... 16)" ],
[ name: "State", type: "ENUM", defaultValue: "auto", constraints: [ "auto", "off" ], description: "Port's PoE state" ]
] // Sets the state of the PoE port
command "PowerCyclePoePort", [
[ name: "Port", type: "ENUM", constraints: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], description: "Enter PoE Port # (ex: 1 ... 16)" ]
] // Power cycles the PoE for a specific port of a switch
command "RestartDevice"
//command "DoSomething" // Used for testing/development purposes, comment out before publishing
// 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 "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"
// Uplink port identifies which port is being used for Uplink, media type, and Mbps
attribute "Uplink", "map"
// Known statuses involve Port Enablement, PoE, whether a client is connected, and Mbps
attribute "Port 01 Status", "map"
attribute "Port 02 Status", "map"
attribute "Port 03 Status", "map"
attribute "Port 04 Status", "map"
attribute "Port 05 Status", "map"
attribute "Port 06 Status", "map"
attribute "Port 07 Status", "map"
attribute "Port 08 Status", "map"
attribute "Port 09 Status", "map"
attribute "Port 10 Status", "map"
attribute "Port 11 Status", "map"
attribute "Port 12 Status", "map"
attribute "Port 13 Status", "map"
attribute "Port 14 Status", "map"
attribute "Port 15 Status", "map"
attribute "Port 16 Status", "map"
attribute "Port 17 Status", "map"
attribute "Port 18 Status", "map"
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: "" )
}
if( state.LCMBrightness != null ){
input( type: "number", name: "LCMBrightness", title: "LCD Brightness", description: "Sets the brightness of the LCD display.", defaultValue: state.LCMBrightness, minValue: 10, maxValue: 100, required: false )
} else {
input( type: "number", name: "LCMBrightness", title: "LCD Brightness", description: "Sets the brightness of the LCD display.", defaultValue: 75, minValue: 10, maxValue: 100, required: false )
}
if( state.LCMNightStart != null ){
input( type: "string", name: "LCMNightStart", title: "LCD Night Mode Start", description: "When the LCD should start night mode. Formatted as 22:00 = 10pm", defaultValue: "${ state.LCMNightStart }", required: false )
} else {
input( type: "string", name: "LCMNightStart", title: "LCD Night Mode Start", description: "When the LCD should start night mode. Formatted as 22:00 = 10pm", defaultValue: "22:00", required: false )
}
if( state.LCMNightEnd != null ){
input( type: "string", name: "LCMNightEnd", title: "LCD Night Mode End", description: "When the LCD should end night mode. Formatted as 08:00 = 8am", defaultValue: "${ state.LCMNightEnd }", required: false )
} else {
input( type: "string", name: "LCMNightEnd", title: "LCD Night Mode End", description: "When the LCD should end night mode. Formatted as 08:00 = 8am", defaultValue: "08:00", required: false )
}
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 )
}
//}
}
}
// Meant for testing and development purposes, command should be commented out and code blanked when published
def DoSomething(){
}
// SetDefaults sets all the default values for the device
def SetDefaults(){
for( int x = 1; x <= 9; x++ ){
ProcessState( "Port 0${ x } Status", [ PortID: x, Enabled: "unknown", Media: "unknown", PoE: "unknown", PoE_Status: "unknown", PoE_Usage: "unknown", Speed: 0, Connected: "unknown" ] )
}
for( int x = 10; x <= 18; x++ ){
ProcessState( "Port ${ x } Status", [ PortID: x, Enabled: "unknown", Media: "unknown", PoE: "unknown", PoE_Status: "unknown", PoE_Usage: "unknown", Speed: 0, Connected: "unknown" ] )
}
ProcessState( "Uplink", [ PortID: "unknown", Enabled: "unknown", Media: "unknown", Speed: 0, RemotePort: "unknown", UplinkDevice: "unknown" ] )
ProcessEvent( "Last Seen", "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 ){
// Set initial states for driver attributes
ProcessState( "DriverName", "${ DriverName() }" )
ProcessState( "DriverStatus", null )
ProcessState( "DriverVersion", "${ DriverVersion() }" )
if( LogType == null ){
LogType = "Info"
}
if( NewDevice != true ){
if( ( DeviceName != state.DeviceName ) || ( LCMBrightness != state.LCMBrightness ) || ( LCMNightStart != state.LCMNightStart ) || ( LCMNightEnd != state.LCMNightEnd ) ){
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( ( LCMBrightness != state.LCMBrightness ) || ( LCMNightStart != state.LCMNightStart ) || ( LCMNightEnd != state.LCMNightEnd ) ){
if( LCMBrightness != null ){
Settings = Settings + ",\"lcm_brightness\":${ LCMBrightness }"
} else if( state.LCMBrightness != null ){
Settings = Settings + ",\"lcm_brightness\":${ state.LCMBrightness }"
}
Settings = Settings + ",\"lcm_brightness_override\":true"
if( LCMNightStart != null ){
Settings = Settings + ",\"lcm_night_mode_begins\":\"${ LCMNightStart }\""
} else if( state.LCMNightStart != null ){
Settings = Settings + ",\"lcm_night_mode_begins\":\"${ state.LCMNightStart }\""
}
if( LCMNightEnd != null ){
Settings = Settings + ",\"lcm_night_mode_ends\":\"${ LCMNightEnd }\""
} else if( state.LCMNightEnd != null ){
Settings = Settings + ",\"lcm_night_mode_ends\":\"${ state.LCMNightEnd }\""
}
}
if( DeviceName != null && DeviceName != device.label ){
if( DeviceName != "null" ){
parent.SendChildSettings( device.getDeviceNetworkId(), state.ID, "{\"name\":\"${ DeviceName }\"${ Settings } }" )
} else {
parent.SendChildSettings( device.getDeviceNetworkId(), state.ID, "${ 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 )
}
}
// Sets the state of the PoE port
def SetPoePortState( String PortNumber, String State ){
if( state.ID != null && state.MAC != null ){
parent.SetPortState( device.getDeviceNetworkId(), state.ID, state.MAC, PortNumber as int, State )
} else {
Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot set port.", 5 )
}
}
// Sets the state of the PoE port
def PowerCyclePoePort( String PortNumber ){
if( state.MAC != null ){
parent.PowerCyclePort( state.MAC, PortNumber as int )
} else {
Logging( "No MAC for ${ device.getDeviceNetworkId() }, cannot power cycle port.", 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 )
}
}
// Set the brightness of the Etherlighting
def SetEtherBrightness( Number Brightness ){
if( state.ID != null ){
parent.SetEtherBrightness( device.getDeviceNetworkId(), device.label, state.ID, Brightness )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set the device's Etherlighting brightness.", 5 )
}
}
// Set the mode of the Etherlighting
def SetEtherMode( String Mode ){
if( state.ID != null ){
parent.SetEtherMode( device.getDeviceNetworkId(), device.label, state.ID, Mode )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set the device's Etherlighting mode.", 5 )
}
}
// Set the behavior of the Etherlighting
def SetEtherBehavior( String Behavior ){
if( state.ID != null ){
parent.SetEtherBehavior( device.getDeviceNetworkId(), device.label, state.ID, Behavior )
} else {
Logging( "No ID for ${ device.getDeviceNetworkId() }, cannot set the device's Etherlighting behavior.", 5 )
}
}
// Set the override for the Etherlighting speed indication
def OverrideEtherSpeed( String RGB, String Speed ){
parent.OverrideEtherSpeed( RGB, Speed )
}
// Turn on the device's status LED(s)
def LEDOn(){
if( state.ID != null && state.MAC != null ){
parent.SetLEDOnOff( device.getDeviceNetworkId(), state.ID, state.MAC, "On" )
} else {
Logging( "No ID or MAC for ${ device.getDeviceNetworkId() }, cannot set the device's LED.", 5 )
}
}
// Turn off the device's status LED(s)
def LEDOff(){
if( state.ID != null && state.MAC != null ){
parent.SetLEDOnOff( device.getDeviceNetworkId(), state.ID, state.MAC, "Off" )
} else {
Logging( "No ID or MAC 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
}
}
}