Announcement

Collapse
No announcement yet.

Migrating HS3 Trigger to HS4

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Migrating HS3 Trigger to HS4

    Below is a walkthrough of how I migrated by HS3 event trigger to a HS4 event trigger. The intent is to try to provide others some insight into what I have learned in this area that should be useful to others. I derived this information from the HST Netcam and SamplePlugin implementations. The most valuable aspect of this should be the storyline of how it fits together. Once there is a grasp on the bigger picture then it becomes much easier to know what needs to be done in each conversion. This is the aspect of the HS4 development where HST engineers have an advantage over the external developers.


    For each Trigger type that is to be shown in the HS Event trigger create a class to hold it. In the HSPI Initialize() procedure register it with such as for the class “MQTTReceiveTrigger”
    Code:
    TriggerTypes.AddTriggerType(GetType(MQTTReceiveTrigger))
    The class has a name where it provides whatever is to be shown in the HS Event trigger list and subtrigger names that may be a null list
    Code:
        Protected Overrides Function GetName() As String
            Return TriggerName
        End Function
        Protected Overrides Property SubTriggerTypeNames As List(Of String) = New List(Of String)
    The class will contain three New(..) constructors with signatures to handle various ways it can be used
    Code:
        Public Sub New(ByVal trigInfo As TrigActInfo, ByVal inListener As TriggerTypeCollection.ITriggerTypeListener, Optional ByVal debugLog As Boolean = False)
            MyBase.New(trigInfo, inListener, debugLog)
        End Sub
    
        Public Sub New(ByVal id As Integer, ByVal eventRef As Integer, ByVal selectedSubTriggerIndex As Integer, ByVal dataIn As Byte(), ByVal inListener As TriggerTypeCollection.ITriggerTypeListener, Optional ByVal debugLog As Boolean = False)
            MyBase.New(id, eventRef, selectedSubTriggerIndex, dataIn, inListener, debugLog)
        End Sub
    
        Public Sub New()
            MyBase.New
        End Sub
    It will have a method that is invoked when the user selects this trigger type for an event. At this time the ConfigPage property is set to a Jui View Page that can be build using PageFactory. This replaces the HS3 TriggerBuildUI.
    This the time that a unique ID for this page is established. This is made from the PageId implicitly given by HS and some text that will this class from other trigger classes supported by the plugin.
    Code:
        Protected Overrides Sub OnNewTrigger()
            ConfigPage = InitializeMQTTReceivePage().Page
        End Sub
    
        Private Function InitializeMQTTReceivePage() As PageFactory
            Dim cpf As PageFactory = PageFactory.CreateEventTriggerPage(MQTTReceiveTriggerId, TriggerName)
            cpf.WithInput("TriggerTopic", "MQTT Topic to match", "", HomeSeer.Jui.Types.EInputType.Text)
            cpf.WithInput("TriggerPayload", "Payload to match", "", HomeSeer.Jui.Types.EInputType.Text)
            Return cpf
        End Function
        Private ReadOnly Property MQTTReceiveTriggerId As String
            Get
                Return $"{PageId}-MqttReceiveTrigger"
            End Get
        End Property
    Since OnNewTrigger() was initiated by use action on the UI the Event page will be showing and now new rows will appear that shows what the user needs to complete. This information is what was setup in ConfigPage. HS4 will be calling IsFullyConfigured() to see if the user has had sufficient information completed. The user inputs are visible in the ConfigPage Views (i.e. controls setup when trigger class is initialized). For some controls such as a select list selection or a toggle click the user input will be available immediately. In others such as text input they will only be visible when the Save icon for the trigger is clicked. In IsFullyConfigured() return true if the user inputs are sufficient to define the trigger.
    Code:
        Public Overrides Function IsFullyConfigured() As Boolean
    
            For Each view As AbstractView In ConfigPage.Views
                Dim id As String = view.Id
                If id = "TriggerTopic" Then
                    Dim inputView As InputView
                    inputView = TryCast(view, InputView)
                    Dim value As String = InputView?.GetStringValue
                    If value <> "" Then
                        Return True
                    End If
                End If
            Next
    
            Return False
    
        End Function
    When a viable trigger is defined by user then HS4 will call the trigger class to format it for easy reading. This was TriggerFormatUI in HS3 and is now GetPrettyString() in HS4. Same code can be largely reused.
    Code:
    Public Overrides Function GetPrettyString() As String
    
            Dim sTopic As String = ""
            Dim sPayload As String = ""
    
            For Each view As AbstractView In ConfigPage.Views
                Dim id As String = view.Id 'e.g. TriggerTopic
                If id = "TriggerTopic" Then
                    Dim inputView As InputView
                    inputView = TryCast(view, InputView)
                    sTopic = inputView?.GetStringValue
                ElseIf id = "TriggerPayload" Then
                    Dim inputView As InputView
                    inputView = TryCast(view, InputView)
                    sPayload = inputView?.GetStringValue
                End If
            Next
    
            If sPayload = "" Then
                Return "MQTT Topic “”" & sTopic & q & " received"
            Else
                Return "MQTT Topic “”" & sTopic & q & " received with Payload "”” & sPayload & “””
            End If
    At this point a new trigger has been defined by the user. HS will store away as serialized data what we know as TrigActInfo from HS3. When it needs to be read back the user trigger setup the next time a user wants to edit the existing trigger, or the next time that HS starts and trigger info needs to be initialized then it needs to get and deserialize the data. The deserialization is done in ProcessData, but HS3 and HS4 formats are different so need to have your own ProcessData to handle the case where the data was originally created in HS3 and has not yet been modified by the user.
    Code:
        Protected Overrides Function ProcessData(inData As Byte()) As Byte()
    
            Dim legacyTrigger As MyTrigger1MqttReceive = Nothing
            If inData IsNot Nothing AndAlso inData.Length > 0 Then
                Try
                    'attempt to deserialize the byte array to your custom class
                    legacyTrigger = TrigActInfo.DeserializeLegacyData(Of MyTrigger1MqttReceive)(inData)
    
                Catch ex As Exception
                    'if there was a problem - set the legacy data object to null
                    legacyTrigger = Nothing
                End Try
            End If
    
            If legacyTrigger Is Nothing Then
                'if there is no legacy data object - call through to the default implementation
                Return MyBase.ProcessData(inData)
            End If
    
            Return Data
        End Function
    While your plugin is running it will detect a condition that matches the trigger criteria of this trigger class. To do this it first needs to know all the triggers that have been setup in HS4 for your plugin which is obtained by using TriggerMatches(). In my case I get this list when HS4 starts and then trap in HSEvent when the user makes a change to an event that has my trigger setup. I use a dictionary (oMessageTrigger1Dictionary) to cache all the trigger classes that need to be searched in a manner similar to what was done in HS3. Note in extract below “hs” is “HomeseerSystem” in the HST examples and PLUGIN_NAME is what was INTERFACE in HS3.
    Code:
        Private Sub InitializeTriggers()
            TriggerTypes.AddTriggerType(GetType(MQTTReceiveTrigger))
            BuildTriggerList()
        End Sub
    
        Public Sub BuildTriggerList()
            Dim TrigsToCheck() As HomeSeer.PluginSdk.Events.TrigActInfo = hs.TriggerMatches(PLUGIN_NAME, 1, -1)
            Dim Trig1 As MQTTReceiveTrigger 'MyTrigger1MqttReceive
            oMessageTrigger1Dictionary.Clear()
    
            If TrigsToCheck IsNot Nothing Then 'AndAlso TrigsToCheck.Count > 0 Then
                For Each TC As HomeSeer.PluginSdk.Events.TrigActInfo In TrigsToCheck
                    Try
                        Trig1 = New MQTTReceiveTrigger(TC.UID, TC.evRef, TC.SubTANumber - 1, TC.DataIn, Me, LogDebug)
                        oMessageTrigger1Dictionary.Add(Trig1.MyTrigger1MqttReceive.TriggerUID & Trig1.MyTrigger1MqttReceive.EvRef.ToString, Trig1.MyTrigger1MqttReceive)
                    Catch ex As Exception
                    End Try
                Next
            End If
    
    
        End Sub
    When the trigger conditions has been satisfied then HS4 is informed using the same TriggerFire() method as HS3.

    Code:
            hs.TriggerFire(PLUGIN_NAME, Trig1)
    My HS3 trigger code was developed from the original HS3 sample program and had much code related to serialization and deserialization and determining which of multiple types of triggers the trigger data actually represented. Methods like TriggerFromInfo(), TriggerFromData(), Add_Update_Trigger(), DeSerializeTrigger(), SerializeTrigger(), GetTrigs() are no longer used and the code not transferred to the HS4 implementation.







    #2
    Originally posted by Michael McSharry View Post

    While your plugin is running it will detect a condition that matches the trigger criteria of this trigger class. To do this it first needs to know all the triggers that have been setup in HS4 for your plugin which is obtained by using TriggerMatches(). In my case I get this list when HS4 starts and then trap in HSEvent when the user makes a change to an event that has my trigger setup. I use a dictionary (oMessageTrigger1Dictionary) to cache all the trigger classes that need to be searched in a manner similar to what was done in HS3. Note in extract below “hs” is “HomeseerSystem” in the HST examples and PLUGIN_NAME is what was INTERFACE in HS3.
    (note my emphasis)
    Please, can you explain how you are trapping Event Trigger changes via HSEvent()? I have registered to all events but have yet to see any event trigger or action changes result in an HSEvent() callback.

    Thank you in advance!

    Comment


      #3
      hs.RegisterEventCB(HomeSeer.PluginSdk.Constants.HSEvent.CONF IG_CHANGE, PLUGIN_NAME) is used to setup the callback in the Initialize() sub. HSEvent handles it. I see in my plugin code I no longer have any active code in the response to the CONFIG_CHANGE.

      It was a long time ago when I did this post. What I now have in the Initialize() sub and snipits later in the plugin are shown below. If this does not help then I will try to answer a specific question of what is not clear.

      Click image for larger version

