Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Smart Home – Controlling Shelly® Devices (Part 1)

4.10/5 (7 votes)
30 Nov 2023CPOL5 min read 13.2K   230  
In this article, I demonstrate how to integrate Shelly® relays into my smart home, controlling the devices through routines in VB.NET.

After I made his new house smart with my son-in-law, I also wanted to get the Shelly® relays for my own house.

I mainly control these devices with the Amazon Echo Dot® ("Alexa") - but out of interest and because there is no good documentation to be found, I decided to create routines based on .NET also make it possible to control or query the devices.

In this article, I present the routines I created for this purpose and name the peculiarities that the modules I use have.

The Shelly 2.5, Shelly Dimmer2, Shelly 1PM and Shelly 2PM modules are used in my house. I created the routines for these modules. Of course, there are a few other modules - but the valued reader would then have to create the routines for them himself, possibly using this template.

Since I only have Visual Studio 2010® at my disposal, the framework used here is .NET 4.0

Basics

Basically, communication with the devices takes place via HTML commands. The feedback from the devices themselves is provided as a JSON string for which I have stored the information that interests me in appropriate subclasses. Unfortunately, the partial commands vary from device to device, which is why I had to create specific routines for each device.

I assume basic knowledge of the deserialization of JSON strings here. I won't go into the use of the WebClient any further.

With Which Device Am I "Speaking?"

VB.NET
Private Class ShellyTyp
        Public type As String
        Public app As String
 
        ReadOnly Property Typ() As String
            Get
                If type IsNot Nothing Then Return type
                If app IsNot Nothing Then Return app
                Return ""
            End Get
        End Property
 
    End Class
 
    Function Shelly_GetType(IpAdress As String) As ShellyType
 
        Request = "http://" + IpAdress + "/shelly"
        Dim myType As ShellyType = ShellyType.None
 
        Try
            Dim result As String = webClient.DownloadString(Request)
            Dim JSON_Packet As ShellyTyp = _
                JsonConvert.DeserializeObject(Of ShellyTyp)(result)
 
            Select Case JSON_Packet.Typ
                Case "SHSW-25" : myType = ShellyType.Shelly_25
                Case "SHDM-2" : myType = ShellyType.Shelly_Dimmer2
                Case "Plus1PM", "Plus1Mini" : myType = ShellyType.Shelly_1PM
                Case "Plus2PM" : myType = ShellyType.Shelly_2PM
            End Select
 
            Return myType
 
        Catch ex As Exception
            Return ShellyType.None
        End Try
 
    End Function
C#
private class JSON_ShellyType
{
	public string type = "";
	public string app = "";
	public string Typ
	{
		get
		{
			if (type != "")
				return type;
			if (app != "")
				return app;
			return "";
		}
	}
}

public static ShellyType Shelly_GetType(string IpAdress)
{
	Request = "http://" + IpAdress + "/shelly";
	ShellyType myType = ShellyType.None;
	try
	{
		string result = webClient.DownloadString(Request);
		JSON_ShellyType JSON_Packet = JsonConvert.DeserializeObject<JSON_ShellyType>(result);
		switch (JSON_Packet.Typ)
		{
			case "SHSW-25":
			{
				myType = ShellyType.Shelly_25;
				break;
			}

			case "SHDM-2":
			{
				myType = ShellyType.Shelly_Dimmer2;
				break;
			}

			case "Plus1PM":
			case "Plus1Mini":
			{
				myType = ShellyType.Shelly_1PM;
				break;
			}

			case "Plus2PM":
			{
				myType = ShellyType.Shelly_2PM;
				break;
			}
		}

		return myType;
	}
	catch (Exception)
	{
		return ShellyType.None;
	}
}

As can be seen here, there is a common command for the type query for all devices. The type response is again stored in different JSON properties depending on the device - for some devices in the "type" item and for some devices in the "app" item. The JSON deserialize then fills either one or the other item in my class.

The function shown returns me the respective type for. I use this query in all later queries/commands.

Request of the Device Status

