Click here to Skip to main content
15,887,585 members
Articles / Desktop Programming / Windows Forms

Smart Home – Controlling Shelly® Devices (Part 2)

Rate me:
Please Sign up or sign in to vote.
4.47/5 (4 votes)
6 Dec 2023CPOL3 min read 3.5K   76   4  
This article is a continuation of a series on controlling Shelly® devices in a smart home. It introduces a component that works with standard controls without requiring adaptation.

Image 1

The basis for this article is the description in my previous article Smart Home – Controlling Shelly® Devices – Part 1

In this article I present a component that works with the standard controls without having to adapt them. Here the functions are assigned and the controls are animated via the component that I present in this article.

All or just some of the controls of the form can be selected and the desired action can be assigned to them:

Image 2

In this article I assume that property handling, especially for collection objects, is known.

Basics

In contrast to a control, a component does not know which form it belongs to. This information can only be obtained using a trick. I found the basics on StackOverFlow in this post: get components parent-form

Now I know the form to which my component belongs and therefore have access to all the controls it contains:

VB.NET
Private Sub GetParentForm()
     Dim myHost As IDesignerHost = Nothing
     If MyBase.Site IsNot Nothing Then myHost = CType(MyBase.Site.GetService(GetType(IDesignerHost)), IDesignerHost)
     If myHost IsNot Nothing Then
         myParentForm = CType(myHost.RootComponent, Form)
         Exit Sub
     End If
 End Sub

 Public Overrides Property Site() As System.ComponentModel.ISite
     Get
         Return MyBase.Site
     End Get
     Set(ByVal value As System.ComponentModel.ISite)
         MyBase.Site = value
         GetParentForm()
     End Set
 End Property

 <Category("Info-Data"), Description("the Parentform of this Component")>
 Property ParentForm() As Form
     Get
         Return myParentForm
     End Get
     Set(value As Form)
         myParentForm = value
     End Set
 End Property
 Private myParentForm As Form
C#
private void GetParentForm()
  {
      IDesignerHost myHost = null;
      if (base.Site != null)
          myHost = (IDesignerHost)base.Site.GetService(typeof(IDesignerHost));
      if (myHost != null)
      {
          myParentForm = (System.Windows.Forms.Form)myHost.RootComponent;
          return;
      }
  }

  public override System.ComponentModel.ISite Site
  {
      Get { return base.Site; }
      set
      {
          base.Site = value;
          GetParentForm();
      }
  }

  [Category("Info-Data")]
  [Description("the Parentform of this Component")]
  public Form ParentForm
  {
      get { return myParentForm; }
      set { myParentForm = value; }
  }
  private Form myParentForm;

With my own properties I now can specify with which colors a selected control should be animated and at what time interval a possible value change should be detected.

How it works

This class ControlAssignmentDefinition contains the basis for the function assignment to the controls:

VB.NET
<TypeConverter(GetType(ExpandableObjectConverter))>
Public Class ControlAssignmentDefinition

    <Category("Control"), Description("the Control which should do the Action")>
    Property SelectedControl As Control
        Get
            Return mySelectedControl
        End Get
        Set(value As Control)
            mySelectedControl = value
            savedBackColor = value.BackColor
            savedForeColor = value.ForeColor
        End Set
    End Property
    Private mySelectedControl As Control = Nothing

    Public savedBackColor As Color
    Public savedForeColor As Color

    <Category("Shelly"), Description("IpAdress of the Shelly-Device to work with")>
    <RefreshProperties(RefreshProperties.All)>
    Property IpAdress As String
        Get
            Return my_IPAdress
        End Get
        Set(value As String)
            my_ShellyType = ShellyCom.Shelly_GetType(value)
            If my_ShellyType <> ShellyCom.ShellyType.None Then my_IPAdress = value
            If my_ShellyType = ShellyCom.ShellyType.Shelly_Dimmer2 Then myOutput = 0
        End Set
    End Property
    Private my_IPAdress As String = ""

    <Category("Shelly"), Description("shows the Type of the connected Shelly-Device")>
    ReadOnly Property ShellyType As String
        Get
            Return my_ShellyType.ToString
        End Get
    End Property
    Private my_ShellyType As ShellyCom.ShellyType

    <Category("Shelly"), Description("Output-Number of the Shelly-Device to work with")>
    <DefaultValue(0)>
    Property OutputNr As Integer
        Get
            Return myOutput
        End Get
        Set(value As Integer)
            If (value >= 0) And (value <= 1) Then
                myOutput = value
            End If
        End Set
    End Property
    Private myOutput As Integer = 0

    <Category("Shelly"), Description("the Value which is assigned to the Shelly-Device")>
    <DefaultValue(0)>
    Property Value As Integer
        Get
            Return myValue
        End Get
        Set(value As Integer)
            If (value >= 0) And (value <= 100) Then
                myValue = value
            End If

        End Set
    End Property
    Private myValue As Integer = 0

    <Category("Shelly"), Description("the Action which happens with a Control-Click Event")>
    <DefaultValue(GetType(ShellyActionComponent.ActionDefinition), "none")>
    Property Action As ShellyActionComponent.ActionDefinition
        Get
            Return myAction
        End Get
        Set(value As ShellyActionComponent.ActionDefinition)
            myAction = value
        End Set
    End Property
    Private myAction As ShellyActionComponent.ActionDefinition = ShellyActionComponent.ActionDefinition.none

    Public Sub New()
    End Sub

    Public Overrides Function toString() As String
        If mySelectedControl IsNot Nothing Then Return mySelectedControl.Name + " => " + myAction.ToString
        Return "[-]"
    End Function

