Imports System.ComponentModel
Imports System.IO.Pipes
Imports System.Runtime.InteropServices
Imports System.Runtime.Serialization
Imports HomeSeer.PluginSdk.Devices.Identification
Public Module ten
    ''
    ''  The Global Methods in this Module (ten) are the HS Script Extensions available on the tenWare website
    ''               https://tenholder.net/tenWare2/ScriptingExtensions/Overview.aspx
    ''
    ''     When you copy the tenGlobalMethods.dll to the HS4 root folder, you will have access to these extensions as well as the ones you define in your own Module
    ''
    ''     DO NOT MODIFY THESE FUNCTIONS, THEY WILL BE OVERWRITTEN WHEN YOU UPDATE tenScripting
    ''
    Friend Const DebugON As Boolean = False
    Public Function DeviceOn(hs As HomeSeerAPI.IHSApplication, DevRef As Integer) As String
        '' DeviceOn [hs3]    Turns the Device with a ref of DevRef ON
        ''     
        ''    Returns "" if OK,    otherwise returns an error message
        ''
        ''    DevRef must have ControlUse_On defined
        ''
        ''  This 'hs' version of this function is deprecated, use the 'hs4' version
        ''
        Dim cr As HomeSeerAPI.CAPI.CAPIControlResponse
        cr = hs.CAPIControlHandler(hs.CAPIGetSingleControlByUse(DevRef, HomeSeerAPI.ePairControlUse._On))
        If cr = HomeSeerAPI.CAPI.CAPIControlResponse.All_Success Then
            Return ""
        Else
            '' ON failed
            Return "ControlUse_ON failed for DevRef " & DevRef.ToString
        End If
    End Function
    Public Function DeviceOn(hs As HomeSeerAPI.IHSApplication, DevRefs As List(Of Integer)) As String
        '' DeviceOn (list of Refs) [hs3]    Turns the Devices with a refs in DevRefs ON
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    All refs in DevRefs must have ControlUse_On defined
        ''
        ''  This 'hs' version of DeviceOn is deprecated.  Use the 'hs4' version.
        ''
        Dim cr As HomeSeerAPI.CAPI.CAPIControlResponse
        For Each DevRef As Integer In DevRefs
            cr = hs.CAPIControlHandler(hs.CAPIGetSingleControlByUse(DevRef, HomeSeerAPI.ePairControlUse._On))
            If Not (cr = HomeSeerAPI.CAPI.CAPIControlResponse.All_Success) Then
                Return "ControlUse_ON failed for DevRef " & DevRef.ToString
            End If
        Next DevRef
        Return ""
    End Function

    Public Function DeviceOn(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOn [hs4]    Turns the Device with a ref of FeatureRef ON
        ''     
        ''    If CheckFirst is specified as TRUE then Features NOT OFF will not be turned ON
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.On is found, a search will be made for a Control with a Label containing ON
        ''
        ''    Returns "" if OK,    otherwise returns an error message
        ''
        ''    DevRef must have a Control defined for ON
        ''
        Dim theStatusControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim rc As Boolean
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "Invalid hs4 Object"
        End If
        Trace(hs4, "DeviceOn with single Ref entered")
        '' Do nothing if the Feature is NOT OFF and Checking is requested
        Trace(hs4, "CheckFirst: " & CheckFirst.ToString)
        If CheckFirst And Not IsOff(hs4, FeatureRef) Then Return ""
        '' Get the StatusControl for ON
        Trace(hs4, "Getting Control for ON")
        theStatusControl = GetControl(hs4, FeatureRef, True, UseControlLabel)   'get Control for ON
        If IsNothing(theStatusControl) Then
            '' Could not find a Control for ON
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Could Not Find an ON Control for DevRef " & FeatureRef.ToString, "tenGlobalMethods.DeviceOn")
            Return "Could Not Find an ON Control for FeatureRef " & FeatureRef.ToString
        End If
        Trace(hs4, "Found the ON control, ready to issue SendControlForFeatureByValue for Value: " & theStatusControl.TargetValue.ToString)
        rc = hs4.SendControlForFeatureByValue(FeatureRef, theStatusControl.TargetValue)
        If Not rc Then
            '' SendControl Failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "SendControlForFeatureByValue failed for FeatureRef " & FeatureRef.ToString, "tenGlobalMethods.DeviceOn")
            Return "SendControlForFeatureByValue failed for FeatureRef " & FeatureRef.ToString
        End If
        Trace(hs4, "Looks like all was processed successfully")
        Return ""
    End Function
    Public Function DeviceOn(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRefs As List(Of Integer), Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOn (list of Refs) [hs4]    Turns the Devices with a refs in FeatureRefs ON
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features NOT OFF will not be turned ON
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.On is found, a search will be made for a Control with a Label containing ON
        ''
        ''    All refs in FeatureRefs must have ControlUse_On defined
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        For Each DevRef As Integer In FeatureRefs
            rc = ten.DeviceOn(hs4, DevRef, CheckFirst, UseControlLabel)
            If rc.Length > 0 Then
                '' Failed, record a failure happend
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one FeatureRef in the list of FeatureRefs failed", "tenGlobalMethods.DeviceOn")
            Return "At least one FeatureRef in the list of FeatureRefs failed"
        End If
        Return ""
    End Function
    Public Function DeviceOn(hs4 As HomeSeer.PluginSdk.IHsController, Category As String, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOn (Category) [hs4]    Turns the Devices with a refs in Category to  ON
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features NOT OFF will not be turned ON
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.On is found, a search will be made for a Control with a Label containing ON
        ''
        ''    All refs must have ControlUse_On defined
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        Dim DevRefs As List(Of Integer)
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        DevRefs = ten.GetRefsByCategoryName(hs4, Category)   'get the refs in the category
        For Each DevRef As Integer In DevRefs
            rc = ten.DeviceOn(hs4, DevRef, CheckFirst, UseControlLabel)
            If rc.Length > 0 Then
                '' Failed, record a failure happened
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one Ref in the Category " & Category & " failed", "tenGlobalMethods.DeviceOn")
            Return "At least one Ref in the Category " & Category & " failed"
        End If
        Return ""
    End Function
    Public Function DeviceOn(hs4 As HomeSeer.PluginSdk.IHsController, Categories() As String, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        ''
        '' Turn ON all of the Devices/Features in the Array of Categories
        ''
        ''    If CheckFirst is specified as TRUE then Features NOT OFF will not be turned ON
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.On is found, a search will be made for a Control with a Label containing ON
        ''
        ''  Return "" if successful, otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features NOT OFF will not be turned ON
        ''
        Dim rc As String
        Dim AllDevRefs As New List(Of Integer)
        Dim DevRefs As List(Of Integer)
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get all Refs from all Categories
        For Each category As String In Categories
            DevRefs = GetRefsByCategoryName(hs4, category)
            For Each ref As Integer In DevRefs
                If AllDevRefs.Contains(ref) Then
                    '' do nothing
                Else
                    AllDevRefs.Add(ref)
                End If
            Next
        Next category
        '' Turn them all ON
        If AllDevRefs.Count > 0 Then
            rc = DeviceOn(hs4, AllDevRefs, CheckFirst, UseControlLabel)
            If rc <> "" Then
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "DeviceOn for List of Refs Failed with: " & rc, "tenGlobalMethods.DeviceOn")
                Return "DeviceOn for List of Refs Failed with: " & rc
            End If
        End If
        Return ""
    End Function

    Public Function DeviceOff(hs As HomeSeerAPI.IHSApplication, DevRef As Integer) As String
        '' DeviceOff    Turns the Device with a ref of DevRef OFF
        ''
        ''    Returns "" if OK,    otherwise returns an error message
        ''
        ''    DevRef must have ControlUse_Off defined
        ''
        '' This 'hs' version of DeviceOff is deprecated.  Use the hs4 version.
        ''
        Dim cr As HomeSeerAPI.CAPI.CAPIControlResponse
        cr = hs.CAPIControlHandler(hs.CAPIGetSingleControlByUse(DevRef, HomeSeerAPI.ePairControlUse._Off))
        If cr = HomeSeerAPI.CAPI.CAPIControlResponse.All_Success Then
            Return ""
        Else
            '' OFF failed
            Return "ControlUse_OFF failed for DevRef " & DevRef.ToString
        End If
    End Function
    Public Function DeviceOff(hs As HomeSeerAPI.IHSApplication, DevRefs As List(Of Integer)) As String
        '' DeviceOff (list of Refs)    Turns the Devices with a refs in DevRefs OFF
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    All refs in DevRefs must have ControlUse_Off defined
        ''
        '' This 'hs' version of DeviceOff is deprecated.  Use the 'hs4' version.
        ''
        Dim cr As HomeSeerAPI.CAPI.CAPIControlResponse
        For Each DevRef As Integer In DevRefs
            cr = hs.CAPIControlHandler(hs.CAPIGetSingleControlByUse(DevRef, HomeSeerAPI.ePairControlUse._Off))
            If Not (cr = HomeSeerAPI.CAPI.CAPIControlResponse.All_Success) Then
                Return "ControlUse_OFF failed for DevRef " & DevRef.ToString
            End If
        Next DevRef
        Return ""
    End Function

    Public Function DeviceOff(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOff [hs4]    Turns the Device with a ref of FeatureRef OFF
        ''     
        ''    Returns "" if OK,    otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features that are OFF will NOT be turned OFF
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.Off is found, a search will be made for a Control with a Label containing OFF
        ''
        ''
        Dim theStatusControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim rc As Boolean
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Do nothing if the Feature is OFF and Checking is requested
        If CheckFirst And IsOff(hs4, FeatureRef) Then Return ""
        '' Get the StatusControl of OFF
        theStatusControl = GetControl(hs4, FeatureRef, False, UseControlLabel)   'get Control for OFF
        If IsNothing(theStatusControl) Then
            '' Could not find a Control for OFF
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Could Not Find an OFF Control for DevRef " & FeatureRef.ToString, "tenGlobalMethods.DeviceOff")
            Return "Could Not Find an OFF Control for FeatureRef " & FeatureRef.ToString
        End If
        rc = hs4.SendControlForFeatureByValue(FeatureRef, theStatusControl.TargetValue)
        If Not rc Then
            '' SendControl Failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "SendControlForFeatureByValue failed for DevRef " & FeatureRef.ToString, "tenGlobalMethods.DeviceOff")
            Return "SendControlForFeatureByValue failed for DevRef " & FeatureRef.ToString
        End If
        Return ""
    End Function
    Public Function DeviceOff(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRefs As List(Of Integer), Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOff (list of Refs) [hs4]    Turns the Devices with a refs in DevRefs OFF
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features that are OFF will NOT be turned OFF
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.Off is found, a search will be made for a Control with a Label containing OFF
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        For Each DevRef As Integer In FeatureRefs
            rc = ten.DeviceOff(hs4, DevRef, CheckFirst, UseControlLabel)
            If rc.Length > 0 Then
                '' Failed, record a failure happend
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one Ref in the list of FeatureRefs failed", "tenGlobalMethods.DeviceOff")
            Return "At least one Ref in the list of FeatureRefs failed"
        End If
        Return ""
    End Function
    Public Function DeviceOff(hs4 As HomeSeer.PluginSdk.IHsController, Category As String, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        '' DeviceOff (Category) [hs4]    Turns the Devices in Category to  OFF
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    If CheckFirst is specified as TRUE then Features that are OFF will NOT be turned OFF
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.Off is found, a search will be made for a Control with a Label containing OFF
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        Dim DevRefs As List(Of Integer)
        DevRefs = ten.GetRefsByCategoryName(hs4, Category)
        For Each DevRef As Integer In DevRefs
            rc = ten.DeviceOff(hs4, DevRef, CheckFirst, UseControlLabel)
            If rc.Length > 0 Then
                '' Failed, record a failure happend
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one Ref in the Category " & Category & " failed", "tenGlobalMethods.DeviceOff")
            Return "At least one Ref in the Category " & Category & " failed"
        End If
        Return ""
    End Function
    Public Function DeviceOff(hs4 As HomeSeer.PluginSdk.IHsController, Categories() As String, Optional CheckFirst As Boolean = False, Optional UseControlLabel As Boolean = True) As String
        ''
        '' Turn OFF all Devices in all of the supplied Categories
        ''
        ''    If CheckFirst is specified as TRUE then Features that are OFF will NOT be turned OFF
        ''
        ''    If UseControlLabel is TRUE then if no ControlUse.Off is found, a search will be made for a Control with a Label containing OFF
        ''
        Dim rc As String
        Dim AllDevRefs As New List(Of Integer)
        Dim DevRefs As List(Of Integer)
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get all Refs from all Categories
        For Each category As String In Categories
            DevRefs = GetRefsByCategoryName(hs4, category)
            For Each ref As Integer In DevRefs
                If AllDevRefs.Contains(ref) Then
                    '' do nothing
                Else
                    AllDevRefs.Add(ref)
                End If
            Next
        Next category
        '' Turn them all OFF
        If AllDevRefs.Count > 0 Then
            rc = DeviceOff(hs4, AllDevRefs, CheckFirst, UseControlLabel)
            If rc <> "" Then
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "DeviceOff for List of Refs Failed with: " & rc, "tenGlobalMethods.DeviceOff")
                Return "DeviceOff for List of Refs Failed with: " & rc
            End If
        End If
        Return ""
    End Function
    Public Function DeviceDim(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer, Percent As Double) As String
        '' DeviceDim   Dim FeatureRef to Level    
        ''     
        ''    Returns "" if OK,    otherwise returns an error message
        ''
        ''    Percent specifies the level that FeatureRef will be set to.   Must be between 0.0 and 100.0
        ''
        Dim theStatusControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim rc As Boolean
        Dim Low, High, DimValue As Double
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Validate Percent
        If Percent < 0 Or Percent > 100 Then
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Percent not between 0.0 and 100.0. Percent: " & Percent.ToString, "tenGlobalMethods.DeviceDim")
            Return "Percent not between 0.0 and 100.0. Percent: " & Percent.ToString
        End If
        '' Get the StatusControl for DIM
        theStatusControl = GetDimControl(hs4, FeatureRef)   'get Control for DIM
        If IsNothing(theStatusControl) Then
            '' Could not find a Control for DIM
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Could Not Find a DIM Control for DevRef " & FeatureRef.ToString, "tenGlobalMethods.DeviceDim")
            Return "Could Not Find a  DIM Control for FeatureRef " & FeatureRef.ToString
        End If
        '' Now get the Range Values
        Low = theStatusControl.TargetRange.Min
        High = theStatusControl.TargetRange.Max
        '' Compute target DIM value
        DimValue = Math.Round((High - Low) * (Percent / 100.0) + Low)
        Console.WriteLine("DimValue " & DimValue.ToString)
        '' Dim the Feature
        rc = hs4.SendControlForFeatureByValue(FeatureRef, DimValue)
        If Not rc Then
            '' SendControl Failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "SendControlForFeatureByValue failed for DevRef " & FeatureRef.ToString & ". TargetValue: " & DimValue.ToString, "tenGlobalMethods.DeviceDim")
            Return "SendControlForFeatureByValue failed for DevRef " & FeatureRef.ToString
        End If
        Return ""
    End Function
    Public Function DeviceDim(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRefs As List(Of Integer), Percent As Double) As String
        '' DevicDim (list of Refs)    Dims Devices with a refs in DevRefs to Percent
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    Percent specifies the level that FeatureRef will be set to.   Must be between 0.0 and 100.0
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        For Each DevRef As Integer In FeatureRefs
            rc = ten.DeviceDim(hs4, DevRef, Percent)
            If rc.Length > 0 Then
                '' Failed, record a failure happend
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one Ref in the list of FeatureRefs failed", "tenGlobalMethods.DeviceDim")
            Return "At least one Ref in the list of FeatureRefs failed"
        End If
        Return ""
    End Function
    Public Function DeviceDim(hs4 As HomeSeer.PluginSdk.IHsController, Category As String, Percent As Double) As String
        '' DeviceDim (Category)     Dims the Devices in Category to  Percent
        ''
        ''    Returns "" if All OK,    otherwise returns an error message
        ''
        ''    Percent specifies the level that FeatureRef will be set to.   Must be between 0.0 and 100.0
        ''
        Dim rc As String
        Dim AllOk As Boolean = True
        Dim DevRefs As List(Of Integer)
        DevRefs = ten.GetRefsByCategoryName(hs4, Category)
        For Each DevRef As Integer In DevRefs
            rc = ten.DeviceDim(hs4, DevRef, Percent)
            If rc.Length > 0 Then
                '' Failed, record a failure happend
                AllOk = False
            End If
        Next DevRef
        If Not AllOk Then
            '' At least one call failed
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "At least one Ref in the Category " & Category & " failed", "tenGlobalMethods.DeviceDim")
            Return "At least one Ref in the Category " & Category & " failed"
        End If
        Return ""
    End Function
    Public Function DeviceDim(hs4 As HomeSeer.PluginSdk.IHsController, Categories() As String, Percent As Double) As String
        ''
        '' Dims all Devices in all of the supplied Categories to Percent
        ''
        ''    Percent specifies the level that FeatureRef will be set to.   Must be between 0.0 and 100.0
        ''
        Dim rc As String
        Dim AllDevRefs As New List(Of Integer)
        Dim DevRefs As List(Of Integer)
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get all Refs from all Categories
        For Each category As String In Categories
            DevRefs = GetRefsByCategoryName(hs4, category)
            For Each ref As Integer In DevRefs
                If AllDevRefs.Contains(ref) Then
                    '' do nothing
                Else
                    AllDevRefs.Add(ref)
                End If
            Next
        Next category
        '' Turn them all OFF
        If AllDevRefs.Count > 0 Then
            rc = DeviceDim(hs4, AllDevRefs, Percent)
            If rc <> "" Then
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "DeviceDim for List of Refs Failed with: " & rc, "tenGlobalMethods.DeviceDim")
                Return "DeviceDim for List of Refs Failed with: " & rc
            End If
        End If
        Return ""
    End Function
    Public Function ToggleDevice(hs As HomeSeerAPI.IHSApplication, DevRef As Integer, Value1 As Double, Value2 As Double) As String
        ''
        ''  Will toggle DevRef between Value1 and Value2
        '' 
        ''    If DevRef is neither, set it to Value1
        ''
        '' Returns(String):   "" if OK,    otherwise, error message
        ''
        ''  NOTE: This 'hs' version of ToggleDevice is provided for backward compatibility.  Preferred use is the 'hs4' version
        ''
        Dim ccs() As HomeSeerAPI.CAPIControl
        Dim cr As HomeSeerAPI.CAPIControlResponse
        Dim CurrentState As Double
        Dim Capi1, Capi2 As Integer
        If hs Is Nothing Then
            '' Invalid hs parameter
            Return "hs parameter is invalid"
        End If
        CurrentState = hs.CAPIGetStatus(DevRef).Value   'current state of device
        ccs = hs.CAPIGetControl(DevRef)   'get all controls for this device
        '' Find CAPIcontrols for each value
        Capi1 = -1   'Value1 not found in the controls
        Capi2 = -1   'Value2 not found in the controls
        For Each c As HomeSeerAPI.CAPIControl In ccs
            If c.ControlValue = Value1 Then Capi1 = c.CCIndex   'found control for Value1
            If c.ControlValue = Value2 Then Capi2 = c.CCIndex   'found control for Value2
        Next
        '' Validate controls found
        If Capi1 < 0 Then
            '' No control for Value1
            Return ("Invalid Value1 parameter = " & Value1.ToString)
        End If
        If Capi2 < 0 Then
            '' No control for Value2
            Return "Invalid Value2 parameter = " & Value2.ToString
        End If
        '' Use the controls to toggle state
        Select Case CurrentState
            Case Value1
                '' Is Value1, make it Value2
                cr = hs.CAPIControlHandler(ccs(Capi2))
            Case Value2
                '' Is Value2, make it Value1
                cr = hs.CAPIControlHandler(ccs(Capi1))
            Case Else
                ''  Neither Value1 nor Value2, make it Value1
                cr = hs.CAPIControlHandler(ccs(Capi1))
        End Select
        Return ""
    End Function
    Public Function ToggleDevice(hs4 As HomeSeer.PluginSdk.IHsController, DevRef As Integer, Value1 As Double, Value2 As Double) As String
        ''
        ''  Will toggle DevRef between Value1 and Value2
        '' 
        ''    If DevRef is neither, set it to Value1
        ''
        '' Returns(String):   "" if OK,    otherwise, error message
        ''
        Dim ccs As List(Of HomeSeer.PluginSdk.Devices.Controls.StatusControl)
        Dim CurrentState As Double
        Dim cc1 As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim cc2 As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim rc As Boolean
        If hs4 Is Nothing Then
            '' Invalid hs parameter
            Return "hs4 parameter is invalid"
        End If
        CurrentState = CType(hs4.GetPropertyByRef(DevRef, HomeSeer.PluginSdk.Devices.EProperty.Value), Double)   'get current Value
        ccs = hs4.GetStatusControlsByRef(DevRef)   'get controls for this device
        '' Find CAPIcontrols for each value
        cc1 = Nothing  'Value1 not found in the controls
        cc2 = Nothing  'Value2 not found in the controls
        For Each c As HomeSeer.PluginSdk.Devices.Controls.StatusControl In ccs
            If c.TargetValue = Value1 Then cc1 = c  'found control for Value1
            If c.TargetValue = Value2 Then cc2 = c   'found control for Value2
        Next
        '' Validate controls found
        If IsNothing(cc1) Then
            '' No control for Value1
            Return ("Invalid Value1 parameter = " & Value1.ToString)
        End If
        If IsNothing(cc2) Then
            '' No control for Value2
            Return "Invalid Value2 parameter = " & Value2.ToString
        End If
        '' Use the controls to toggle state
        Select Case CurrentState
            Case Value1
                '' Is Value1, make it Value2
                rc = hs4.SendControlForFeatureByValue(DevRef, Value2)   'execute the control
                If Not rc Then Return "SendControlForFeatureByValue failed for Value " & Value2.ToString
            Case Value2
                '' Is Value2, make it Value1
                rc = hs4.SendControlForFeatureByValue(DevRef, Value1)   'execute the control
                If Not rc Then Return "SendControlForFeatureByValue failed for Value " & Value1.ToString
            Case Else
                ''  Neither Value1 nor Value2, make it Value1
                rc = hs4.SendControlForFeatureByValue(DevRef, Value1)   'execute the control
                If Not rc Then Return "SendControlForFeatureByValue failed for Value " & Value1.ToString
        End Select
        Return ""
    End Function
    Public Function ToggleDeviceOnOff(hs As HomeSeerAPI.IHSApplication, DevRef As Integer) As String
        ''
        '' ToggleDeviceOnOff will toggle a device between On and Off states
        ''
        ''     If Device is anything but OFF, it will turn it OFF
        ''     If Device is OFF, it will turn it ON
        ''
        ''     The DevRef device must have ControlUse values for ON and OFF defined
        ''
        '' Returns(String):   "" if OK,    otherwise, error message
        ''
        '' NOTE: This 'hs' version of ToggleDeviceOnOff is provided for backward compatibility.  Preferred use is the 'hs4' version.
        ''
        Dim s1 As String
        Dim ret As String
        Try
            s1 = hs.CAPIGetStatus(DevRef).Status   'get current status
            If s1.ToUpper.Contains("OFF") Then
                '' It is OFF, turn it ON
                ret = ten.DeviceOn(hs, DevRef)
                If ret.Length > 0 Then
                    '' Failed
                    Return "ten.DeviceOn failed for DevRef " & DevRef.ToString
                Else
                    '' Success
                    Return ""
                End If
            Else
                '' Not OFF, turn it OFF
                ret = ten.DeviceOff(hs, DevRef)
                If ret.Length > 0 Then
                    '' Failed
                    Return "ten.DeviceOff failed for DevRef " & DevRef.ToString
                Else
                    '' Success
                    Return ""
                End If
            End If
        Catch ex As Exception
            Return "Exception in ten.ToggleDeviceOnOff: " & ex.Message
        End Try
    End Function
    Public Function ToggleDeviceOnOff(hs4 As HomeSeer.PluginSdk.IHsController, DevRef As Integer) As String
        ''
        '' ToggleDeviceOnOff will toggle a device between On and Off states
        ''
        ''     If Device is anything but OFF, it will turn it OFF
        ''     If Device is OFF, it will turn it ON
        ''
        ''     The DevRef device must have ControlUse values for ON and OFF defined
        ''
        '' Returns(String):   "" if OK,    otherwise, error message
        ''
        Dim s1 As String
        Dim ret As String
        Try
            s1 = hs4.GetPropertyByRef(DevRef, HomeSeer.PluginSdk.Devices.EProperty.DisplayedStatus).ToString
            If s1.ToUpper.Contains("OFF") Then
                '' It is OFF, turn it ON
                ret = ten.DeviceOn(hs4, DevRef)
                If ret.Length > 0 Then
                    '' Failed
                    Return "ten.DeviceOn failed for DevRef " & DevRef.ToString
                Else
                    '' Success
                    Return ""
                End If
            Else
                '' Not OFF, turn it OFF
                ret = ten.DeviceOff(hs4, DevRef)
                If ret.Length > 0 Then
                    '' Failed
                    Return "ten.DeviceOff failed for DevRef " & DevRef.ToString
                Else
                    '' Success
                    Return ""
                End If
            End If
        Catch ex As Exception
            Return "Exception in ten.ToggleDeviceOnOff: " & ex.Message
        End Try
    End Function
    Public Function GetFeatureRefByFullname(hs As HomeSeerAPI.IHSApplication, DeviceName As String, FeatureName As String, Optional ByVal FeatureNameOnly As Boolean = False) As Integer
        ''
        ''  Returns the Feature Reference Number for the Feature from the DeviceName and FeatureName
        ''       If FeatureName is empty, then DevRef of Root Device is returned
        ''       Returns 0 for errors, display in the HS log
        ''
        ''       If Optional FeatureNameOnly is TRUE (default is FALSE) then only the Feature NAME is specified (without LOCATION and LOCATION2)
        ''
        '' NOTE: This function requires 'hs', 'hs4' is invalid
        ''
        Dim DeviceRef As Integer
        Dim DeviceObj As Scheduler.Classes.DeviceClass
        Dim AssociatedDevices As Integer()
        Dim Relationship As Integer
        Dim s1 As String, i, j As Integer
        If hs Is Nothing Then Return 0   'invalid HS object
        '' Get the Device Ref
        DeviceRef = hs.GetDeviceRefByName(DeviceName)   'get the Device ref
        If DeviceRef < 1 Then
            hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> Could not find Device with DeviceName=" & DeviceName, "#FF0000")
            Return 0
        End If
        If FeatureName.Length < 1 Then Return DeviceRef
        DeviceObj = CType(hs.GetDeviceByRef(DeviceRef), Scheduler.Classes.DeviceClass)   'get the device object
        If DeviceObj Is Nothing Then
            hs.WriteLogEx("tenGlobalMethods", "Could not retrieve Device Ojbect for DevRef=" & DeviceRef.ToString & " DeviceName=" & DeviceName, "#FF0000")
            Return 0
        End If
        Relationship = DeviceObj.Relationship(hs)
        Select Case Relationship
            Case 2
                '' Parent
                AssociatedDevices = DeviceObj.AssociatedDevices(hs)   'get any feature devrefs
                If AssociatedDevices.Count = 0 Then
                    '' Parent, but no Features
                    If FeatureName.Length > 0 Then
                        '' No Features, but FeatureName specified
                        hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> A FeatureName was specified, but DeviceName has no Features, DeviceName=" & DeviceName, "#FF0000")
                        hs.WriteLogEx("tenGlobalMethods", "     FeatureName=" & FeatureName, "#FF0000")
                        Return 0
                    Else
                        Return DeviceRef
                    End If
                Else
                    '' Parent, has Features
                    Try
                        For j = 0 To AssociatedDevices.Count - 1
                            i = AssociatedDevices(j)    'DevRef of a child
                            DeviceObj = CType(hs.GetDeviceByRef(i), Scheduler.Classes.DeviceClass)   'get the device object for a Feature
                            If FeatureNameOnly Then
                                '' Only check parameter agains Name
                                s1 = DeviceObj.Name(hs).Trim
                            Else
                                '' Check parameter agains Location2, Location, and Name
                                s1 = (DeviceObj.Location2(hs).Trim & " " & DeviceObj.Location(hs).Trim & " " & DeviceObj.Name(hs).Trim).Trim
                            End If
                            If s1.ToUpper = FeatureName.ToUpper.Trim Then
                                '' Found Feature requested
                                If DeviceObj.Relationship(hs) = 4 Then
                                    '' It is really a Feature
                                    Return DeviceObj.Ref(hs)   'return the DevRef of the Feature
                                Else
                                    '' Not marked as Feature
                                    hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> Feature " & FeatureName & " marked as " & DeviceObj.Relationship(hs).ToString, "#FF0000")
                                    Return 0
                                End If
                            End If
                        Next j
                    Catch ex As Exception
                        hs.WriteLogEx("tenGlobalMethods", "Exception iterating AssociatedDevices", "#FF0000")
                        hs.WriteLogEx("tenGlobalMethods", "    AssociatedDevices.Count = " & AssociatedDevices.Count.ToString & "  j=" & j.ToString & "  i=" & i.ToString, "#FF0000")
                    End Try
                    '' Feature was not found
                    hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> Feature " & FeatureName & " was not found for Device " & DeviceName, "#FF0000")
                    Return 0
                End If
            Case 3
                '' Standalone
                If FeatureName.Length > 0 Then
                    '' FeatureName specified, but DeviceName is a root
                    hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> A FeatureName was specified, but DeviceName is a Root Device, DeviceName=" & DeviceName, "#FF0000")
                    Return 0
                Else
                    '' Return DevRef of DeviceName
                    Return DeviceRef
                End If
            Case 4
                '' Child
                hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> DeviceName appears to be a Feature: " & DeviceName, "#FF0000")
                Return 0
            Case Else
                ''  Error,  not set or indeterminate
                hs.WriteLogEx("tenGlobalMethods", "GetFeatureRefByFullname>> DeviceName is not clearly a parent device: " & DeviceName, "#FF0000")
                hs.WriteLogEx("tenGlobalMethods", "    Relationship Code: " & Relationship.ToString, "#FF0000")
                Return 0
        End Select
    End Function
    Public Function GetCategoriesByRef(hs4 As HomeSeer.PluginSdk.IHsController, Devref As Integer) As List(Of String)
        ''
        '' GetCategoriesByRef    Returns a List(Of String) containing the Category Names for the DevRef
        ''
        Dim AllCats As Dictionary(Of String, String)   'ID,Name
        Dim Cats As New List(Of String)
        Dim EmptyResult As New List(Of String)
        Dim Refs As List(Of Integer)
        If hs4 Is Nothing Then
            '' Invalid hs4
            Return Nothing
        End If
        Try
            AllCats = hs4.GetAllCategories()   'get all HS categories
        Catch ex As Exception
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetAllCategories exception: " & ex.Message, "GetCategoriesByRef")
            Return EmptyResult
        End Try
        If AllCats.Count < 1 Then
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "No HS4 Categories Found", "GetCategoriesByRef")
            Return EmptyResult
        End If
        For Each kvp As KeyValuePair(Of String, String) In AllCats   'ID,Name
            Try
                Refs = hs4.GetRefsByCategoryId(kvp.Key)   'get all refs in this category
            Catch ex As Exception
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetRefsByCategory (for Category " & kvp.Value & ") exception" & ex.Message, "GetCategoriesByRef")
                Return EmptyResult
            End Try
            For Each ref As Integer In Refs
                If hs4.DoesRefExist(ref) Then   'check for invalid entry in Category
                    If ref = Devref Then
                        '' This Ref in this Category is the requested DevRef
                        If Cats.Contains(kvp.Value) Then
                            '' Already there, do nothing
                        Else
                            '' Not there, add it
                            Cats.Add(kvp.Value)
                        End If
                        Exit For
                    End If
                End If
            Next
        Next
        Return Cats
    End Function
    Public Function GetRefsByCategoryName(hs4 As HomeSeer.PluginSdk.IHsController, CatName As String) As List(Of Integer)
        ''
        '' GetRefsByCategoryName    Returns a List(Of Integer) containing all Device Refs for the specified Category Name
        ''
        Dim Refs, Refs2 As List(Of Integer)   'Device Refs
        Dim AllCats As Dictionary(Of String, String)   'ID,Name
        Dim EmptyResult As New List(Of Integer)
        Dim CategoryId As String
        '' Get the Category ID for the Category Name
        Try
            AllCats = hs4.GetAllCategories()   'get all HS categories
        Catch ex As Exception
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetAllCategories exception: " & ex.Message, "GetRefsByCategoryName")
            Return EmptyResult
        End Try
        For Each kvp As KeyValuePair(Of String, String) In AllCats    'ID,Name
            If kvp.Value = CatName Then
                '' Found the requested Category
                CategoryId = kvp.Key   'ID for this Category
                '' Get all of the Refs for this Category
                Try
                    Refs = hs4.GetRefsByCategoryId(CategoryId)   'get all refs in this category
                Catch ex As Exception
                    hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetRefsByCategory (for Category " & kvp.Value & ") exception" & ex.Message, "GetRefsByCategoryName")
                    Return EmptyResult
                End Try
                '' Obtained all Refs in the Category, now check that they are valid
                Refs2 = New List(Of Integer)
                For Each i As Integer In Refs
                    If hs4.DoesRefExist(i) Then Refs2.Add(i)
                Next i
                Return Refs2
            End If
        Next kvp
        hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Could not find Category " & CatName, "GetRefsByCategoryName")
        Return EmptyResult
    End Function
    Public Function GetRefsInBothLists(List1 As List(Of Integer), List2 As List(Of Integer)) As List(Of Integer)
        ''
        ''  Return a list of Refs that are in both List1 and List2
        ''
        Dim i1, i2 As IEnumerable(Of Integer)
        i1 = List1
        i2 = List2
        Return i1.Intersect(i2).ToList
    End Function
    Public Function GetRefsInEitherList(ByVal List1 As List(Of Integer), ByVal List2 As List(Of Integer)) As List(Of Integer)
        ''
        '' Returns a list of Unique Refs that appear in either List1 OR List2
        ''
        Dim retlist As List(Of Integer)
        retlist = List1
        For Each i As Integer In List2
            If Not retlist.Contains(i) Then
                retlist.Add(i)
            End If
        Next i
        Return retlist
    End Function
    Public Function GetFullnameByRef(hs4 As HomeSeer.PluginSdk.IHsController, DevRef As Integer) As String()
        '' GetFullnameByRef      Returns a 2 element string array containing 
        ''                           String(0) = Device Name (Root)
        ''                           String(1) = Feature Name
        ''
        ''      If DevRef is a root device, then String(1) = ""
        ''
        Dim ss(1) As String
        Dim i1 As Integer
        Dim aName, aLocation, aLocation2 As String
        Dim Relationship As Integer
        Dim AssociatedDevices As HashSet(Of Integer)
        Dim Dev As HomeSeer.PluginSdk.Devices.AbstractHsDevice
        '' Get Fullname of DevRef
        Dev = hs4.GetDeviceByRef(DevRef)
        If Dev Is Nothing Then
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetFullnameByRef: DevRef " & DevRef.ToString & " not found", "tenGlobalMethods")
            Return {"", ""}
        End If
        aName = Dev.Name
        aLocation = Dev.Location
        aLocation2 = Dev.Location2
        If hs4.IsRefDevice(Dev.Ref) Then
            '' Device
            ss(0) = (aLocation2 & " " & aLocation & " " & aName).Trim
            ss(1) = ""
            Return ss
        Else
            '' Feature
            ss(1) = (aLocation2 & " " & aLocation & " " & aName).Trim   'Fullname of Feature device
            Relationship = Dev.Relationship
            If Relationship <> HomeSeer.PluginSdk.Devices.Identification.ERelationship.Feature Then
                '' Not a Feature
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "GetFullnameByRef: DevRef " & Dev.Ref.ToString & " is marked as Feature but is not a Child", "tenGlobalMethods")
                ss(0) = ""
                Return ss
            Else
                '' Got a good Feature, get its Parent device
                AssociatedDevices = Dev.AssociatedDevices
                If AssociatedDevices.Count <> 1 Then
                    hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "DevRef " & Dev.Ref.ToString & " is marked as Feature but it has " & AssociatedDevices.Count.ToString & " Associated Devices", "GetFullNameByRef")
                    ss(0) = ""
                    Return ss
                Else
                    '' Single Associated Device, get Fullname of root device
                    i1 = AssociatedDevices(0)   'parent DevRef
                    aName = hs4.GetPropertyByRef(i1, HomeSeer.PluginSdk.Devices.EProperty.Name).ToString
                    aLocation = hs4.GetPropertyByRef(i1, HomeSeer.PluginSdk.Devices.EProperty.Location).ToString
                    aLocation2 = hs4.GetPropertyByRef(i1, HomeSeer.PluginSdk.Devices.EProperty.Location2).ToString
                    ss(0) = (aLocation2 & " " & aLocation & " " & aName).Trim   'Fullname of root device
                    Return ss
                End If
            End If
        End If
    End Function
    Public Function SaveDeviceStates(hs4 As HomeSeer.PluginSdk.IHsController, SaveName As String, DevRefs As List(Of Integer)) As String
        ''
        '' Retrieve the Values of each Device in the supplied list of DevRefs and save them to INI file as SaveName
        ''
        ''  Returns "" if successful, otherwise returns an error message
        ''
        Dim d1 As Double
        Dim i1 As Integer
        Dim SavedAll As Boolean
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Save each Device State
        i1 = 1   'next device number
        SavedAll = True
        For Each ref As Integer In DevRefs
            d1 = CType(hs4.GetPropertyByRef(ref, HomeSeer.PluginSdk.Devices.EProperty.Value), Double)   'get current value
            If ControlExists(hs4, ref, d1) Then
                '' Device has a Control for its current Value
                hs4.SaveINISetting(SaveName, "Ref" & Format(i1, "0000"), Format(ref, "0000"), "tenDeviceStates.ini")   'save Device Ref
                hs4.SaveINISetting(SaveName, "Value" & Format(i1, "0000"), d1.ToString, "tenDeviceStates.ini")   'save Device Value
                i1 = i1 + 1
            Else
                '' No Control exists for this Value
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Warning, "Cannot save Device, No Control for " & ref.ToString & " with Value " & d1.ToString, "ten.SaveDeviceStates")
                SavedAll = False
            End If
        Next ref
        '' Save the State Count 
        hs4.SaveINISetting(SaveName, "NumberOfSavedStates", Format(i1 - 1, "0000"), "tenDeviceStates.ini")
        If SavedAll Then
            Return ""
        Else
            Return "Did not save all Devices, see HS4 log"
        End If
    End Function
    Public Function SaveDeviceStates(hs4 As HomeSeer.PluginSdk.IHsController, SaveName As String, Category As String) As String
        ''
        '' Retrieve the Values of each Device in the supplied Category and save them as SaveName
        ''
        Dim d1 As Double
        Dim i1 As Integer
        Dim SavedAll As Boolean
        Dim DevRefs As List(Of Integer)   'Refs in the Category
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get the Refs from the supplied Category
        DevRefs = ten.GetRefsByCategoryName(hs4, Category)
        If DevRefs.Count < 1 Then
            '' Found no Refs in Category
            Return "The supplied Category contained no Devices: " & Category
        End If
        '' Save each Device State
        i1 = 1   'next device number
        SavedAll = True
        For Each ref As Integer In DevRefs
            d1 = CType(hs4.GetPropertyByRef(ref, HomeSeer.PluginSdk.Devices.EProperty.Value), Double)   'get current value
            If ControlExists(hs4, ref, d1) Then
                '' Device has a Control for its current Value
                hs4.SaveINISetting(SaveName, "Ref" & Format(i1, "0000"), Format(ref, "0000"), "tenDeviceStates.ini")   'save Device Ref
                hs4.SaveINISetting(SaveName, "Value" & Format(i1, "0000"), d1.ToString, "tenDeviceStates.ini")   'save Device Value
                i1 = i1 + 1
            Else
                '' No Control exists for this Value
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Warning, "Cannot save Device, No Control for " & ref.ToString & " with Value " & d1.ToString, "ten.SaveDeviceStates")
                SavedAll = False
            End If
        Next ref
        '' Save the State Count 
        hs4.SaveINISetting(SaveName, "NumberOfSavedStates", Format(i1 - 1, "0000"), "tenDeviceStates.ini")
        If SavedAll Then
            Return ""
        Else
            Return "Did not save all Devices, see HS4 log"
        End If
    End Function
    Public Function SaveDeviceStates(hs4 As HomeSeer.PluginSdk.IHsController, SaveName As String, Categories() As String) As String
        ''
        '' Retrieve the Values of each Device in the supplied Categories and save them as SaveName
        ''
        Dim d1 As Double
        Dim i1 As Integer
        Dim SavedAll As Boolean
        Dim DevRefs As New List(Of Integer)   'Refs in the Category
        Dim someRefs As List(Of Integer)
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get the Refs from the supplied Categories
        If IsNothing(Categories) Then
            Return "The supplied Categories is null"
        End If
        If Categories.Count < 1 Then
            Return "The List of Categories contained no entries"
        End If
        For Each s In Categories
            someRefs = ten.GetRefsByCategoryName(hs4, s)   'get refs in this category
            '' Add this Category refs to total list
            For Each i As Integer In someRefs
                If Not DevRefs.Contains(i) Then
                    DevRefs.Add(i)   'add only unique refs
                End If
            Next i
        Next s
        '' Save each Device State
        i1 = 1
        SavedAll = True
        For Each ref As Integer In DevRefs
            d1 = CType(hs4.GetPropertyByRef(ref, HomeSeer.PluginSdk.Devices.EProperty.Value), Double)   'get current value
            If ControlExists(hs4, ref, d1) Then
                '' Device has a Control for its current Value
                hs4.SaveINISetting(SaveName, "Ref" & Format(i1, "0000"), Format(ref, "0000"), "tenDeviceStates.ini")   'save Device Ref
                hs4.SaveINISetting(SaveName, "Value" & Format(i1, "0000"), d1.ToString, "tenDeviceStates.ini")   'save Device Value
                i1 = i1 + 1
            Else
                '' No Control exists for this Value
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Warning, "Cannot save Device, No Control for " & ref.ToString & " with Value " & d1.ToString, "ten.SaveDeviceStates")
                SavedAll = False
            End If
        Next ref
        '' Save the State Count 
        hs4.SaveINISetting(SaveName, "NumberOfSavedStates", Format(i1 - 1, "0000"), "tenDeviceStates.ini")
        If SavedAll Then
            Return ""
        Else
            Return "Did not save all Devices, see HS4 log"
        End If
    End Function
    Public Function RestoreDeviceStates(hs4 As HomeSeer.PluginSdk.IHsController, SaveName As String, Optional Clear As Boolean = False) As String
        ''
        '' Restore the States of all Devices Saved as SaveName
        ''
        ''      Clear = True will remove the entries from SaveName after they are retrieved
        ''
        '' Validate the hs4 object
        Dim NumberOfSavedStates As Integer
        Dim rc As Boolean
        Dim aRef As Integer, aValue As Double
        If IsNothing(hs4) Then
            Return "hs4 parameter is invalid"
        End If
        '' Get number of saved Device States
        NumberOfSavedStates = CInt(hs4.GetINISetting(SaveName, "NumberOfSavedStates", "0000", "tenDeviceStates.ini"))
        If NumberOfSavedStates < 1 Then
            Return ""  'No saved Devices, do nothing
        End If
        '' Retrieve States from INI and restore the Device States
        For i As Integer = 1 To NumberOfSavedStates
            '' Get an entry from the INI file
            aRef = CInt(hs4.GetINISetting(SaveName, "Ref" & Format(i, "0000"), "0", "tenDeviceStates.ini"))
            If aRef < 1 Then
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Ref=0 for " & SaveName, "ten.DeviceStatesFromIni")
                Return "Invalid saved entry for " & SaveName
            End If
            If hs4.DoesRefExist(aRef) Then
                '' The Ref exists
                aValue = CDbl(hs4.GetINISetting(SaveName, "Value" & Format(i, "0000"), "0", "tenDeviceStates.ini"))
                '' Set the Device Value from the Saved State
                Try
                    rc = hs4.SendControlForFeatureByValue(aRef, aValue)   'set target device to saved state
                Catch ex As Exception
                    hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "hs4.SendControlForFeatureByValue exception: " & ex.Message, "ten.RestoreDeviceStates")
                    Return "hs4.SendControlForFeatureByValue exception: " & ex.Message
                End Try
                If Not rc Then
                    '' Failed
                    hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "hs4.SendControlForFeatureByValue Failed. Ref:" & aRef.ToString & "  Value:" & aValue.ToString, "ten.RestoreDeviceStates")
                    Return "hs4.SendControlForFeatureByValue Failed. Ref:" & aRef.ToString & "  Value:" & aValue.ToString
                End If
            Else
                '' The saved Ref does not exist
                hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "Saved Ref no longer exists, cannot be restored. Ref:" & aRef.ToString, "ten.RestoreDeviceStates")
            End If
        Next i
        If Clear Then
            '' Clear the existing entries
            hs4.SaveINISetting(SaveName, "NumberOfSavedStates", "0000", "tenDeviceStates.ini")   'set count to 0
        End If
        Return ""
    End Function
    Public Function RemoveDeviceStates(hs4 As HomeSeer.PluginSdk.IHsController, SaveName As String) As String
        '' Clear the existing entries
        hs4.SaveINISetting(SaveName, "NumberOfSavedStates", "0000", "tenDeviceStates.ini")   'set count to 0
        Return ""
    End Function
    Public Function IsOff(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer) As Boolean
        ''
        '' IsOff returns TRUE if the specified Feature if OFF, otherwise returns FALSE (ie, if it is DIM, return FALSE)
        ''
        Dim theFeature As HomeSeer.PluginSdk.Devices.HsFeature
        Dim theStatusControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim FoundControl As Boolean
        Dim Status As String
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return False
        End If
        theFeature = hs4.GetFeatureByRef(FeatureRef)
        If IsNothing(theFeature) Then
            '' Must be invalid Ref
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, FeatureRef.ToString & " is an invalid FeatureRef", "tenGlobalMethods.IsOff")
            Return False
        End If
        '' Get the Control for OFF
        theStatusControl = Nothing
        If theFeature.HasControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Off) Then
            theStatusControl = theFeature.GetFirstControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Off)
            If IsNothing(theStatusControl) Then
                FoundControl = False
            Else
                FoundControl = True
            End If
        Else
            FoundControl = False
        End If
        If FoundControl Then
            '' Ref has an OFF control so use it
            If theFeature.Value = theStatusControl.TargetValue Then
                Return True   'feature value equals the target value for ControlUse.OFF
            End If
        End If
        '' Could not confirm it is OFF by the StatusControl, use the DisplayStatus
        Status = hs4.GetPropertyByRef(FeatureRef, HomeSeer.PluginSdk.Devices.EProperty.DisplayedStatus).ToString
        If Status.ToUpper.Contains("OFF") Then
            Return True
        Else
            Return False
        End If
    End Function
    Public Function AreAllOff(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRefs As List(Of Integer)) As Boolean
        ''
        '' AreAllOff will return FALSE is ANY of the Features are NOT OFF  (On, Dim, etc)
        ''
        ''  All Features must have ControlUse.Off defined
        ''
        If IsNothing(hs4) Then
            Return False
        End If
        For Each ref As Integer In FeatureRefs
            If Not ten.IsOff(hs4, ref) Then Return False   'all are NOT OFF
        Next
        Return True   'all are OFF
    End Function
    Public Function AreAllOff(hs4 As HomeSeer.PluginSdk.IHsController, Category As String) As Boolean
        ''
        '' AreAllOff will return FALSE if ANY of the Features in Category are NOT OFF  (On, Dim, etc)
        ''
        ''  All Features must have ControlUse.Off defined
        ''
        Dim AllOff As Boolean
        Dim FeatureRefs As List(Of Integer)
        If IsNothing(hs4) Then
            Return False
        End If
        FeatureRefs = ten.GetRefsByCategoryName(hs4, Category)
        If IsNothing(FeatureRefs) Then
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, "The CategoryAttribute " & Category & " could not be found", "tenGlobalMethods.AreAllOff")
            Return False
        End If
        AllOff = True
        For Each ref As Integer In FeatureRefs
            If Not ten.IsOff(hs4, ref) Then Return False   'all are NOT OFF
        Next
        Return True   'all are OFF
    End Function
    Public Function AreAllOff(hs4 As HomeSeer.PluginSdk.IHsController, Categories() As String) As Boolean
        ''
        ''  AreAllOff will return FALSE if ANY of the Features in ANY of the Categories are NOT OFF (On, Dim, etc)
        ''
        ''      All Features must have ControlUse.Off defined
        ''
        If IsNothing(hs4) Then
            Return False
        End If
        For Each category In Categories
            If Not ten.AreAllOff(hs4, category) Then Return False
        Next category
        Return True
    End Function




    Friend Sub Trace(hs4 As HomeSeer.PluginSdk.IHsController, s As String)
        ''
        '' Helper function to log trace messages if DebugON is set to true
        ''
        If DebugON Then
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Trace, s, "tenGlobalMethods Debug")
        End If
    End Sub
    Friend Function ControlExists(hs4 As HomeSeer.PluginSdk.IHsController, DevRef As Integer, Value As Double) As Boolean
        ''
        ''  Helper function to check if DevRef has a Control defined for Value
        ''
        Dim SCs As List(Of HomeSeer.PluginSdk.Devices.Controls.StatusControl)
        Dim Found As Boolean = False
        SCs = hs4.GetStatusControlsByRef(DevRef)   'get all controls for this Ref
        If SCs.Count < 1 Then Return False
        For Each sc As HomeSeer.PluginSdk.Devices.Controls.StatusControl In SCs
            '' loop through all defined controls
            If sc.IsRange Then
                '' This Control has a range of Values
                If Value >= sc.TargetRange.Min And Value <= sc.TargetRange.Max Then
                    Found = True
                    Exit For
                End If
            Else
                '' This Control is a single Value
                If Value = sc.TargetValue Then
                    Found = True
                    Exit For
                End If
            End If
        Next sc
        Return Found
    End Function
    Friend Function GetControl(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer, TurnOn As Boolean, Optional TryControlLabel As Boolean = False) As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        ''
        '' Helper function to find the correct Control for DeviceOn and DeviceOff
        ''
        Dim aControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim theFeature As HomeSeer.PluginSdk.Devices.HsFeature
        Dim SCs As List(Of HomeSeer.PluginSdk.Devices.Controls.StatusControl)
        Dim s1, s2, s3 As String
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return Nothing
        End If
        '' Get the Feature to be controlled
        Try
            theFeature = hs4.GetFeatureByRef(FeatureRef)
        Catch ex As Exception
            theFeature = Nothing
        End Try
        If IsNothing(theFeature) Then
            '' Must be invalid DevRef
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, FeatureRef.ToString & " is an invalid FeatureRef", "tenGlobalMethods.GetControl")
            Return Nothing
        End If
        '' Try to get ControlUse
        Try
            If TurnOn Then
                '' Get Control for ON
                If Not theFeature.HasControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.On) Then
                    aControl = Nothing
                Else
                    aControl = theFeature.GetFirstControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.On)
                End If
            Else
                '' Get Control for OFF
                If Not theFeature.HasControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Off) Then
                    aControl = Nothing
                Else
                    aControl = theFeature.GetFirstControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Off)
                End If
            End If
        Catch ex As Exception
            aControl = Nothing
        End Try
        If Not IsNothing(aControl) Then Return aControl   'found Control, Return it
        If Not TryControlLabel Then Return Nothing   'no ControlUse and search not requested, return error
        '' Try searching for a matching Control
        If TurnOn Then s1 = " ON " Else s1 = " OFF "   'what we are looking for
        SCs = hs4.GetStatusControlsByRef(FeatureRef)   'get all controls for this Ref
        If SCs.Count < 1 Then Return Nothing
        For Each sc As HomeSeer.PluginSdk.Devices.Controls.StatusControl In SCs
            '' loop through all defined controls
            If Not sc.IsRange Then
                '' Not a Range of Values
                s2 = sc.GetLabelForValue(sc.TargetValue)   'get a Control Label
                s3 = " " & s2.ToUpper & " "   'pad this label with spaces
                If s3.Contains(s1) Then
                    '' Found the correct Control
                    Return sc
                End If
            End If
        Next sc
        Return Nothing
    End Function
    Friend Function GetDimControl(hs4 As HomeSeer.PluginSdk.IHsController, FeatureRef As Integer) As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        ''
        '' Helper function to find the correct Control for DeviceDim
        ''
        Dim aControl As HomeSeer.PluginSdk.Devices.Controls.StatusControl
        Dim theFeature As HomeSeer.PluginSdk.Devices.HsFeature
        Dim SCs As List(Of HomeSeer.PluginSdk.Devices.Controls.StatusControl)
        Dim s1, s2, s3 As String
        '' Validate the hs4 object
        If IsNothing(hs4) Then
            Return Nothing
        End If
        '' Get the Feature to be controlled
        Try
            theFeature = hs4.GetFeatureByRef(FeatureRef)
        Catch ex As Exception
            theFeature = Nothing
        End Try
        If IsNothing(theFeature) Then
            '' Must be invalid DevRef
            hs4.WriteLog(HomeSeer.PluginSdk.Logging.ELogType.Error, FeatureRef.ToString & " is an invalid FeatureRef", "tenGlobalMethods.GetDimControl")
            Return Nothing
        End If
        '' Try to get ControlUse
        Try

            '' Get Control for DIM using ControlUse
            If Not theFeature.HasControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Dim) Then
                aControl = Nothing
            Else
                aControl = theFeature.GetFirstControlForUse(HomeSeer.PluginSdk.Devices.Controls.EControlUse.Dim)
            End If
        Catch ex As Exception
            aControl = Nothing
        End Try
        If Not IsNothing(aControl) Then
            '' Got ControlUse.Dim
            If aControl.IsRange Then
                '' ControlUse.Dim and Control has a Range of Values
                Return aControl
            End If
        End If
        '' Could not find ControlUse.Dim, so search for a Control with a Range of Values and a Label that contains DIM
        '' Try searching for a matching Control
        s1 = " DIM "
        SCs = hs4.GetStatusControlsByRef(FeatureRef)   'get all controls for this Ref
        If SCs.Count < 1 Then Return Nothing   'couldn't find any controls for this FeatureRef
        For Each sc As HomeSeer.PluginSdk.Devices.Controls.StatusControl In SCs
            '' loop through all defined controls
            If sc.IsRange Then
                '' Has a Range of Values
                s2 = sc.GetLabelForValue(sc.TargetValue)   'get a Control Label
                s3 = " " & s2.ToUpper & " "   'pad this label with spaces
                If s3.Contains(s1) Then
                    '' Found the correct Control
                    Return sc
                End If
            End If
        Next sc
        Return Nothing
    End Function
End Module