VB.NET
Function Shelly_GetStatus(IpAdress As String) As IO_Status
	Dim myType As ShellyType = Shelly_GetType(IpAdress)
	Select Case myType
		Case ShellyType.Shelly_25
			Return Shelly_25_GetStatus(IpAdress)
		Case ShellyType.Shelly_Dimmer2
			Return Shelly_Dimmer2_GetStatus(IpAdress)
		Case ShellyType.Shelly_1PM
			Return Shelly_1PM_GetStatus(IpAdress)
		Case ShellyType.Shelly_2PM
			Return Shelly_2PM_GetStatus(IpAdress)
		Case ShellyType.None
			Return New IO_Status
	End Select

	Return New IO_Status
End Function

Class IO_Status

	Public Connection As ShellyResult = ShellyResult.None

	Public In0 As Boolean = False

	Public In1 As Boolean = False

	Public Out0 As Boolean = False

	Public Out1 As Boolean = False

	Public Mode As ShellyMode = ShellyMode.none

	Public OutValue As Integer = -1

	Overrides Function toString() As String
		Dim s As String = Connection.ToString
		Dim inActive As String = ""
		   If In0 Then inActive += "0"
		   If In1 Then inActive += "1"
		   If inActive < > "" Then s += ", in:" + inActive 
		Dim outActive As String = ""
			If Out0 Then outActive += "0"
			If Out1 Then outActive += "1"
			If outActive < > "" Then s += ", out:" + outActive 
			If OutValue > = 0 Then s += ", " + Str(OutValue).Trim + "%" 
		If Mode < > ShellyMode.none Then s += ", mode:" + Mode.ToString 
		Return s
    End Function
End Class
C#
public static Shelly_IOStatus Shelly_GetStatus(string IpAdress)
{
	ShellyType myType = Shelly_GetType(IpAdress);
	switch (myType)
	{
		case ShellyType.Shelly_25:
		{
			return Shelly_25_GetStatus(IpAdress);
		}

		case ShellyType.Shelly_Dimmer2:
		{
			return Shelly_Dimmer2_GetStatus(IpAdress);
		}

		case ShellyType.Shelly_1PM:
		{
			return Shelly_1PM_GetStatus(IpAdress);
		}

		case ShellyType.Shelly_2PM:
		{
			return Shelly_2PM_GetStatus(IpAdress);
		}

		case ShellyType.None:
		{
			return new Shelly_IOStatus();
		}
	}

	return new Shelly_IOStatus();
}

public class Shelly_IOStatus
{
	public ShellyResult Connection = ShellyResult.None;
	public bool In0 = false;
	public bool In1 = false;
	public bool Out0 = false;
	public bool Out1 = false;
	public ShellyMode Mode = ShellyMode.none;
	public ShellyRollerState RollerState = ShellyRollerState.none;
	public int OutValue = -1;
	public override string ToString()
	{
		string s = Connection.ToString();
		string inActive = "";
		if (In0)
			inActive += "0";
		if (In1)
			inActive += "1";
		if (inActive != "")
			s += ", in:" + inActive;
		string outActive = "";
		if (Out0)
			outActive += "0";
		if (Out1)
			outActive += "1";
		if (outActive != "")
			s += ", out:" + outActive;
		if (OutValue >= 0)
			s += ", " + Convert.ToString(OutValue).Trim() + "%";
		if (Mode != ShellyMode.none)
			s += ", mode:" + Mode.ToString();
		return s;
	}
}

The Shelly_GetStatus function shown here returns the status of the Shelly device at the specified IP address. The function itself branches to the corresponding subfunction according to the respective Shelly type.

In order to achieve standardization here, there is the same IO-status for all devices, only non-existent areas are not assigned in the subfunctions.

Device Status Subfunction

I will describe the sub-function itself here using the example for one of the devices. All other devices only differ in the command and the JSON string received in response.

In the following example, I use the query of a Shelly-1PM:

VB.NET
Private Class JSON_Shelly12PM_Status

	 < Newtonsoft.Json.JsonProperty("switch:0") >  
     Public Switch0 As cRelay
	 < Newtonsoft.Json.JsonProperty("switch:1") >  
     Public Switch1 As cRelay
	 < Newtonsoft.Json.JsonProperty("cover:0") >  
     Public Cover0 As cCover
	 < Newtonsoft.Json.JsonProperty("input:0") >  
     Public Input0 As cInput
	 < Newtonsoft.Json.JsonProperty("input:1") >  
     Public Input1 As cInput

	Partial Public Class cRelay
		Public output As Boolean
	End Class

	Partial Public Class cCover
		Public state As String
		Public last_direction As String
		Public current_pos As Integer
	End Class

	Partial Public Class cInput
		Public state As Object
	End Class

	ReadOnly Property RelayState As Boolean()
		Get
			Dim myState(1) As Boolean
			If Switch0 IsNot Nothing Then myState(0) = Switch0.output
			If Switch1 IsNot Nothing Then myState(1) = Switch1.output
			If Cover0 IsNot Nothing Then
				Select Case Cover0.state
					Case "stopped"
						myState(0) = False
						myState(1) = False
					Case "opening"
						myState(0) = True
						myState(1) = False
					Case "closing"
						myState(0) = False
						myState(1) = True
				End Select
			End If

			Return myState
		End Get
	End Property

	ReadOnly Property InputState As Boolean()
		Get
			Dim myState(1) As Boolean
			If Not Boolean.TryParse(Input0.state, myState(0)) Then myState(0) = False
			If Not Boolean.TryParse(Input1.state, myState(1)) Then myState(1) = False
			Return myState
		End Get
	End Property

	ReadOnly Property Mode As ShellyMode
		Get
			If Switch0 IsNot Nothing Then Return ShellyMode.Relay
			If Cover0 IsNot Nothing Then Return ShellyMode.Roller
			Return ShellyMode.none
		End Get
	End Property

	ReadOnly Property RollerState As ShellyRollerState
		Get
			If Cover0 IsNot Nothing Then
				If(Cover0.state = "stop") And (Cover0.last_direction = "opening") Then  Return ShellyRollerState.Stop_AfterOpening  
                If(Cover0.state = "closing") Then Return ShellyRollerState.Closing
				If(Cover0.state = "stop") And (Cover0.last_direction = "closing") Then  Return ShellyRollerState.Stop_AfterClosing  
                If(Cover0.state = "opening") Then Return ShellyRollerState.Opening
			End If

			Return ShellyRollerState.none
	    End Get
	End Property
End Class

Function Shelly_1PM_GetStatus(IpAdress As String) As IO_Status
	Dim myStatus As New IO_Status
	Request = "http://" + IpAdress + "/rpc/Shelly.GetStatus"
	Try
		Dim result As String = webClient.DownloadString(Request)
		Dim JSON_Packet As JSON_Shelly12PM_Status =
		 JsonConvert.DeserializeObject(Of JSON_Shelly12PM_Status)(result)
		myStatus.Out0 = JSON_Packet.RelayState(0)
		myStatus.Out0 = False
		myStatus.OutValue = -1
		myStatus.Mode = "Relay"
		myStatus.In0 = JSON_Packet.InputState(0)
		myStatus.In1 = False
		myStatus.Connection = ShellyResult.Connected
		Return myStatus

	Catch ex As Exception
		myStatus.Connection = ShellyResult.ErrorConnection
		Return myStatus
	End Try
End Function
C#
private class JSON_Shelly12PM_Status
{
	[Newtonsoft.Json.JsonProperty("switch:0")]
	public cRelay Switch0 = null;
	[Newtonsoft.Json.JsonProperty("switch:1")]
	public cRelay Switch1 = null;
	[Newtonsoft.Json.JsonProperty("cover:0")]
	public cCover Cover0 = null;
	[Newtonsoft.Json.JsonProperty("input:0")]
	public cInput Input0 = null;
	[Newtonsoft.Json.JsonProperty("input:1")]
	public cInput Input1 = null;
	public partial class cRelay
	{
		public bool output = false;
	}

	public partial class cCover
	{
		public string state = "";
		public string last_direction = "";
		public int current_pos = 0;
	}

	public partial class cInput
	{
		public object state = null;
	}

	public bool[] RelayState
	{
		get
		{
			bool[] myState = new bool[2];
			if (Switch0 != null)
				myState[0] = Switch0.output;
			if (Switch1 != null)
				myState[1] = Switch1.output;
			if (Cover0 != null)
			{
				switch (Cover0.state)
				{
					case "stopped":
					{
						myState[0] = false;
						myState[1] = false;
						break;
					}

					case "opening":
					{
						myState[0] = true;
						myState[1] = false;
						break;
					}

					case "closing":
					{
						myState[0] = false;
						myState[1] = true;
						break;
					}
				}
			}

			return myState;
		}
	}