End Class
C#
[TypeConverter(typeof(ExpandableObjectConverter))]
 public class ControlAssignmentDefinition
 {
     [Category("Control")]
     [Description("the Control which should do the Action")]
     public Control SelectedControl
     {
         get { return mySelectedControl; }
         set
         {
             mySelectedControl = value;
             savedBackColor = value.BackColor;
             savedForeColor = value.ForeColor;
         }
     }
     private Control mySelectedControl = null;

     public Color savedBackColor;
     public Color savedForeColor;

     [Category("Shelly")]
     [Description("IpAdress of the Shelly-Device to work with")]
     [RefreshProperties(RefreshProperties.All)]
     public string IpAdress
     {
         get { return my_IPAdress; }
         set
         {
             my_ShellyType = ShellyCom.Shelly_GetType(value);
             if (my_ShellyType != ShellyCom.ShellyType.None)
                 my_IPAdress = value;
             if (my_ShellyType == ShellyCom.ShellyType.Shelly_Dimmer2)
                 myOutput = 0;
         }
     }
     private string my_IPAdress = "";

     [Category("Shelly")]
     [Description("shows the Type of the connected Shelly-Device")]
     public string ShellyType
     {
         get { return Convert.ToString(my_ShellyType); }
     }
     private ShellyCom.ShellyType my_ShellyType;

     [Category("Shelly")]
     [Description("Output-Number of the Shelly-Device to work with")]
     [DefaultValue(0)]
     public int OutputNr
     {
         get { return myOutput; }
         set
         {
             if ((value >= 0) & (value <= 1))
                 myOutput = value;
         }
     }
     private int myOutput = 0;

     [Category("Shelly")]
     [Description("the Value which is assigned to the Shelly-Device")]
     [DefaultValue(0)]
     public int Value
     {
         get { return myValue; }
         set
         {
             if ((value >= 0) & (value <= 100))
                 myValue = value;
         }
     }
     private int myValue = 0;

     [Category("Shelly")]
     [Description("the Action which happens with a Control-Click Event")]
     [DefaultValue(typeof(ShellyActionComponent.ActionDefinition), "none")]
     public ShellyActionComponent.ActionDefinition Action
     {
         get { return myAction; }
         set { myAction = value; }
     }
     private ShellyActionComponent.ActionDefinition myAction = ShellyActionComponent.ActionDefinition.none;

     public ControlAssignmentDefinition()
     {
     }

     public override string ToString()
     {
         if (mySelectedControl != null)
             return mySelectedControl.Name + " => " + Convert.ToString(myAction);
         return "[-]";
     }
 }

In order to be able to address several controls of the form with this class, it is embedded in the ActionCollection as a List (of ControlAssignmentDefinition).

This collection provides the designer script of the form with the assignments to the controls via the ShellyActions property:

VB.NET
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
ReadOnly Property ShellyActions As ActionCollection
    Get
        Return my_ShellyActions
    End Get
