Smart Home – управление устройствами Shelly®
В этой статье я покажу, как интегрировать реле Shelly® в мой умный дом, управляя устройствами через рутины в VB.NET.
После того, как я умный дом сделал для своего зятя, я также захотел получить модули реле Shelly® для своего дома.
Я в основном управляю этими устройствами с помощью Amazon Echo Dot® («Alexa
») – но из-за интереса и отсутствия хорошей документации я решил создать рутины на основе .NET, чтобы также можно было управлять или опрашивать устройства.
В этой статье я представляю разработанные мной рутины для этой цели и называю особенности, которые имеют используемые мной модули.
В моем доме используются модули Shelly 2.5, Shelly Dimmer2, Shelly 1PM и Shelly 2PM. Я создал рутины для этих модулей. Конечно, есть немного других модулей – но уважаемый читатель может создать рутины для них сам, возможно, используя этот шаблон.
Поскольку у меня только Visual Studio 2010® в распоряжении, здесь используется .NET 4.0
Основы
В основном, общение с устройствами осуществляется с помощью HTML-команд. Обратная связь от самих устройств представляется в виде строки JSON, в которой я сохраняю интересующую меня информацию в соответствующих подклассах. К сожалению, частичные команды различаются для каждого устройства, поэтому мне пришлось создать специфические рутины для каждого устройства.
Здесь я предполагаю базовые знания десериализации строк JSON. Я не буду подробно рассматривать использование WebClient.
С каким устройством я “разговариваю”?
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
Как видно здесь, есть общая команда для запроса типа для всех устройств. Ответ типа снова сохраняется в разных свойствах JSON, в зависимости от устройства – для некоторых устройств в элементе “type”, а для некоторых в элементе “app”. JSON-десериализация затем заполняет один из этих элементов в моем классе.
Функция, показанная здесь, возвращает мне соответствующий тип. Я использую этот запрос во всех последующих запросах/командах.
Запрос состояния устройства
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
Функция Shelly_GetStatus
, показанная здесь, возвращает статус Shelly устройства по указанному IP-адресу. Сама функция ветвится на соответствующую подфункцию в зависимости от типа Shelly.
Для обеспечения стандартизации здесь используется одинаковое состояние ввода-вывода для всех устройств, только несуществующие области в подфункциях не назначаются.
Подфункция состояния устройства
Я описываю саму подфункцию здесь, используя пример для одного из устройств. Остальные устройства отличаются только командой и полученной JSON-строкой в ответ.
В следующем примере я использую запрос Shelly-1PM
:
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
Shelly-1PM – это 1-канальное реле с одним только вводом. Однако JSON-строка, возвращаемая самим устройством, ничем не отличается от JSON-строки Shelly-2PM
– поэтому я использую одинаковый класс для десериализации JSON-строки для обоих устройств.
Управление устройством / отправка команды
В качестве примера я представляю функцию для управления реле в Shelly. Есть также возможность управлять яркостью диммера и перемещать жалюзи в определенное положение. Однако все эти функции не отличаются своими основами.
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
Интеграция в элемент управления Button
Следующий код демонстрирует интеграцию методов в элемент управления Button. В этом случае я расширяю стандартную кнопку с некоторыми свойствами и соответствующими функциями.
Кнопка теперь вызывает метод Shelly_ToggleOutput
при событии нажатия и меняет свои цвета в соответствии с состоянием вывода выбранного устройства Shelly.
Imports System.ComponentModelPublic Class ShellyButton
Inherits ButtonSub New()
MyBase.BackColor = my_DefaultBackColor
MyBase.ForeColor = my_DefaultForeColor
End Sub#Region "Свойства"
' делает стандартное свойство невидимым внутри PropertyGrid
<Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
Shadows Property ForeColor As Color' Замена стандартного свойства внутри PropertyGrid
<Category("Shelly"), Description("Default ForeColor of the Control")>
<DefaultValue(GetType(System.Drawing.Color), "Black")>
Shadows 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")>
Shadows 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' делает стандартное свойство невидимым внутри PropertyGrid
<Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
Shadows Property BackColor As Color' Замена стандартного свойства внутри PropertyGrid
<Category("Shelly"), Description("Default BackColor of the Control")>
<DefaultValue(GetType(System.Drawing.Color), "LightGray")>
Shadows 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)>
<DefaultValue(1000)>
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 "Методы"
' вызывает метод ToggleButton при нажатии на кнопку
Protected Overrides Sub OnClick(e As System.EventArgs)
Dim result As ShellyResult = Shelly_ToggleOutput(my_IPAdress, my_ShellyOutputNr)
End Sub' событие Timer-Tick производит анимацию кнопки при активации
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Интересные моменты
В общем, включены следующие методы:
Shelly_GetStatusString |
передает полную и форматированную строку с результатами выбранному запросу |
Shelly_GetType |
передает тип устройства Shelly по выбранному IP-адресу |
Shelly_GetStatus |
передает текущий статус устройства Shelly по выбранному IP-адресу, соответствующие характеристики возвращаются в Shelly_IOStatus . В зависимости от типа устройства используются подметоды:
|
Shelly_SetOutput |
устанавливает выбранный выход на устройстве Shelly для выбранного IP-адреса в выбранный статус |
Shelly_ToggleOutput |
переключает состояние выбранного выхода на устройстве Shelly по выбранному IP-адресу |
Shelly_SetRoller |
устанавливает жалюзи / роллеты в выбранную позицию на устройстве Shelly по выбранному IP-адресу |
Shelly_ToggleRoller |
переключает состояние движения жалюзи / роллеты в выбранную позицию на устройстве Shelly по выбранному IP-адресу |
Shelly_SetDimmer |
управляет диммером на устройстве Shelly по выбранному IP-адресу, устанавливая выбранное значение яркости |
с возвращаемыми типами:
Enum ShellyType |
возможные типы устройств Shelly |
Enum ShellyResult |
Возможные результаты запроса |
Enum ShellyMode |
возможные режимы работы устройства Shelly |
Enum ShellyRollerState |
возможные состояния двигателя жалюзи / роллеты |
Class Shelly IOStatus |
IO-статус запрашиваемого устройства Shelly |
И, наконец, последние слова
Я хотел бы поблагодарить @RichardDeeming и @Andre Oosthuizen за помощь в некоторых деталях, которые я не знал.
Основную информацию о самих устройствах я получил с сайта Shelly Support.
Я самостоятельно определял имена элементов запросов с помощью метода обратного проектирования.
Leave a Reply