	public bool[] InputState
	{
		get
		{
			bool[] myState = new bool[2];
			if (!bool.TryParse(Convert.ToString(Input0.state), out myState[0]))
			{
				myState[0] = false;
			}

			if (!bool.TryParse(Convert.ToString(Input1.state), out myState[1]))
			{
				myState[1] = false;
			}

			return myState;
		}
	}

	public ShellyMode Mode
	{
		get
		{
			if (Switch0 != null)
				return ShellyMode.Relay;
			if (Cover0 != null)
				return ShellyMode.Roller;
			return ShellyMode.none;
		}
	}

	public ShellyRollerState RollerState
	{
		get
		{
			if (Cover0 != null)
			{
				if ((Cover0.state == "stop") & (Cover0.last_direction == "opening"))
					return ShellyRollerState.Stop_AfterOpening;
				if ((Cover0.state == "closing"))
					return ShellyRollerState.Closing;
				if ((Cover0.state == "stop") & (Cover0.last_direction == "closing"))
					return ShellyRollerState.Stop_AfterClosing;
				if ((Cover0.state == "opening"))
					return ShellyRollerState.Opening;
			}

			return ShellyRollerState.none;
		}
	}
}

private static Shelly_IOStatus Shelly_1PM_GetStatus(string IpAdress)
{
	Shelly_IOStatus myStatus = new Shelly_IOStatus();
	Request = "http://" + IpAdress + "/rpc/Shelly.GetStatus";
	try
	{
		string result = webClient.DownloadString(Request);
		JSON_Shelly12PM_Status JSON_Packet = JsonConvert.DeserializeObject<JSON_Shelly12PM_Status>(result);
		myStatus.Out0 = JSON_Packet.RelayState[0];
		myStatus.Out0 = false;
		myStatus.OutValue = -1;
		myStatus.Mode = ShellyMode.Relay;
		myStatus.In0 = JSON_Packet.InputState[0];
		myStatus.In1 = false;
		myStatus.Connection = ShellyResult.Connected;
		return myStatus;
	}
	catch (Exception)
	{
		myStatus.Connection = ShellyResult.ErrorConnection;
		return myStatus;
	}
}

The Shelly-1PM is a 1-channel relay with only one input. However, the JSON string returned by the device itself is no different from that of a Shelly-2PM - that's why I use the same class for deserialization of the JSON string for both devices.

Controlling a Device / Sending a Command

As an example, I present the function for controlling a relay in Shelly. There is also the option of controlling the brightness of a dimmer and moving a blind to a specific position. However, none of these functions differ in their basics.

VB.NET
Function Shelly_SetOutput(IpAdress As String, OutNr As Integer, State As Boolean  ) As ShellyResult  
    Dim myType As ShellyType = Shelly_GetType(IpAdress)
	Request = "http://" + IpAdress + "/relay/"
	Select Case myType
		Case ShellyType.Shelly_1PM
			Request += "0?turn="
			If Not State Then
				Request += "off"
			Else
				Request += "on"
			End If

		Case ShellyType.Shelly_2PM, ShellyType.Shelly_25
			Select Case OutNr
				Case 0, 1
					Request += Str(OutNr).Trim
				Case Else
					Return ShellyResult.ErrorShellyType
			End Select

			Request += "?turn="
			If Not State Then
				Request += "off"
			Else
				Request += "on"
			End If

		Case ShellyType.Shelly_Dimmer2
			Request = "http://" + IpAdress + "/light/0?turn="
			If Not State Then
				Request += "off"
			Else
				Request += "on"
			End If

		Case Else
			Return ShellyResult.NoAction
	End Select

	Try
		Dim result As String = webClient.DownloadString(Request)
		Return ShellyResult.Done
	Catch ex As Exception
		Return ShellyResult.ErrorConnection
	End Try

	Return ShellyResult.NoAction
