From d6dfcb14a9ef2925ee4192ed64ff58474b75d9c9 Mon Sep 17 00:00:00 2001 From: Parnic Date: Wed, 20 Feb 2019 15:19:42 -0600 Subject: [PATCH] Added support for remote connection by name via Pentair servers This brings along proper password encryption and validation for the login process (which is still not needed for local connections). You can run the remote connection tests by passing a system name and password as 2 arguments (so the system name needs to be quoted, e.g.: `test "Pentair: xx-xx-xx" 1234`) Also updated to latest .NET Core and C# versions while I was at it. --- EasyTouchUnit.cs | 15 +++++ FindUnits.cs | 2 +- Messages/GetGatewayData.cs | 69 +++++++++++++++++++++++ RemoteConnect.cs | 22 ++++++++ ScreenLogicConnect.csproj | 3 +- Test/Program.cs | 112 +++++++++++++++++++++++++------------ Test/Test.csproj | 2 +- UnitConnection.cs | 42 ++++++++------ 8 files changed, 211 insertions(+), 56 deletions(-) create mode 100644 Messages/GetGatewayData.cs create mode 100644 RemoteConnect.cs diff --git a/EasyTouchUnit.cs b/EasyTouchUnit.cs index aa4ca0d..4ef8629 100644 --- a/EasyTouchUnit.cs +++ b/EasyTouchUnit.cs @@ -45,5 +45,20 @@ namespace ScreenLogicConnect System.Diagnostics.Debug.WriteLine(e.StackTrace); } } + + public EasyTouchUnit(Messages.GetGatewayData data) + { + try + { + gatewayName = data.GatewayName; + ipAddress = IPAddress.Parse(data.IPAddr); + port = data.Port; + isValid = data.GatewayFound && data.PortOpen; + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.StackTrace); + } + } } } diff --git a/FindUnits.cs b/FindUnits.cs index b6ce493..483adca 100644 --- a/FindUnits.cs +++ b/FindUnits.cs @@ -30,7 +30,7 @@ namespace ScreenLogicConnect await udpClient.SendAsync(broadcastData, broadcastData.Length, new IPEndPoint(IPAddress.Broadcast, multicastPort)); var buf = await udpClient.ReceiveAsync().TimeoutAfter(TimeSpan.FromSeconds(1)); - if (buf != null) + if (buf != null && buf.RemoteEndPoint != null) { var findServerResponse = new EasyTouchUnit(buf); if (findServerResponse.isValid) diff --git a/Messages/GetGatewayData.cs b/Messages/GetGatewayData.cs new file mode 100644 index 0000000..132f044 --- /dev/null +++ b/Messages/GetGatewayData.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ScreenLogicConnect.Messages +{ + public class GetGatewayData : HLMessage + { + public string GatewayName; + public bool GatewayFound; + public bool LicenseOK; + public string IPAddr; + public short Port; + public bool PortOpen; + public bool RelayOn; + + public const short HLM_GETGATEWAYDATA = 18003; + + public static GetGatewayData QUERY(string systemName, short senderID = 0) + { + var ret = new GetGatewayData(senderID, HLM_GETGATEWAYDATA); + ret.GatewayName = systemName; + return ret; + } + + public GetGatewayData(short senderID, short msgID) + : base(senderID, msgID) + { + } + + public GetGatewayData(HLMessage msg) + : base(msg) + { + } + + public override byte[] asByteArray() + { + using (var ms = new MemoryStream()) + { + using (var bw = new BinaryWriter(ms)) + { + bw.WritePrefixLength(GatewayName); + bw.WritePrefixLength(GatewayName); + } + + data = ms.ToArray(); + } + + return base.asByteArray(); + } + + protected override void decode() + { + using (var ms = new MemoryStream(data)) + { + using (var br = new BinaryReader(ms)) + { + GatewayFound = br.ReadBoolean(); + LicenseOK = br.ReadBoolean(); + IPAddr = HLMessageTypeHelper.extractString(br); + Port = br.ReadInt16(); + PortOpen = br.ReadBoolean(); + RelayOn = br.ReadBoolean(); + } + } + } + } +} diff --git a/RemoteConnect.cs b/RemoteConnect.cs new file mode 100644 index 0000000..7c29088 --- /dev/null +++ b/RemoteConnect.cs @@ -0,0 +1,22 @@ +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace ScreenLogicConnect +{ + public class RemoteConnect + { + public const int ServerDispatcherPort = 500; + public const string ServerDispatcherURL = "screenlogicserver.pentair.com"; + + public static async Task GetGatewayInfo(string systemName) + { + using (var client = new TcpClient()) + { + client.Connect(ServerDispatcherURL, ServerDispatcherPort); + var ns = client.GetStream(); + ns.SendHLMessage(Messages.GetGatewayData.QUERY(systemName)); + return new EasyTouchUnit(new Messages.GetGatewayData(await UnitConnection.GetMessage(ns))); + } + } + } +} diff --git a/ScreenLogicConnect.csproj b/ScreenLogicConnect.csproj index 7c8f610..a9d3c4c 100644 --- a/ScreenLogicConnect.csproj +++ b/ScreenLogicConnect.csproj @@ -1,7 +1,8 @@ - netcoreapp2.0 + netcoreapp2.2 + latest diff --git a/Test/Program.cs b/Test/Program.cs index 12b6be3..d3dfd01 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; namespace Test @@ -6,53 +7,90 @@ namespace Test class Program { static async Task Main(string[] args) + { + if (args.Length >= 2) + { + await DoRemoteConnect(args[0], args[1]); + } + else + { + await DoLocalConnect(); + } + } + + static async Task DoLocalConnect() { var servers = await ScreenLogicConnect.FindUnits.Find(); if (servers != null) { foreach (var server in servers) { - var connection = new ScreenLogicConnect.UnitConnection(); - await connection.ConnectTo(server); - var status = connection.GetPoolStatus(); - var config = connection.GetControllerConfig(); - var degSymbol = config.m_DegC == 1 ? "C" : "F"; - Console.WriteLine($"Air temp: {status.m_AirTemp} degrees {degSymbol}"); - var currTempList = status.m_CurrentTemp; - int poolTemp = 0; - int spaTemp = 0; - if (currTempList.Length > 0) + if (server.isValid) { - poolTemp = currTempList[0]; + await ConnectToUnit(server); + break; } - if (currTempList.Length > 1) - { - spaTemp = currTempList[1]; - } - if (poolTemp != 0) - { - Console.WriteLine($"Pool temp: {poolTemp} degrees {degSymbol}{(status.isPoolActive() ? "" : " (Last)")}"); - } - else - { - Console.WriteLine("Couldn't get pool temperature."); - } - if (spaTemp != 0) - { - Console.WriteLine($"Spa temp: {spaTemp} degrees {degSymbol}{(status.isSpaActive() ? "" : " (Last)")}"); - } - else - { - Console.WriteLine("Couldn't get spa temperature."); - } - Console.WriteLine($"ORP: {status.m_ORP}"); - Console.WriteLine($"pH: {status.m_PH / 100.0f:0.00}"); - Console.WriteLine($"Salt: {status.m_SaltPPM * 50} PPM"); - Console.WriteLine($"Saturation: {status.m_Saturation / 100.0f}"); - - break; } } + + if (servers == null || servers.Count == 0 || !servers.Any(x => x.isValid)) + { + Console.WriteLine("No local units found."); + } + } + + static async Task DoRemoteConnect(string systemName, string systemPassword) + { + var unit = await ScreenLogicConnect.RemoteConnect.GetGatewayInfo(systemName); + if (unit.isValid) + { + await ConnectToUnit(unit, systemPassword); + } + else + { + Console.WriteLine("Unable to connect to remote unit."); + } + } + + static async Task ConnectToUnit(ScreenLogicConnect.EasyTouchUnit server, string systemPassword = null) + { + var connection = new ScreenLogicConnect.UnitConnection(); + await connection.ConnectTo(server, systemPassword); + var status = await connection.GetPoolStatus(); + var config = await connection.GetControllerConfig(); + var degSymbol = config.m_DegC == 1 ? "C" : "F"; + Console.WriteLine($"Air temp: {status.m_AirTemp} degrees {degSymbol}"); + var currTempList = status.m_CurrentTemp; + int poolTemp = 0; + int spaTemp = 0; + if (currTempList.Length > 0) + { + poolTemp = currTempList[0]; + } + if (currTempList.Length > 1) + { + spaTemp = currTempList[1]; + } + if (poolTemp != 0) + { + Console.WriteLine($"Pool temp: {poolTemp} degrees {degSymbol}{(status.isPoolActive() ? "" : " (Last)")}"); + } + else + { + Console.WriteLine("Couldn't get pool temperature."); + } + if (spaTemp != 0) + { + Console.WriteLine($"Spa temp: {spaTemp} degrees {degSymbol}{(status.isSpaActive() ? "" : " (Last)")}"); + } + else + { + Console.WriteLine("Couldn't get spa temperature."); + } + Console.WriteLine($"ORP: {status.m_ORP}"); + Console.WriteLine($"pH: {status.m_PH / 100.0f:0.00}"); + Console.WriteLine($"Salt: {status.m_SaltPPM * 50} PPM"); + Console.WriteLine($"Saturation: {status.m_Saturation / 100.0f}"); } } } diff --git a/Test/Test.csproj b/Test/Test.csproj index 0481c66..b5a6d13 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp2.2 diff --git a/UnitConnection.cs b/UnitConnection.cs index cbb6b50..17fce2a 100644 --- a/UnitConnection.cs +++ b/UnitConnection.cs @@ -11,7 +11,7 @@ namespace ScreenLogicConnect { TcpClient client; - public async Task ConnectTo(EasyTouchUnit unit) + public async Task ConnectTo(EasyTouchUnit unit, string password = null) { if (client != null) { @@ -31,36 +31,45 @@ namespace ScreenLogicConnect var recvBuf = new byte[1024]; var readBytes = stream.Read(recvBuf, 0, recvBuf.Length); Debug.WriteLine("read {0}", readBytes); + string challengeStr = null; + using (var ms = new MemoryStream(recvBuf)) + { + using (var br = new BinaryReader(ms)) + { + br.ReadBytes(8); + challengeStr = Messages.HLMessageTypeHelper.extractString(br); + } + } Debug.WriteLine("sending login message"); - stream.SendHLMessage(createLoginMessage(new byte[16])); + stream.SendHLMessage(createLoginMessage(new HLEncoder(password).GetEncryptedPassword(challengeStr))); readBytes = stream.Read(recvBuf, 0, recvBuf.Length); Debug.WriteLine("read {0}", readBytes); } - public Messages.GetPoolStatus GetPoolStatus() + public async Task GetPoolStatus() { Debug.WriteLine("sending status message"); client.GetStream().SendHLMessage(Messages.GetPoolStatus.QUERY(0)); - return new Messages.GetPoolStatus(getMessage(client.GetStream())); + return new Messages.GetPoolStatus(await GetMessage(client.GetStream())); } - public Messages.GetControllerConfig GetControllerConfig() + public async Task GetControllerConfig() { Debug.WriteLine("sending controller config message"); client.GetStream().SendHLMessage(Messages.GetControllerConfig.QUERY(0)); - return new Messages.GetControllerConfig(getMessage(client.GetStream())); + return new Messages.GetControllerConfig(await GetMessage(client.GetStream())); } - public Messages.GetMode GetMode() + public async Task GetMode() { Debug.WriteLine("sending get-mode message"); client.GetStream().SendHLMessage(Messages.GetMode.QUERY(0)); - return new Messages.GetMode(getMessage(client.GetStream())); + return new Messages.GetMode(await GetMessage(client.GetStream())); } - private static Messages.HLMessage getMessage(NetworkStream ns) + public static async Task GetMessage(NetworkStream ns) { int bytesRead = 0; byte[] headerBuffer = new byte[8]; @@ -68,7 +77,7 @@ namespace ScreenLogicConnect { try { - bytesRead += ns.Read(headerBuffer, bytesRead, 8 - bytesRead); + bytesRead += await ns.ReadAsync(headerBuffer, bytesRead, 8 - bytesRead); if (bytesRead < 0) { return null; @@ -87,7 +96,7 @@ namespace ScreenLogicConnect bytesRead = 0; while (bytesRead < msgDataSize) { - bytesRead += ns.Read((byte[])(Array)dataBuffer, bytesRead, msgDataSize - bytesRead); + bytesRead += await ns.ReadAsync((byte[])(Array)dataBuffer, bytesRead, msgDataSize - bytesRead); if (bytesRead < 0) { @@ -100,7 +109,7 @@ namespace ScreenLogicConnect return new Messages.HLMessage(headerBuffer, dataBuffer); } - private static string connectionMessage = "CONNECTSERVERHOST"; + private static string connectionMessage = "CONNECTSERVERHOST\r\n\r\n"; private byte[] CreateConnectServerSoftMessage() { using (var ms = new MemoryStream()) @@ -108,10 +117,6 @@ namespace ScreenLogicConnect using (var bw = new BinaryWriter(ms)) { bw.Write(Encoding.ASCII.GetBytes(connectionMessage)); - bw.Write((byte)'\r'); - bw.Write((byte)'\n'); - bw.Write((byte)'\r'); - bw.Write((byte)'\n'); } return ms.ToArray(); @@ -126,6 +131,11 @@ namespace ScreenLogicConnect login.m_version = "ScreenLogicConnect library"; login.m_procID = 2; + if (encodedPwd == null) + { + encodedPwd = new byte[16]; + } + if (encodedPwd.Length > 16) { login.m_byteArray = new byte[16];