/*
* NeptuneSystemsApexChild
*
* Description:
* This Hubitat driver provides a spot to put Apex data in children of the parent device. It has no
* ability to check anything on it's own but the children make it much easier for dashboard and comparison purposes.
*
* 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 receive just state variables without posting events
* Ability to display Apex attributes
* 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.2.8 - Correction to ProcessEvent function and removal of old driver-specific attributes when Preferences are saved
* 0.2.7 - Additional states and attributes added
* 0.2.6 - Rework of the version checking and Tile Template replacement of HTML Template
* 0.2.5 - Changes to driver version checking, fixes to updated, removal of the ClearStateVariables command
* 0.2.4 - Additional attributes to support a Trident
* 0.2.3 - Added some additional attributes and changed Clear command name
* 0.2.2 - Added support for pH2
* 0.2.1 - Added support for virtual outlets
* 0.2.0 - Fixed update check and added attributes
* 0.1.1 - Added Outputs (to support AI, DOS, and Vortech modules)
* 0.1.0 - Initial revision
*
* Thank you(s):
* Thank you to @Cobra for inspiration of how I perform driver version checking
* Thank you to @mircolino for working out a parent/child method and pointing out other areas for significant improvement as well as coming up with the
* HTML Template method to assist in formatting child/sensors for dashboard use
*/
// Returns the driver name
def DriverName(){
return "NeptuneSystemsApexChild"
}
// Returns the driver version
def DriverVersion(){
return "0.2.8"
}
// Driver Metadata
metadata{
definition( name: "NeptuneSystemsApexChild", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/NeptuneSystemsApexChild.groovy" ) {
capability "Sensor"
capability "TemperatureMeasurement"
capability "pHMeasurement"
capability "VoltageMeasurement"
capability "PowerMeter"
// Commands
// Attributes for the driver itself
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 being built into the device
attribute "Software", "string" // Software keeps track of the version of the Apex software itself
attribute "temperature", "number" // Temperature value received
attribute "pH", "number" // pH value received
attribute "pH2", "number" // pH2 value received
attribute "ORP", "number" // ORP value received
attribute "Salinity", "number" // Salinity value received
attribute "voltage", "number" // Voltage value received from modules like EB832
attribute "Amps", "number" // Amps value received from modules like EB4 or EB8, not a sum for EB832
attribute "power", "number" // Total power value received from modules like EB832
attribute "Hostname", "string" // Hostname returned by the Apex
attribute "Updated", "string" // Date/time the Apex indicated the data was from
attribute "Serial", "string" // Serial # of the Apex
attribute "Apex Model", "string" // What model Apex was determined
// Attribute for modules that have on/off capabilities like AFS
attribute "switch", "ENUM", [ "on", "off" ]
attribute "Feeder Mode", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
// Attributes known to exist on Apex systems
attribute "Switch Input 1", "string"
attribute "Switch Input 2", "string"
attribute "Switch Input 3", "string"
attribute "Switch Input 4", "string"
attribute "Switch Input 5", "string"
attribute "Switch Input 6", "string"
attribute "Variable Output 1", "string"
attribute "Variable Output 2", "string"
attribute "Variable Output 3", "string"
attribute "Variable Output 4", "string"
attribute "Switch Input 1 Name", "string"
attribute "Switch Input 2 Name", "string"
attribute "Switch Input 3 Name", "string"
attribute "Switch Input 4 Name", "string"
attribute "Switch Input 5 Name", "string"
attribute "Switch Input 6 Name", "string"
attribute "Variable Output 1 Name", "string"
attribute "Variable Output 2 Name", "string"
attribute "Variable Output 3 Name", "string"
attribute "Variable Output 4 Name", "string"
attribute "Alarm", "string"
attribute "Warn", "string"
attribute "Email", "string"
// Outputs
attribute "Output 1 Name", "string"
attribute "Output 2 Name", "string"
attribute "Output 1", "string"
attribute "Output 2", "string"
// Outlets
attribute "Outlet 1 Name", "string"
attribute "Outlet 2 Name", "string"
attribute "Outlet 3 Name", "string"
attribute "Outlet 4 Name", "string"
attribute "Outlet 5 Name", "string"
attribute "Outlet 6 Name", "string"
attribute "Outlet 7 Name", "string"
attribute "Outlet 8 Name", "string"
attribute "Outlet 1", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 2", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 3", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 4", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 5", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 6", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 7", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Outlet 8", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "24v Name", "string"
attribute "24v A Name", "string"
attribute "24v B Name", "string"
attribute "24v", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "24v A", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "24v B", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
// What the child device is thought to be
attribute "Type", "string"
// Attributes to allow users to set custom tests
attribute "ApexInputNumber1", "number"
attribute "ApexInputNumber2", "number"
attribute "ApexInputNumber3", "number"
attribute "ApexInputNumber4", "number"
attribute "ApexOutputNumber1", "number"
attribute "ApexOutputNumber2", "number"
attribute "ApexOutputString1", "string"
attribute "ApexOutputString2", "string"
// Attributes for Variable Outputs
attribute "Variable 1 Name", "string"
attribute "Variable 2 Name", "string"
attribute "Variable 3 Name", "string"
attribute "Variable 4 Name", "string"
attribute "Variable 1 Intensity", "number"
attribute "Variable 2 Intensity", "number"
attribute "Variable 3 Intensity", "number"
attribute "Variable 4 Intensity", "number"
// Attributes for Serial Outputs
attribute "Serial 1 Name", "string"
attribute "Serial 2 Name", "string"
attribute "Serial 1 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Serial 2 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Serial 1 Intensity", "number"
attribute "Serial 2 Intensity", "number"
// Attributes for Virtual Outputs
attribute "Virtual A1 Name", "string"
attribute "Virtual A2 Name", "string"
attribute "Virtual A3 Name", "string"
attribute "Virtual A4 Name", "string"
attribute "Virtual A5 Name", "string"
attribute "Virtual A6 Name", "string"
attribute "Virtual A7 Name", "string"
attribute "Virtual A8 Name", "string"
attribute "Virtual A1 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A2 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A3 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A4 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A5 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A6 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A7 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
attribute "Virtual A8 Switch", "ENUM", [ "On", "Off", "On - Auto", "Off - Auto" ]
// Attributes for a Trident
attribute "alk", "number" // alk value received
attribute "ca", "number" // ca value received
attribute "mg", "number" // mg value received
attribute "Trident Selector 1", "string"
attribute "Trident Selector 2", "string"
// Tile Template attribute
attribute "Tile", "string"; // Ex: "[font size='2'][b]Temperature:[/b] ${ temperature }°${ location.getTemperatureScale() }[/br][/font]"
}
preferences{
section{
if( ShowAllPreferences ){
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 )
}
}
}
}
// updated
def updated(){
if( LogType == null ){
LogType = "Info"
}
unschedule()
// Get times to use for scheduling
def Hour = ( new Date().format( "h" ) as int )
def Minute = ( new Date().format( "m" ) as int )
def Second = ( new Date().format( "s" ) as int )
// Set the driver name and version before update checking is scheduled
if( state."Driver Name" != null ){
state.remove( "Driver Name" )
state.remove( "Driver Version" )
device.deleteCurrentState( "Driver Name" )
device.deleteCurrentState( "Driver Version" )
}
ProcessState( "DriverName", "${ DriverName() }" )
ProcessState( "DriverVersion", "${ DriverVersion() }" )
// Schedule the daily driver version check
schedule( "${ Second } ${ Minute } ${ Hour } ? * *", "CheckForUpdate" )
Logging( "Updated", 2 )
}
// 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()
}
// 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 ){
if( state."${ Variable }" != Value ){
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 and update if it has changed
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
}
}
}