End Function
C#
public static ShellyResult Shelly_SetOutput(string IpAdress, int OutNr, bool State)
{
ShellyType myType = Shelly_GetType(IpAdress);
Request = "http://" + IpAdress + "/relay/";
switch (myType)
{
	case ShellyType.Shelly_1PM:
	{
		Request += "0?turn=";
		if (!State)
			Request += "off";
		else
			Request += "on";
		break;
	}

	case ShellyType.Shelly_2PM:
	case ShellyType.Shelly_25:
	{
		switch (OutNr)
		{
			case 0:
			case 1:
			{
				Request += Convert.ToString(OutNr).Trim();
				break;
			}

			default:
			{
				return ShellyResult.ErrorShellyType;
			}
		}

		Request += "?turn=";
		if (!State)
			Request += "off";
		else
			Request += "on";
		break;
	}

	case ShellyType.Shelly_Dimmer2:
	{
		Request = "http://" + IpAdress + "/light/0?turn=";
		if (!State)
			Request += "off";
		else
			Request += "on";
		break;
	}

	default:
	{
		return ShellyResult.NoAction;
	}
}

try
{
	string result = webClient.DownloadString(Request);
	return ShellyResult.Done;
}
catch (Exception)
{
	return ShellyResult.ErrorConnection;
} //return ShellyResult.NoAction;
}

Integration at a Button-Control

The following code shows the integration of the methods into a button control. In this case, I extend the standard button with some properties and integrate the functions accordingly.

The button now calls the Shelly_ToggleOutput method at the click event and changes its colors according to the output status of the selected Shelly device.

VB.NET
Imports System.ComponentModel

Public Class ShellyButton
	Inherits Button

	Sub New()
		MyBase.BackColor = my_DefaultBackColor
		MyBase.ForeColor = my_DefaultForeColor
	End Sub

	 #Region "Properties"
     ' makes the Standard - Property unvisible inside the PropertyGrid   
     < Browsable(False), EditorBrowsable(EditorBrowsableState.Never) >  
     Shadows Property ForeColor As Color

	 ' Replacement for the Standard - Property inside the PropertyGrid   
     < Category("Shelly"), Description("Default ForeColor of the Control") >   
     < DefaultValue(GetType(System.Drawing.Color), "Black") >  
     Property DefaultForeColor As Color
		Get
			Return my_DefaultForeColor
		End Get

		Set(ByVal value As Color)
			my_DefaultForeColor = value
			MyBase.BackColor = value
		End Set
	 End Property

	 Private my_DefaultForeColor As Color = Color.Black

	 < Category("Shelly"), Description("ForeColor of the Control when animated") >   
     < DefaultValue(GetType(System.Drawing.Color), "White") >  
     Property AnimationForeColor As Color
		Get
			Return my_AnimationForeColor
		End Get

		Set(ByVal value As Color)
			my_AnimationForeColor = value
		End Set
	End Property

	Private my_AnimationForeColor As Color = Color.White

	 ' makes the Standard - Property unvisible inside the PropertyGrid   
     < Browsable(False), EditorBrowsable(EditorBrowsableState.Never) >  
     Shadows Property BackColor As Color

	 ' Replacement for the Standard - Property inside the PropertyGrid   
     < Category("Shelly"), Description("Default BackColor of the Control") >   
     < DefaultValue(GetType(System.Drawing.Color), "LightGray") >  
     Property DefaultBackColor As Color
		Get
			Return my_DefaultBackColor
		End Get

		Set(ByVal value As Color)
			my_DefaultBackColor = value
			MyBase.BackColor = value
			Me.Invalidate()
		End Set
	End Property

	Private my_DefaultBackColor As Color = Color.LightGray

	< Category("Shelly"), Description("BackColor of the Control when animated") >
    < DefaultValue(GetType(System.Drawing.Color), "Green") >  
    Property AnimationBackColor As Color
		Get
			Return my_AnimationBackColor
		End Get

		Set(ByVal value As Color)
			my_AnimationBackColor = value
			Me.Invalidate()
		End Set
	End Property

	Private my_AnimationBackColor As Color = Color.Green

	 < Category("Shelly"), Description("Refresh-Interval for the Animation") >   
     < DefaultValue(1000) >  
     Property RefreshInterval As Integer
		Get
			Return my_Timer.Interval
		End Get

		Set(value As Integer)
			If value > 500 Then 
				my_Timer.Interval = value
			End If
		End Set
	End Property

	 < Category("Shelly"), Description("Enables the Refresh of the Animation") >   
     < DefaultValue(False) >  
     Property RefreshEnabled As Boolean
		Get
			Return my_RefreshEnabled
		End Get

		Set(value As Boolean)
			my_RefreshEnabled = value
			If Not DesignMode Then my_Timer.Enabled = value
		End Set
	End Property

	Private my_RefreshEnabled As Boolean = False

	 < 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 = Shelly_GetType(value).ToString
			If my_ShellyType < > "None" Then my_IPAdress = value 
			  End Set
	End Property

	Private my_IPAdress As String = ""

	 < Category("Shelly"), Description("Output-Number of the Shelly-Device to work with") >   
     < DefaultValue(0) >  
     Property ShellyOutputNr As Integer
		Get
			Return my_ShellyOutputNr
		End Get

		Set(value As Integer)
			If(value > = 0) And(value < = 1)  Then my_ShellyOutputNr = value
		End Set
	End Property

	Private my_ShellyOutputNr As Integer = 0

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

	Private my_ShellyType As String

	 #End Region

