diff --git a/Brematic.Net.sln b/Brematic.Net.sln new file mode 100644 index 0000000..779d322 --- /dev/null +++ b/Brematic.Net.sln @@ -0,0 +1,45 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2041 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Brematic.Net", "src\Brematic.Net\Brematic.Net.csproj", "{AB55CB3E-977B-4101-9419-13F866F8D6D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C4A1C06F-9D6D-40C5-95A2-18D2B129618B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Brematic.Net.Tests", "tests\Brematic.Net.Tests\Brematic.Net.Tests.csproj", "{65E659DB-34CD-4961-990F-2463DE6B096B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{CC115A2C-A9E5-44AC-8002-8E0E5309C130}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Brematic.Net.ConsoleSample", "samples\Brematic.Net.ConsoleSample\Brematic.Net.ConsoleSample.csproj", "{D5A85DD6-403B-4192-832C-C091D7E99BB9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AB55CB3E-977B-4101-9419-13F866F8D6D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB55CB3E-977B-4101-9419-13F866F8D6D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB55CB3E-977B-4101-9419-13F866F8D6D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB55CB3E-977B-4101-9419-13F866F8D6D2}.Release|Any CPU.Build.0 = Release|Any CPU + {65E659DB-34CD-4961-990F-2463DE6B096B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65E659DB-34CD-4961-990F-2463DE6B096B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65E659DB-34CD-4961-990F-2463DE6B096B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65E659DB-34CD-4961-990F-2463DE6B096B}.Release|Any CPU.Build.0 = Release|Any CPU + {D5A85DD6-403B-4192-832C-C091D7E99BB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5A85DD6-403B-4192-832C-C091D7E99BB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5A85DD6-403B-4192-832C-C091D7E99BB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5A85DD6-403B-4192-832C-C091D7E99BB9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {65E659DB-34CD-4961-990F-2463DE6B096B} = {C4A1C06F-9D6D-40C5-95A2-18D2B129618B} + {D5A85DD6-403B-4192-832C-C091D7E99BB9} = {CC115A2C-A9E5-44AC-8002-8E0E5309C130} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {80F2A93D-9921-4E99-984F-B43C220A64A5} + EndGlobalSection +EndGlobal diff --git a/samples/Brematic.Net.ConsoleSample/Brematic.Net.ConsoleSample.csproj b/samples/Brematic.Net.ConsoleSample/Brematic.Net.ConsoleSample.csproj new file mode 100644 index 0000000..23e0012 --- /dev/null +++ b/samples/Brematic.Net.ConsoleSample/Brematic.Net.ConsoleSample.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/samples/Brematic.Net.ConsoleSample/Program.cs b/samples/Brematic.Net.ConsoleSample/Program.cs new file mode 100644 index 0000000..7940918 --- /dev/null +++ b/samples/Brematic.Net.ConsoleSample/Program.cs @@ -0,0 +1,21 @@ +using Brematic.Net.Devices; +using Brematic.Net.Devices.Brennenstuhl; +using Brematic.Net.Gateways; + +namespace Brematic.Net.ConsoleSample +{ + internal class Program + { + private static void Main(string[] args) + { + var systemCode = "01010"; + var unitCode = "00010"; + + var device = new RCS1000N(systemCode, unitCode); + + var gateway = new BrennenstuhlGateway("192.168.178.213"); + + gateway.SendRequest(device, DeviceAction.On); + } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Brematic.Net.csproj b/src/Brematic.Net/Brematic.Net.csproj new file mode 100644 index 0000000..d9baf23 --- /dev/null +++ b/src/Brematic.Net/Brematic.Net.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + Tobias Efinger + 0.0.1 + https://github.com/tefinger/Brematic.Net + https://github.com/tefinger/Brematic.Net + brematic intertechno brennenstuhl smarthome + + + diff --git a/src/Brematic.Net/Devices/Brennenstuhl/RCR1000N.cs b/src/Brematic.Net/Devices/Brennenstuhl/RCR1000N.cs new file mode 100644 index 0000000..5833b1c --- /dev/null +++ b/src/Brematic.Net/Devices/Brennenstuhl/RCR1000N.cs @@ -0,0 +1,7 @@ +namespace Brematic.Net.Devices.Brennenstuhl +{ + public class RCR1000N : RCS1000N + { + public RCR1000N(string systemCode, string unitCode) : base(systemCode, unitCode) { } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Devices/Brennenstuhl/RCS1000N.cs b/src/Brematic.Net/Devices/Brennenstuhl/RCS1000N.cs new file mode 100644 index 0000000..1afa11f --- /dev/null +++ b/src/Brematic.Net/Devices/Brennenstuhl/RCS1000N.cs @@ -0,0 +1,83 @@ +using Brematic.Net.Exceptions; +using Brematic.Net.Gateways; + +namespace Brematic.Net.Devices.Brennenstuhl +{ + public class RCS1000N : Device + { + private const int Repeat = 10; + private const int PauseBS = 5600; + private const int PauseIT = 11200; + private const int Tune = 350; + private const int BaudBS = 25; + private const int BaudIT = 26; + private const int SpeedBS = 16; + private const int SpeedIT = 32; + private const int TxVersion = 1; + + private const string Low = "1,"; + private const string High = "3,"; + + private const string SeqLow = Low + High + Low + High; + private const string SeqHigh = Low + High + High + Low; + + private const string Additional = SeqLow + SeqHigh; + private const string On = SeqLow + SeqHigh; + private const string Off = SeqHigh + SeqLow; + + public RCS1000N(string systemCode, string unitCode) : base(systemCode, unitCode) { } + + private string HeadBSGW => $"TXP:0,0,{Repeat},{PauseBS},{Tune},{BaudBS},"; + private string TailBSGW => $"{TxVersion},{SpeedBS};"; + private string HeadITGW => $"0,0,{Repeat},{PauseIT},{Tune},{BaudIT},0,"; + private string TailITGW => $"{TxVersion},{SpeedIT},0"; + + private string Encode(string code) + { + var encodedMsg = ""; + foreach (var c in code) + switch (c) + { + case '0': + encodedMsg += SeqHigh; + break; + case '1': + encodedMsg += SeqLow; + break; + default: + throw new InvalidCodeException(); + } + + return encodedMsg; + } + + public override string GetSignal(Gateway gateway, DeviceAction action) + { + var systemMsg = Encode(SystemCode); + var unitMsg = Encode(UnitCode); + + string head; + string tail; + + if (gateway.GetType() == typeof(BrennenstuhlGateway)) + { + head = HeadBSGW; + tail = TailBSGW; + } + else if (gateway.GetType() == typeof(IntertechnoGateway)) + { + head = HeadITGW; + tail = TailITGW; + } + else + { + throw new UnsupportedGatewayException(); + } + + var actionValue = action == DeviceAction.On ? On : Off; + var data = head + systemMsg + unitMsg + actionValue + tail; + + return data; + } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Devices/Device.cs b/src/Brematic.Net/Devices/Device.cs new file mode 100644 index 0000000..e4a8e6f --- /dev/null +++ b/src/Brematic.Net/Devices/Device.cs @@ -0,0 +1,19 @@ +using Brematic.Net.Gateways; + +namespace Brematic.Net.Devices +{ + public abstract class Device + { + protected Device(string systemCode, string unitCode) + { + SystemCode = systemCode; + UnitCode = unitCode; + } + + protected string SystemCode { get; } + + protected string UnitCode { get; } + + public abstract string GetSignal(Gateway gateway, DeviceAction action); + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Devices/DeviceAction.cs b/src/Brematic.Net/Devices/DeviceAction.cs new file mode 100644 index 0000000..b4413e2 --- /dev/null +++ b/src/Brematic.Net/Devices/DeviceAction.cs @@ -0,0 +1,8 @@ +namespace Brematic.Net.Devices +{ + public enum DeviceAction + { + On, + Off + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Exceptions/InvalidCodeException.cs b/src/Brematic.Net/Exceptions/InvalidCodeException.cs new file mode 100644 index 0000000..e51c4a4 --- /dev/null +++ b/src/Brematic.Net/Exceptions/InvalidCodeException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Brematic.Net.Exceptions +{ + public class InvalidCodeException : Exception + { + public InvalidCodeException() : base("Invalid value in system or unit code!") { } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Exceptions/UnsupportedGatewayException.cs b/src/Brematic.Net/Exceptions/UnsupportedGatewayException.cs new file mode 100644 index 0000000..72bf489 --- /dev/null +++ b/src/Brematic.Net/Exceptions/UnsupportedGatewayException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Brematic.Net.Exceptions +{ + public class UnsupportedGatewayException : Exception + { + public UnsupportedGatewayException() : base("Unsupported gateway") { } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Gateways/BrennenstuhlGateway.cs b/src/Brematic.Net/Gateways/BrennenstuhlGateway.cs new file mode 100644 index 0000000..7932025 --- /dev/null +++ b/src/Brematic.Net/Gateways/BrennenstuhlGateway.cs @@ -0,0 +1,11 @@ +using Brematic.Net.Devices; + +namespace Brematic.Net.Gateways +{ + public class BrennenstuhlGateway : Gateway + { + public BrennenstuhlGateway(string ipAddress) : this(ipAddress, 49880) { } + + public BrennenstuhlGateway(string ipAddress, int port) : base(ipAddress, port) { } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Gateways/Gateway.cs b/src/Brematic.Net/Gateways/Gateway.cs new file mode 100644 index 0000000..77d78db --- /dev/null +++ b/src/Brematic.Net/Gateways/Gateway.cs @@ -0,0 +1,51 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using Brematic.Net.Devices; + +namespace Brematic.Net.Gateways +{ + public abstract class Gateway + { + protected Gateway(string ipAddress, int port) + { + IPAddress = ipAddress; + Port = port; + } + + private string IPAddress { get; } + + private int Port { get; } + + public void SendRequest(Device device, DeviceAction action) + { + var data = device.GetSignal(this, action); + SendDataToSocket(data); + } + + private Socket ConnectSocket() + { + var hostEntry = Dns.GetHostEntry(IPAddress); + + foreach (var address in hostEntry.AddressList) + { + var ipEndPoint = new IPEndPoint(address, Port); + var tempSocket = new Socket(SocketType.Dgram, ProtocolType.Udp); + tempSocket.Connect(ipEndPoint); + + if (tempSocket.Connected) return tempSocket; + } + + return null; + } + + protected void SendDataToSocket(string data) + { + var byteData = Encoding.UTF8.GetBytes(data); + using (var socket = ConnectSocket()) + { + socket.Send(byteData); + } + } + } +} \ No newline at end of file diff --git a/src/Brematic.Net/Gateways/IntertechnoGateway.cs b/src/Brematic.Net/Gateways/IntertechnoGateway.cs new file mode 100644 index 0000000..ea8dc81 --- /dev/null +++ b/src/Brematic.Net/Gateways/IntertechnoGateway.cs @@ -0,0 +1,12 @@ +using System; +using Brematic.Net.Devices; + +namespace Brematic.Net.Gateways +{ + public class IntertechnoGateway : Gateway + { + public IntertechnoGateway(string ipAddress) : this(ipAddress, 49880) { } + + public IntertechnoGateway(string ipAddress, int port) : base(ipAddress, port) { } + } +} \ No newline at end of file diff --git a/tests/Brematic.Net.Tests/Brematic.Net.Tests.csproj b/tests/Brematic.Net.Tests/Brematic.Net.Tests.csproj new file mode 100644 index 0000000..b7e2fc4 --- /dev/null +++ b/tests/Brematic.Net.Tests/Brematic.Net.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1;net471 + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/tests/Brematic.Net.Tests/Devices/Brennenstuhl/BrennenstuhlTests.cs b/tests/Brematic.Net.Tests/Devices/Brennenstuhl/BrennenstuhlTests.cs new file mode 100644 index 0000000..74e7f24 --- /dev/null +++ b/tests/Brematic.Net.Tests/Devices/Brennenstuhl/BrennenstuhlTests.cs @@ -0,0 +1,63 @@ +using Brematic.Net.Devices; +using Brematic.Net.Devices.Brennenstuhl; +using Brematic.Net.Gateways; +using Xunit; + +namespace Brematic.Net.Tests.Devices.Brennenstuhl +{ + public class BrennenstuhlTests + { + private readonly BrennenstuhlGateway _brennenstuhlGateway; + private readonly IntertechnoGateway _intertechnoGateway; + + public BrennenstuhlTests() + { + _brennenstuhlGateway = new BrennenstuhlGateway("192.168.178.213"); + _intertechnoGateway = new IntertechnoGateway("192.68.178.213"); + } + + [Theory] + [InlineData(DeviceAction.On, + "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;", + "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" + )] + [InlineData(DeviceAction.Off, + "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;", + "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0")] + public void RCS1000N_Test(DeviceAction action, string expectedResultBS, string expectedResultIT) + { + // given + var device = new RCS1000N("10000", "00100"); + + // when + var resultBS = device.GetSignal(_brennenstuhlGateway, action); + var resultIT = device.GetSignal(_intertechnoGateway, action); + + // then + Assert.Equal(expectedResultBS, resultBS); + Assert.Equal(expectedResultIT, resultIT); + } + + [Theory] + [InlineData(DeviceAction.On, + "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;", + "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" + )] + [InlineData(DeviceAction.Off, + "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;", + "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0")] + public void RCR1000N_Test(DeviceAction action, string expectedResultBS, string expectedResultIT) + { + // given + var device = new RCR1000N("10000", "00100"); + + // when + var resultBS = device.GetSignal(_brennenstuhlGateway, action); + var resultIT = device.GetSignal(_intertechnoGateway, action); + + // then + Assert.Equal(expectedResultBS, resultBS); + Assert.Equal(expectedResultIT, resultIT); + } + } +} \ No newline at end of file