Name:	1.png
Views:	163
Size:	79.2 KB
ID:	1533012
      Code:
      Public Sub RebuildTriggers()
        BuildTriggerList(1)
        BuildTriggerList(2)
        BuildTriggerList(3)
      End Sub
      Click image for larger version

Name:	2.png
Views:	139
Size:	220.4 KB
ID:	1533013

      In the recurring plugin logic

      Code:
      For Each arrSource As String() In oMessageTrigger1Dictionary.Values
      :
      :
      If bTrig Then
        Dim configuredTriggers As HomeSeer.PluginSdk.Events.TrigActInfo() = hs.GetTriggersByType(PLUGIN_NAME, 1)
        For Each configuredTrigger As HomeSeer.PluginSdk.Events.TrigActInfo In configuredTriggers
            If iUID = configuredTrigger.UID + configuredTrigger.evRef * 10000 Then
              hs.TriggerFire(PLUGIN_NAME, configuredTrigger)
            End If
         Next
       Next
      :
      :
      End if

      Comment


        #4
        Thank you for your response.
        I do properly register during the plugin's Initialization() method (for me it is c#):
        Code:
        protected override void Initialize()        
        {
            // ... lots of stuff then...
            //     HomeSeerSystem is my IHsController
            //
            HomeSeerSystem.RegisterEventCB( HomeSeer.PluginSdk.Constants.HSEvent.CONFIG_CHANGE, this.Id );
            HomeSeerSystem.RegisterEventCB( HomeSeer.PluginSdk.Constants.HSEvent.LOG, this.Id) ;
        }
        
        //
        // ... lots more stuff then...
        //
        
        public override void HsEvent( HomeSeer.PluginSdk.Constants.HSEvent eventType, object[] @params )        
        {            
            switch(eventType)            
            {                
                case HomeSeer.PluginSdk.Constants.HSEvent.CONFIG_CHANGE:                    
                    //
                    //   currently nothing to do here but I have a breakpoint so I can detect if I arrive here.
                    //
                    break;   
        
                case HomeSeer.PluginSdk.Constants.HSEvent.LOG:
                    //
                    // I do arrive here for each writing to the log
                    //
                    break;              
            }             
        }
        I get an HsEvent() callback for each log file write but not for UI changes to an event's trigger or action configurations.

        What am I missing here?

        Comment


          #5
          Okay, I see part of the problem. I only receive a CONFIG_CHANGE if I change the trigger, not parameters in the same trigger. For example if I keep the trigger type (my trigger type) but change the settings for that trigger type I receive no HsEvent() callback.

          Comment


            #6
            Alright, I think I have hacked some nasty code together to work around this.
            if OnConfigItemUpdate() is returning true then I am adding lambda code to the thread pool which pauses for a second then processes the current list of TrigActInfo objects.

            I feel that most of the work I have to do with this SDK is duct tape and bailing wire. I have to be missing something obvious but at least I am no longer unblocked.
            Thanks for your help.

            Comment


              #7
              I was just going to post the same thing where I update my trigger dictionary parameters as the user changes them in the same procedure as you are using.

              I think you are on the same boat as the rest of us. Do what you can based upon the couple of example projects in the SDK.

              Comment

              Working...
              X