#Region "Methods"
  call the ToggleButton - Method with the Button - Click  
    Protected Overrides Sub OnClick(e As System.EventArgs)
	   Dim result As ShellyResult = Shelly_ToggleOutput(my_IPAdress, my_ShellyOutputNr)
	End Sub

	 ' the Timer - Tick does when activated the Animation of the Button  
     Sub Timer_Tick() Handles my_Timer.Tick
		my_Status = Shelly_GetStatus(my_IPAdress)
		my_OutActive =(my_ShellyOutputNr = 0 And my_Status.Out0)
		 Or  (my_ShellyOutputNr = 1 And my_Status.Out1)  If my_OutActive Then
			MyBase.BackColor = my_AnimationBackColor
			MyBase.ForeColor = my_AnimationForeColor
		Else
			MyBase.BackColor = my_DefaultBackColor
			MyBase.ForeColor = my_DefaultForeColor
		End If
	End Sub

	Private my_Status As Shelly_IOStatus

	Private my_OutActive As Boolean = False

	Private WithEvents my_Timer As New Timer With {.Enabled = False, .Interval = 1000}
#End Region
End Class
C#
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using ShellyConnect_C;

//using ShellyCom;
public class ShellyButton : System.Windows.Forms.Button
{
	public ShellyButton()
	{
		my_Timer = new System.Windows.Forms.Timer()
		{
			Enabled = false,
			Interval = 1000
		};
		my_Timer.Tick += Timer_Tick;
		base.BackColor = my_DefaultBackColor;
		base.ForeColor = my_DefaultForeColor;
	}

	// makes the Standard -Property unvisible inside the PropertyGrid
    [Browsable(false)][EditorBrowsable(EditorBrowsableState.Never)] 
    public new Color ForeColor { get; set; }

	// Replacement for the Standard-Property inside the PropertyGrid
	[Category("Shelly")]
	[Description("Default ForeColor of the Control ")]
    [DefaultValue(typeof(System.Drawing.Color), "Black")] 
    public new Color DefaultForeColor
	{
		get
		{
			return my_DefaultForeColor;
		}

		set
		{
			my_DefaultForeColor = value;
			base.BackColor = value;
		}
	}

	private Color my_DefaultForeColor = Color.Black;

	[Category("Shelly")]
	[Description("ForeColor of the Control when animated ")]
    [DefaultValue(typeof(System.Drawing.Color), "White")] 
    public Color AnimationForeColor
	{
		get
		{
			return my_AnimationForeColor;
		}

		set
		{
			my_AnimationForeColor = value;
		}
	}

	private Color my_AnimationForeColor = Color.White;

	// makes the Standard -Property unvisible inside the PropertyGrid
   [Browsable(false)][EditorBrowsable(EditorBrowsableState.Never)] 
   public new Color BackColor { get; set; }

	// Replacement for the Standard-Property inside the PropertyGrid
	[Category("Shelly")]
	[Description("Default BackColor of the Control ")]
    [DefaultValue(typeof(System.Drawing.Color), "LightGray")] 
    public new Color DefaultBackColor
	{
		get
		{
			return my_DefaultBackColor;
		}

		set
		{
			my_DefaultBackColor = value;
			base.BackColor = value;
			this.Invalidate();
		}
	}

	private Color my_DefaultBackColor = Color.LightGray;