End Property
Private my_ShellyActions As New ActionCollection
C#
[Category("Shelly")]
[Description("Assignment to the Controls")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ActionCollection ShellyActions
{
    get { return my_ShellyActions; }
}
private ActionCollection my_ShellyActions = new ActionCollection();

The properties of the ControlAssignmentDefinition class already mentioned have the following meaning:

  • SelectedControl - the control that should execute the assigned function
  • IpAdress – the IPAdress of the Shelly device to work with
  • ShellyType – shows the Type of the Shelly Device of the selected IpAdress
  • Action – which action should happen? (SetOut, ToggleOut, SetDimmer, SetRoller, ToggleRoller)
  • Output number – the number of the output of the Shelly device to work with
  • Value – the Value which is assigned to the Output – if the selected Action is Dimmer or Roller the Value is their percentage

For each control defined here, the click event is evaluated in the collection and assigned to the selected action.

For the sake of simplicity, I embedded a timer in the component to animate the controls used, the interval of which calls the ShellyRequest method, in which the elements of the collection are then gone through and checked for status changes.

The controls are then animated accordingly - although there is different logic for this, depending on the function selected.

VB.NET
Private myTimer As New Timer With {.Enabled = False, .Interval = 1000}

Private Sub ShellyRequest(sender As Object, e As System.EventArgs) 'Handles myTimer.Tick
    If myParentForm IsNot Nothing And Not DesignMode Then
        Dim ShellyStatus As ShellyCom.Shelly_IOStatus
        Dim myItem As ControlAssignmentDefinition
        Dim outActive1, outActive2, outActive3, notActive As Boolean
        Dim ControlType As Type

        For i As Integer = 0 To my_ShellyActions.Count - 1
            myItem = my_ShellyActions.Item(i)
            If ShellyCom.Shelly_GetType(myItem.IpAdress) <> ShellyCom.ShellyType.None Then
                ShellyStatus = ShellyCom.Shelly_GetStatus(myItem.IpAdress)
                notActive = ((myItem.Action = ActionDefinition.SetOut) Or (myItem.Action = ActionDefinition.SetDimmer)) And (myItem.Value = 0)
                outActive1 = (myItem.Action = ActionDefinition.SetOut) And (myItem.Value <> 0)
                outActive2 = (myItem.Value <> 0) And (((myItem.OutputNr = 0) And ShellyStatus.Out0) Or ((myItem.OutputNr = 1) And ShellyStatus.Out1))
                outActive3 = ShellyStatus.RollerState = ShellyCom.ShellyRollerState.Opening Or ShellyStatus.RollerState = ShellyCom.ShellyRollerState.Closing
                ControlType = myItem.SelectedControl.GetType

                Select Case ControlType
                    Case GetType(Button)
                        If (outActive1 Or outActive2 Or outActive3) And Not notActive Then
                            myItem.SelectedControl.BackColor = my_AnimationBackColor
                            myItem.SelectedControl.ForeColor = my_AnimationForeColor
                        Else
                            myItem.SelectedControl.BackColor = myItem.savedBackColor
                            myItem.SelectedControl.ForeColor = myItem.savedForeColor
                        End If
                    Case GetType(Label), GetType(TextBox)
                        myItem.SelectedControl.Text = ShellyStatus.OutValue.ToString + " %"
                        If ShellyStatus.Mode = ShellyCom.ShellyMode.Roller Then myItem.SelectedControl.Text += " - " + ShellyStatus.RollerState.ToString
                End Select

            End If
        Next
    End If
End Sub
C#
private Timer myTimer = new Timer() { Enabled = false, Interval = 1000 };

private void ShellyRequest(object sender, System.EventArgs e) // Handles myTimer.Tick
{
    if (myParentForm != null & !DesignMode)
    {
        ShellyCom.Shelly_IOStatus ShellyStatus;
        ControlAssignmentDefinition myItem;
        bool outActive1, outActive2, outActive3, notActive;
        Type ControlType;

        for (int i = 0; i <= my_ShellyActions.Count - 1; i++)
        {
            myItem = my_ShellyActions.Item(i);
            if (ShellyCom.Shelly_GetType(myItem.IpAdress) != ShellyCom.ShellyType.None)
            {
                ShellyStatus = ShellyCom.Shelly_GetStatus(myItem.IpAdress);
                notActive = ((myItem.Action == ActionDefinition.SetOut) | (myItem.Action == ActionDefinition.SetDimmer)) & (myItem.Value == 0);
                outActive1 = (myItem.Action == ActionDefinition.SetOut) & (myItem.Value != 0);
                outActive2 = ((myItem.OutputNr == 0) & ShellyStatus.Out0) | ((myItem.OutputNr == 1) & ShellyStatus.Out1);
                outActive3 = ShellyStatus.RollerState == ShellyCom.ShellyRollerState.Opening | ShellyStatus.RollerState == ShellyCom.ShellyRollerState.Closing; ControlType = myItem.SelectedControl.GetType();

                if (ControlType == typeof(Button))
                {
                    if ((outActive1 | outActive2 | outActive3) & !notActive)
                    {
                        myItem.SelectedControl.BackColor = my_AnimationBackColor;
                        myItem.SelectedControl.ForeColor = my_AnimationForeColor;
                    }
                    else
                    {
                        myItem.SelectedControl.BackColor = myItem.savedBackColor;
                        myItem.SelectedControl.ForeColor = myItem.savedForeColor;
                    }
                }
                else if(ControlType == typeof(Label) | ControlType == typeof(TextBox))
                    myItem.SelectedControl.Text = Convert.ToString(ShellyStatus.OutValue)+ " %";
                    if (ShellyStatus.Mode == ShellyCom.ShellyMode.Roller)
                        myItem.SelectedControl.Text += " - " + Convert.ToString(ShellyStatus.RollerState);
            }
        }
    }
}

However, if the actual value of a blind or dimmer is to be displayed, this can only be done on a Label or a Textbox. In this case, these elements would also have to be included in the ShellyActions.

An assignment could look like this:

Image 3

Finally – last words

I have planned the Button control for executing the actions (use of the click event) and the Label and Textbox controls for displaying values ​​(use of the text property). For other controls, a connection did not seem to make sense to me - except you would want to work with customized controls here

I would like to thank @sean-ewington for the help in creating this and the previous article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --