TuyaCore in vb.net
Closed this issue · 1 comments
Hi Marcus,
A couple of year ago I wrote an UWP program that controls lights and sensors based on Zwave. Recently I bought some Tuya lights and sockets and want to implement those in my program. I found your TuyaCore and downloaded it. As I write my program in vb.net I rewrote your C# version. I also cannot use Windows Core because my program has to run on a Raspberry Pi and therefor uses Windows UWP. After some struggle I finaly got the program error-free. Unfortunatelly, I get error messages when I try to address any of my Tuya devices. I did manage to find the DevID and the LocalKey for my devices. The errormessage I get is: Message = "Unable to read data from the transport connection: The external host has broken the connection.". Stacktrace: StackTrace = " at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)" & vbCrLf & " at System.Threading.Tasks.TaskFactory1.FromAsyncCoreLogic(IAsyncResult iar, Func
2 endFunction, Action1 endAction, Task
1 promise, Boolean requiresSynchronization)" ...
I'm not familiar with programming a WiFi call so I have no clue what this error means. Would you be so kind to help me please? I have added my vb.net tuyacode code below. I had to look for some alternative coding here and there because some statements are not supported in vb.net/UWP. The routines below are called from a main program by this code:
Protected Async Sub SetTuyaStatus(ByVal OnOff As Boolean)
Dim device As New TuyaCore.TuyaPlug()
device.IP = "192.168.2.222"
device.LocalKey = "" // deleted the localkey
device.Id = "" // deleted the device ID
Try
Dim status As TuyaCore.TuyaPlug.TuyaStatus = Await device.GetStatus()
Await device.SetStatus(Not status.Powered)
Catch ex As Exception
LogEventSource.Log.Error("Error in routine SetTuyaStatus:" & ex.Message.ToString)
End Try
End Sub
The error occurs in the GetStatus routine.
Tuya Classes:
Imports System.Net.Sockets
Imports System.Security.Cryptography
Imports System.Text
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports Windows.Security.Cryptography.Core
Imports Windows.Storage.Streams
Namespace TuyaCore
Public Class TuyaDevice
Public Sub New()
End Sub
Public Property IP As String
Public Property Id As String
Public Property LocalKey As String
Public Property Port As Integer = 6668
Public Property Version As String = "3.1"
Protected Function CreatePayload(ByVal data As String, ByVal Optional encrypt As Boolean = True) As Byte()
If Not encrypt Then Return Encoding.UTF8.GetBytes(data)
Dim bytes = Encoding.UTF8.GetBytes(data)
'Dim aes = New AesManaged() With {
' .Mode = CipherMode.ECB,
' .Key = Encoding.UTF8.GetBytes(LocalKey)
'}
Dim Key As Byte() = Encoding.UTF8.GetBytes(LocalKey)
Using ms = New MemoryStream()
Using cs = New CryptoStream(ms, Aes.Create.CreateEncryptor(), CryptoStreamMode.Write)
cs.Write(bytes, 0, bytes.Length)
'cs.Close()
bytes = ms.ToArray()
End Using
End Using
Dim data64 As String = Convert.ToBase64String(bytes)
Dim payload As Byte() = Encoding.UTF8.GetBytes($"data={data64}||lpv={Version}||")
Using md5 As MD5 = MD5.Create()
Using ms = New MemoryStream()
ms.Write(payload, 0, payload.Length)
'ms.Write(aes.Key, 0, aes.Key.Length)
ms.Write(Key, 0, Key.Length)
Dim md5s As String = ArrayToHex(md5.ComputeHash(ms.ToArray())).Substring(8, 16)
Return Encoding.UTF8.GetBytes($"{Version}{md5s}{data64}")
End Using
End Using
End Function
Protected Async Function Send(ByVal data As Byte()) As Task(Of Byte())
Dim tries As Integer = 2
Dim lastException As Exception = Nothing
While Math.Max(System.Threading.Interlocked.Decrement(tries), tries + 1) > 0
Try
Using client = New TcpClient()
Await client.ConnectAsync(IP, Port)
Using stream = client.GetStream()
Using ms = New MemoryStream()
Dim buffer As Byte() = New Byte(1023) {}
Await stream.WriteAsync(data, 0, data.Length)
Dim bytes As Integer = Await stream.ReadAsync(buffer, 0, buffer.Length)
'stream.Close()
ms.Write(buffer, 0, bytes)
Return ms.ToArray()
End Using
End Using
End Using
Catch ex As IOException
lastException = ex
End Try
Await Task.Delay(500)
End While
Throw lastException
End Function
Protected Function ReadBuffer(ByVal data As Byte()) As String
Dim spec = New With {
.prefix = New Byte() {0, 0, 85, 170, 0, 0, 0, 0, 0, 0, 0},
.suffix = New Byte() {0, 0, 170, 85}
}
If data.Length < 24 OrElse Not data.Take(11).SequenceEqual(spec.prefix) Then
Throw New Exception("invalid read buffer/prefix")
End If
Dim length As Integer = BitConverter.ToInt32(data.Skip(12).Take(4).Reverse().ToArray(), 0)
If data.Length <> 16 + length Then
Throw New Exception("invalid read buffer length")
End If
Dim payload As String = Encoding.UTF8.GetString(data.Skip(20).Take(length - 12).ToArray())
If Not data.Skip(16 + length - 4).Take(4).SequenceEqual(spec.suffix) Then
Throw New Exception("invalid read buffer/suffix")
End If
Return payload
End Function
Protected Function ConstructBuffer(ByVal data As Byte(), ByVal command As Byte) As Byte()
Dim spec = New With {
.prefix = New Byte() {0, 0, 85, 170, 0, 0, 0, 0, 0, 0, 0},
.suffix = New Byte() {0, 0, 0, 0, 0, 0, 170, 85}
}
Dim dataLength As Byte() = BitConverter.GetBytes(data.Length + spec.suffix.Length)
If BitConverter.IsLittleEndian Then Array.Reverse(dataLength)
Using ms = New MemoryStream()
ms.Write(spec.prefix, 0, spec.prefix.Length)
ms.Write(New Byte() {command}, 0, 1)
ms.Write(dataLength, 0, 4)
ms.Write(data, 0, data.Length)
ms.Write(spec.suffix, 0, spec.suffix.Length)
Return ms.ToArray()
End Using
End Function
Private Shared Function ArrayToHex(ByVal arr As Byte()) As String
Return BitConverter.ToString(arr).Replace("-", String.Empty).ToLower()
End Function
Private Shared Function HexToArray(ByVal hex As String) As Byte()
Return Enumerable.Range(0, hex.Length / 2).[Select](Function(x) Convert.ToByte(hex.Substring(x * 2, 2), 16)).ToArray()
End Function
Public Structure TuyaStatus
Public Powered As Boolean
Public Delay As Integer
Public Current_mA As Double
Public Power_W As Double
Public Voltage_V As Double
End Structure
End Class
Public Class TuyaPlug
Inherits TuyaDevice
Public Async Function GetStatus() As Task(Of TuyaStatus)
Try
Dim cmd As New Dictionary(Of String, String)
cmd.Add("gwId", LocalKey)
cmd.Add("devId", Id)
Dim json As String = JsonConvert.SerializeObject(cmd)
Dim payload = CreatePayload(json, False)
Dim buffer = ConstructBuffer(payload, 10)
json = ReadBuffer(Await Send(buffer))
Dim tuyaStatus As TuyaStatus = New TuyaStatus()
Dim result As JObject = JObject.Parse(json)
If result("dps") IsNot Nothing Then
If result("dps")("1") IsNot Nothing Then tuyaStatus.Powered = CBool(result("dps")("1").ToObject(GetType(Boolean)))
If result("dps")("2") IsNot Nothing Then tuyaStatus.Delay = CInt(result("dps")("2").ToObject(GetType(Integer)))
If result("dps")("4") IsNot Nothing Then tuyaStatus.Current_mA = CDbl(result("dps")("4").ToObject(GetType(Double)))
If result("dps")("5") IsNot Nothing Then tuyaStatus.Power_W = CDbl(result("dps")("5").ToObject(GetType(Double)))
If result("dps")("6") IsNot Nothing Then tuyaStatus.Voltage_V = CDbl(result("dps")("6").ToObject(GetType(Double))) / 10.0
If result("dps")("18") IsNot Nothing Then tuyaStatus.Current_mA = CDbl(result("dps")("18").ToObject(GetType(Double)))
If result("dps")("19") IsNot Nothing Then tuyaStatus.Power_W = CDbl(result("dps")("19").ToObject(GetType(Double))) / 10.0
If result("dps")("20") IsNot Nothing Then tuyaStatus.Voltage_V = CDbl(result("dps")("20").ToObject(GetType(Double))) / 10.0
End If
Return (tuyaStatus)
Catch ex As Exception
'Throw New Exception($"Error reading buffer: {json}", ex)
End Try
End Function
Public Async Function SetStatus(ByVal status As Boolean, ByVal Optional delay As Integer = 0) As Task
Dim dps As New Dictionary(Of String, Object)
dps.Add("1", status)
dps.Add("2", delay)
Dim cmd As New Dictionary(Of String, Object)
cmd.Add("t", (DateTime.Now - New DateTime(1970, 1, 1)).TotalSeconds.ToString("0"))
cmd.Add("devId", Id)
cmd.Add("dps", dps)
cmd.Add("uid", "")
Dim json As String = JsonConvert.SerializeObject(cmd)
Dim payload = CreatePayload(json)
Dim buffer = ConstructBuffer(payload, 7)
Dim result = ReadBuffer(Await Send(buffer))
If result <> "" Then
Throw New Exception($"Unexpected result: {result}")
End If
End Function
End Class
End Namespace
Raspberry Pi should be able to run .Net Core, check out https://github.com/dotnet/core/blob/master/samples/RaspberryPiInstructions.md