	[Category("Shelly")]
	[Description("BackColor of the Control when animated ")]
    [DefaultValue(typeof(System.Drawing.Color), "Green")] 
    public Color AnimationBackColor
	{
		get
		{
			return my_AnimationBackColor;
		}

		set
		{
			my_AnimationBackColor = value;
			this.Invalidate();
		}
	}

	private Color my_AnimationBackColor = Color.Green;

	[Category("Shelly")]
	[Description("Refresh-Interval for the Animation ")]
    [DefaultValue(1000)] 
    public int RefreshInterval
	{
		get
		{
			return my_Timer.Interval;
		}

		set
		{
			if (value > 500)
				my_Timer.Interval = value;
		}
	}

	[Category("Shelly")]
	[Description("Enables the Refresh of the Animation")]
	[DefaultValue(false)]
	public bool RefreshEnabled
	{
		get
		{
			return my_RefreshEnabled;
		}

		set
		{
			my_RefreshEnabled = value;
			if (!DesignMode)
				my_Timer.Enabled = value;
		}
	}

	private bool my_RefreshEnabled = false;

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

	private string my_IPAdress = "192.168.178.201";

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

	private int my_ShellyOutputNr = 0;

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

	private string my_ShellyType;

	// call the ToggleButton -Method with the Button-Click 
    protected override void OnClick(System.EventArgs e)
	{
		ShellyCom.ShellyResult result = ShellyCom.Shelly_ToggleOutput(my_IPAdress, my_ShellyOutputNr);
	}

	// the Timer-Tick does when activated the Animation of the Button 
    public void Timer_Tick(object sender, System.EventArgs e)
	{
		my_Status = ShellyCom.Shelly_GetStatus(my_IPAdress);
		my_OutActive = (my_ShellyOutputNr == 0 & my_Status.Out0) | (my_ShellyOutputNr == 1 & my_Status.Out1);
		if (my_OutActive)
		{
			base.BackColor = my_AnimationBackColor;
			base.ForeColor = my_AnimationForeColor;
		}
		else
		{
			base.BackColor = my_DefaultBackColor;
			base.ForeColor = my_DefaultForeColor;
		}
	}

	private ShellyCom.Shelly_IOStatus my_Status;
	private bool my_OutActive = false;
	private Timer my_Timer;
}

Points of Interest

Overall, the following methods are included:

Shelly_GetStatusString passes the complete and formatted result string to the selected request
Shelly_GetType passes the type of Shelly device at the selected IP address
Shelly_GetStatus transfers the current device status of the Shelly device to the selected IP address the corresponding characteristics are returned in the Shelly_IOStatusDepending on the device type, the sub-methods are used:
  • Shelly_25_GetStatus: get the state of a Shelly 2.5
  • Shelly_25_convertJSON: convert the JSON-String of the Shelly 2.5 request
  • Shelly_Dimmer2_GetStatus: get the state of a Shelly Dimmer2
  • Shelly_Dimmer2_convertJSON: convert the JSON-String of the Shelly Dimmer2 request
  • Shelly_1PM_GetStatus: get the state of a Shelly 1PM
  • Shelly_1PM_convertJSON: convert the JSON-String of the Shelly 1PM request
  • Shelly_2PM_GetStatus: get the state of a Shelly 2PM
  • Shelly_2PM_convertJSON: convert the JSON-String of the Shelly 2PM request
Shelly_SetOutput sets the selected output on the Shelly device at the selected IP address to the selected status
Shelly_ToggleOutput toggles the status of the selected output on the Shelly device at the selected IP address
Shelly_SetRoller sets the blinds / rollers to the selected position on the Shelly device at the selected IP address
Shelly_ToggleRoller toggles the driving status of the blind / roller to the selected position on the Shelly device at the selected IP address
Shelly_SetDimmer controls the dimmer on the Shelly device at the selected IP address to the selected brightness value

with the return types:

Enum ShellyType the possible Shelly-Types
Enum ShellyResult the possible results of a request
Enum ShellyMode the possible operating modes of the Shelly device
Enum ShellyRollerState the possible states of the blind / roller drive
Class Shelly IOStatus the IO-Status of the requested Shelly device

Finally – Last Words

I like to thank @RichardDeeming and @Andre Oosthuizen for assistance with some details I didn't know.

I obtained basic information about the devices themselves from the Page Shelly Support.

I had to determine the item names of the queries myself using reverse engineering.

History

  • 23rd November, 2023: Initial version

License

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