diff --git a/.gitignore b/.gitignore index 34c17c7..1bbf276 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,35 @@ /Mqtt-Dashboard/Mqtt-Dashboard/bin/Release/Newtonsoft.Json.xml /Mqtt-Dashboard/Mqtt-Dashboard/bin/Release/Utils-IoT.dll.config /Mqtt-Dashboard/Mqtt-Dashboard/bin/Release/Utils-IoT.pdb +/Zway/.vs/Zway/v15 +/Zway/Zway/obj/Debug +/Zway/Zway/bin/Debug +/IoT-Bot/.vs/config +/Zway-Bot/.vs +/Zway-Bot/packages +/Zway-Bot/Zway-Bot/obj +/Zway-Bot/Zway-Bot/bin/Debug +/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMosquitto.pdb +/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMqtt.pdb +/Zway-Bot/Zway-Bot/bin/Release/M2Mqtt.Net.pdb +/Zway-Bot/Zway-Bot/bin/Release/Utils-IoT.pdb +/Zway-Bot/Zway-Bot/bin/Release/Utils.pdb +/Zway-Bot/Zway-Bot/bin/Release/Zway-Bot.pdb +/Zway-Bot/Zway-Bot/bin/Release/Zway.pdb +/Zway/Zway/bin/Release/Zway.pdb +/Zway/Zway/obj/Release +/Utils/IoT/Connector/User/Telegram/obj/Release +/Utils/IoT/Connector/User/Telegram/bin/Release/ConnectorUserTelegram.dll.config +/Utils/IoT/Connector/User/Telegram/bin/Release/ConnectorUserTelegram.pdb +/Utils/IoT/Connector/User/Telegram/bin/Release/Newtonsoft.Json.xml +/Utils/IoT/Connector/User/Telegram/bin/Release/System.Diagnostics.DiagnosticSource.xml +/Utils/IoT/Connector/User/Telegram/bin/Release/Utils-IoT.pdb +/Utils/IoT/Connector/Data/Mqtt/obj +/Utils/IoT/Connector/Data/Mqtt/bin/Debug +/Utils/IoT/Connector/Data/Mqtt/bin/Release/ConnectorDataMqtt.pdb +/Utils/IoT/Connector/Data/Mqtt/bin/Release/M2Mqtt.Net.pdb +/Utils/IoT/Connector/Data/Mqtt/bin/Release/Utils-IoT.pdb +/Utils/IoT/Connector/Data/Mosquitto/obj +/Utils/IoT/Connector/Data/Mosquitto/bin/Debug +/Utils/IoT/Connector/Data/Mosquitto/bin/Release/ConnectorDataMosquitto.pdb +/Utils/IoT/Connector/Data/Mosquitto/bin/Release/Utils-IoT.pdb diff --git a/IoT-Bot/IoT-Bot.sln b/IoT-Bot/IoT-Bot.sln index 0c2eb85..3eb7bd9 100644 --- a/IoT-Bot/IoT-Bot.sln +++ b/IoT-Bot/IoT-Bot.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils-IoT", "..\Utils\IoT\U EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "..\Utils\Utils\Utils.csproj", "{FAC8CE64-BF13-4ECE-8097-AEB5DD060098}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zway", "..\Zway\Zway\Zway.csproj", "{166258ED-CB3D-43F5-8E8D-3A993B64D022}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Release|Any CPU.Build.0 = Release|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.Build.0 = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.ActiveCfg = Release|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/IoT-Bot/IoT-Bot/Condition/ACondition.cs b/IoT-Bot/IoT-Bot/Condition/ACondition.cs index ca83a93..7262316 100644 --- a/IoT-Bot/IoT-Bot/Condition/ACondition.cs +++ b/IoT-Bot/IoT-Bot/Condition/ACondition.cs @@ -7,20 +7,68 @@ namespace IoTBot.Condition { abstract class ACondition { protected ASensor sensor; protected Dictionary settings; + protected String name; protected ADataBackend data; protected AUserBackend user; protected ACondition(String name, Dictionary settings, ASensor sensor, ADataBackend data, AUserBackend user) { this.settings = settings; + this.name = name; this.data = data; this.user = user; this.sensor = sensor; } public void Attach() { - this.sensor.Update += this.Sensor_Update; + if(this.sensor != null) { + this.sensor.Update += this.Sensor_Update; + } + switch (this.settings["source"].ToLower()) { + case "user": + if(this.user != null) { + this.user.MessageIncomming += this.User_Update; + } + break; + case "data": + if(this.data != null) { + this.data.MessageIncomming += this.Data_Update; + } + break; + case "both": + if (this.user != null) { + this.user.MessageIncomming += this.User_Update; + } + if (this.data != null) { + this.data.MessageIncomming += this.Data_Update; + } + break; + } } + protected void Send(String message = "", String topic = "") { + if(message == "") { + message = this.settings["target_message"]; + } + if(topic == "") { + topic = this.settings["target_topic"]; + } + switch (this.settings["target"].ToLower()) { + case "user": + this.user.Send(message); + break; + case "data": + this.data.Send(topic, message); + break; + case "sensor": + //this.sensor.Set(message); + break; + } + } + + protected abstract void Data_Update(Object sender, MqttEventArgs e); + + protected abstract void User_Update(Object sender, UserMessageEventArgs e); + protected abstract void Sensor_Update(Object sender, EventArgs e); public static ACondition GetInstance(String name, Dictionary settings, ASensor sensor, ADataBackend data, AUserBackend user) { diff --git a/IoT-Bot/IoT-Bot/Condition/Edge.cs b/IoT-Bot/IoT-Bot/Condition/Edge.cs index becced0..fc2e6db 100644 --- a/IoT-Bot/IoT-Bot/Condition/Edge.cs +++ b/IoT-Bot/IoT-Bot/Condition/Edge.cs @@ -9,6 +9,10 @@ namespace IoTBot.Condition { public Edge(String name, Dictionary settings, ASensor sensor, ADataBackend data, AUserBackend user) : base(name, settings, sensor, data, user) { } + protected override void Data_Update(Object sender, MqttEventArgs e) { + //throw new NotImplementedException(); + } + protected override void Sensor_Update(Object sender, EventArgs e) { if(this.sensor.Datatypes == ASensor.Types.Bool) { if(this.sensor.GetBool == Boolean.Parse(this.settings["sensor_value"]) && this.histBool != this.sensor.GetBool) { @@ -19,5 +23,9 @@ namespace IoTBot.Condition { } } } + + protected override void User_Update(Object sender, UserMessageEventArgs e) { + //throw new NotImplementedException(); + } } } diff --git a/IoT-Bot/IoT-Bot/Condition/TelegramBot.cs b/IoT-Bot/IoT-Bot/Condition/TelegramBot.cs new file mode 100644 index 0000000..57dd2ff --- /dev/null +++ b/IoT-Bot/IoT-Bot/Condition/TelegramBot.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using BlubbFish.IoT.Zway; +using BlubbFish.IoT.Zway.Devices.CommandClasses; +using BlubbFish.Utils.IoT.Connector; +using BlubbFish.Utils.IoT.Sensor; + +namespace IoTBot.Condition { + class Telegrambot : ACondition { + private ZwayController zw; + + public Telegrambot(String name, Dictionary settings, ASensor sensor, ADataBackend data, AUserBackend user) : base(name, settings, sensor, data, user) { + this.zw = new ZwayController("10.100.0.214", "admin", ""); + this.zw.Update += this.Zw_Update; + } + + private void Zw_Update(Object sender, BlubbFish.IoT.Zway.Events.DeviceUpdateEvent e) { + Console.WriteLine("-> ZW: "+sender.ToString()); + } + + protected override void Data_Update(Object sender, MqttEventArgs e) { + //throw new NotImplementedException(); + } + + protected override void Sensor_Update(Object sender, EventArgs e) { + //throw new NotImplementedException(); + } + + protected override void User_Update(Object sender, UserMessageEventArgs e) { + if(e.Message == "/start") { + this.Send("Hallo zurück! Ich kann aktuell die Befehle /schalter"); + } + if (e.Message.StartsWith("/schalter")) { + this.BotSchalter(e.Message); + } + } + + private void BotSchalter(String message) { + if (message == "/schalter") { + this.user.Send("Was soll ich tun?", new String[] { "/schalter einschalten", "/schalter ausschalten", "/schalter status" }); + } + if(message == "/schalter status") { + String ret = "Der Status des Schalters ist: "; + foreach (Switchbinary item in this.zw.GetCommandClasses()) { + ret += item.Name + ": " + (item.Level ? "An" : "Aus") + ", "; + } + this.user.Send(ret.Substring(0, ret.Length - 2)); + } + if (message == "/schalter einschalten") { + List s = new List(); + foreach (Switchbinary item in this.zw.GetCommandClasses()) { + if (!item.Level) { + s.Add("/schalter einschalten " + item.Name); + } + } + if (s.Count > 0) { + this.user.Send("Was soll ich tun?", s.ToArray()); + } else { + this.user.Send("Keine Geräte ausgeschaltet!"); + } + } else if(message.StartsWith("/schalter einschalten ")) { + String device = message.Substring(22); + foreach (Switchbinary item in this.zw.GetCommandClasses()) { + if(item.Name == device) { + item.Level = true; + this.user.Send("Einschalten von " + item.Name + " " + (item.Level ? "" : "nicht ") + "erfolgreich"); + } + } + } + if (message == "/schalter ausschalten") { + List s = new List(); + foreach (Switchbinary item in this.zw.GetCommandClasses()) { + if (item.Level) { + s.Add("/schalter ausschalten " + item.Name); + } + } + if (s.Count > 0) { + this.user.Send("Was soll ich tun?", s.ToArray()); + } else { + this.user.Send("Keine Geräte eingeschaltet!"); + } + } else if (message.StartsWith("/schalter ausschalten ")) { + String device = message.Substring(22); + foreach (Switchbinary item in this.zw.GetCommandClasses()) { + if (item.Name == device) { + item.Level = false; + this.user.Send("Ausschalten von " + item.Name + " " + (item.Level ? "nicht " : "") + "erfolgreich"); + } + } + } + } + } +} diff --git a/IoT-Bot/IoT-Bot/Condition/Trigger.cs b/IoT-Bot/IoT-Bot/Condition/Trigger.cs new file mode 100644 index 0000000..5e05976 --- /dev/null +++ b/IoT-Bot/IoT-Bot/Condition/Trigger.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BlubbFish.Utils.IoT.Connector; +using BlubbFish.Utils.IoT.Sensor; + +namespace IoTBot.Condition { + class Trigger : ACondition { + public Trigger(String name, Dictionary settings, ASensor sensor, ADataBackend data, AUserBackend user) : base(name, settings, sensor, data, user) { } + + protected override void Data_Update(Object sender, MqttEventArgs e) { + //throw new NotImplementedException(); + } + + protected override void Sensor_Update(Object sender, EventArgs e) { + //throw new NotImplementedException(); + } + + protected override void User_Update(Object sender, UserMessageEventArgs e) { + if (e.Message == this.settings["trigger"]) { + this.Send(); + } + } + } +} diff --git a/IoT-Bot/IoT-Bot/ConditionWorker.cs b/IoT-Bot/IoT-Bot/ConditionWorker.cs index 5e3fb20..554b4dc 100644 --- a/IoT-Bot/IoT-Bot/ConditionWorker.cs +++ b/IoT-Bot/IoT-Bot/ConditionWorker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using BlubbFish.Utils; using BlubbFish.Utils.IoT.Connector; using BlubbFish.Utils.IoT.Sensor; using IoTBot.Condition; @@ -17,13 +16,16 @@ namespace IoTBot { } public void SetCondition(String name, Dictionary settings) { - Dictionary sensor_settings = new Dictionary(); - foreach (KeyValuePair item in settings) { - if(item.Key.StartsWith("sensor_")) { - sensor_settings.Add(item.Key.Substring(7), item.Value); + ASensor sensor = null; + if (settings.ContainsKey("sensor_type")) { + Dictionary sensor_settings = new Dictionary(); + foreach (KeyValuePair item in settings) { + if (item.Key.StartsWith("sensor_")) { + sensor_settings.Add(item.Key.Substring(7), item.Value); + } } + sensor = ASensor.GetInstance(this.data, sensor_settings, name); } - ASensor sensor = ASensor.GetInstance(this.data, sensor_settings, name); this.conditions.Add(ACondition.GetInstance(name, settings, sensor, this.data, this.user)); } diff --git a/IoT-Bot/IoT-Bot/IoT-Bot.csproj b/IoT-Bot/IoT-Bot/IoT-Bot.csproj index 588acdc..bf1b443 100644 --- a/IoT-Bot/IoT-Bot/IoT-Bot.csproj +++ b/IoT-Bot/IoT-Bot/IoT-Bot.csproj @@ -7,8 +7,8 @@ {89077643-B472-419F-8EAB-56B9E2D13ABC} Exe Properties - MqttToTelegram - MqttToTelegram + IoTBot + IoTBot v4.6.2 512 true @@ -37,6 +37,8 @@ + + @@ -55,6 +57,10 @@ {fac8ce64-bf13-4ece-8097-aeb5dd060098} Utils + + {166258ed-cb3d-43f5-8e8d-3a993b64d022} + Zway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Zway-Bot/Zway-Bot/Resources/Icon.ico b/Zway-Bot/Zway-Bot/Resources/Icon.ico new file mode 100644 index 0000000..4d42acd Binary files /dev/null and b/Zway-Bot/Zway-Bot/Resources/Icon.ico differ diff --git a/Zway-Bot/Zway-Bot/Resources/icon.svg b/Zway-Bot/Zway-Bot/Resources/icon.svg new file mode 100644 index 0000000..d6bba08 --- /dev/null +++ b/Zway-Bot/Zway-Bot/Resources/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Zway-Bot/Zway-Bot/Zway-Bot.csproj b/Zway-Bot/Zway-Bot/Zway-Bot.csproj new file mode 100644 index 0000000..053fc03 --- /dev/null +++ b/Zway-Bot/Zway-Bot/Zway-Bot.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {AEF8059F-8AEF-45C7-85C4-318C8F797900} + Exe + ZwayBot + Zway-Bot + v4.7.1 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + ZwayBot.Program + + + Resources\Icon.ico + + + + ..\packages\LitJson.0.9.0\lib\LitJson.dll + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + {39235fad-ba9d-4b51-82fc-6969967beae9} + ConnectorDataMosquitto + + + {ee6c8f68-ed46-4c1c-abdd-cfcdf75104f2} + ConnectorDataMqtt + + + {b870e4d5-6806-4a0b-b233-8907eedc5afc} + Utils-IoT + + + {fac8ce64-bf13-4ece-8097-aeb5dd060098} + Utils + + + {166258ed-cb3d-43f5-8e8d-3a993b64d022} + Zway + + + + + + + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + \ No newline at end of file diff --git a/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMosquitto.dll b/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMosquitto.dll new file mode 100644 index 0000000..ce671c5 Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMosquitto.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMqtt.dll b/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMqtt.dll new file mode 100644 index 0000000..52d6034 Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/ConnectorDataMqtt.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/LitJson.dll b/Zway-Bot/Zway-Bot/bin/Release/LitJson.dll new file mode 100644 index 0000000..f00f11f Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/LitJson.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/M2Mqtt.Net.dll b/Zway-Bot/Zway-Bot/bin/Release/M2Mqtt.Net.dll new file mode 100644 index 0000000..154580d Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/M2Mqtt.Net.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/Utils-IoT.dll b/Zway-Bot/Zway-Bot/bin/Release/Utils-IoT.dll new file mode 100644 index 0000000..e569d45 Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/Utils-IoT.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/Utils.dll b/Zway-Bot/Zway-Bot/bin/Release/Utils.dll new file mode 100644 index 0000000..b86be2c Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/Utils.dll differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/Zway-Bot.exe b/Zway-Bot/Zway-Bot/bin/Release/Zway-Bot.exe new file mode 100644 index 0000000..378e85a Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/Zway-Bot.exe differ diff --git a/Zway-Bot/Zway-Bot/bin/Release/Zway.dll b/Zway-Bot/Zway-Bot/bin/Release/Zway.dll new file mode 100644 index 0000000..fc9ccf3 Binary files /dev/null and b/Zway-Bot/Zway-Bot/bin/Release/Zway.dll differ diff --git a/Zway-Bot/Zway-Bot/packages.config b/Zway-Bot/Zway-Bot/packages.config new file mode 100644 index 0000000..eb9ba23 --- /dev/null +++ b/Zway-Bot/Zway-Bot/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Zway/Zway.sln b/Zway/Zway.sln new file mode 100644 index 0000000..373e352 --- /dev/null +++ b/Zway/Zway.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zway", "Zway\Zway.csproj", "{166258ED-CB3D-43F5-8E8D-3A993B64D022}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.Build.0 = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.ActiveCfg = Release|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2F3A172A-4D52-4191-A84A-675D35B4A292} + EndGlobalSection +EndGlobal diff --git a/Zway/Zway/Devices/CommandClasses/Battery.cs b/Zway/Zway/Devices/CommandClasses/Battery.cs new file mode 100644 index 0000000..c7df38d --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/Battery.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 128 = Battery + /// + public class Battery : ACommandClass { + + public Double Level { get; private set; } + + public override event UpdatedValue Update; + + public Battery(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.InitComplex(json); + } + + internal override void SetUpdate(JsonData json, Match match) { + Boolean success = false; + if (match.Groups[4].Value == ".data") { + if (json.Keys.Contains("last") && json["last"].Keys.Contains("value")) { + this.Level = Double.Parse(json["last"]["value"].ToString()); + success = true; + } + } else if (match.Groups[4].Value == ".data.last") { + if (json.Keys.Contains("value")) { + this.Level = Double.Parse(json["value"].ToString()); + success = true; + } + } else if (match.Groups[4].Value.StartsWith(".data.history.")) { + } else if (match.Groups[4].Value == ".data.lastChange") { + } else { + Helper.WriteError("Kenne in "+this.Name+" ["+this.Id+"] "+ match.Groups[4].Value+" nicht!"); + } + if (success && this.CheckSetUpdateTime(json)) { + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate, this)); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data") && json["data"].Keys.Contains("last") && json["data"]["last"].Keys.Contains("value")) { + this.Level = Double.Parse(json["data"]["last"]["value"].ToString()); + } + } + + public override String ToString() { + return "Battery " + this.Name + " [" + this.Id + "]: " + this.Level; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + }; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/CentralScene.cs b/Zway/Zway/Devices/CommandClasses/CentralScene.cs new file mode 100644 index 0000000..db4764b --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/CentralScene.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 91 = CentralScene + /// + public class Centralscene : ACommandClass { + public override event UpdatedValue Update; + + public ReadOnlyDictionary> ValidScenesModes { get; private set; } + public Int32 Scene { get; private set; } + public Int32 Key { get; private set; } + + public Centralscene(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.ValidScenesModes = new ReadOnlyDictionary>(new Dictionary>()); + this.InitComplex(json); + } + + internal override void SetUpdate(JsonData json, Match match) { + if (match.Groups[4].Value == ".data.currentScene") { + if (json.Keys.Contains("value")) { + this.Scene = Int32.Parse(json["value"].ToString()); + } + } else if (match.Groups[4].Value == ".data.keyAttribute") { + if (json.Keys.Contains("value") && this.CheckSetUpdateTime(json)) { + this.Key = Int32.Parse(json["value"].ToString()); + this.Update?.Invoke(this, new DeviceUpdateEvent(new Tuple(this.Scene, this.Key), this.LastUpdate, this)); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + if(data.Keys.Contains("sceneSupportedKeyAttributesMask")) { + Dictionary> scenes = new Dictionary>(); + foreach (String item in data["sceneSupportedKeyAttributesMask"].Keys) { + if (Int32.TryParse(item, out Int32 mode) && + data["sceneSupportedKeyAttributesMask"][item].Keys.Contains("value") && + data["sceneSupportedKeyAttributesMask"][item]["value"].IsArray) { + JsonData values = data["sceneSupportedKeyAttributesMask"][item]["value"]; + List modes = new List(); + foreach (JsonData value in values) { + modes.Add(Int32.Parse(value.ToString())); + } + scenes.Add(mode, new ReadOnlyCollection(modes)); + } + } + this.ValidScenesModes = new ReadOnlyDictionary>(scenes); + if (data.Keys.Contains("currentScene") && + data["currentScene"].Keys.Contains("value") && + data["currentScene"]["value"] != null) { + this.Scene = Int32.Parse(data["currentScene"]["value"].ToString()); + } + if (data.Keys.Contains("keyAttribute") && + data["keyAttribute"].Keys.Contains("value") && + data["keyAttribute"]["value"] != null) { + this.Key = Int32.Parse(data["keyAttribute"]["value"].ToString()); + } + } + } + } + + public override String ToString() { + return "CentralScene " + this.Name + " [" + this.Id + "]: " + this.Scene+"-"+this.Key; + } + + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "scene", this.Scene }, + { "key", this.Key }, + }; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs new file mode 100644 index 0000000..fe83f28 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + class Configurationsub : ACommandClass { + public override event UpdatedValue Update; + private Int64 _level; + + public Int64 Level { + get { + return this._level; + } + set { + if (this.Size == 1 && Int16.TryParse(value.ToString(), out Int16 value16)) { + this.SetTriple(this.SensorId, value16, 1); + } else if(this.Size == 2 && Int32.TryParse(value.ToString(), out Int32 value32)) { + this.SetTriple(this.SensorId, value32, 2); + } else if(this.Size == 4) { + this.SetTriple(this.SensorId, value, 4); + } + } + } + public Int32 Size { get; private set; } + + public Configurationsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; + InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("val") && + json["val"].Keys.Contains("value") && + json["val"]["value"] != null && + json.Keys.Contains("size") && + json["size"].Keys.Contains("value")) { + this._level = Int64.Parse(json["val"]["value"].ToString()); + this.Size = Int32.Parse(json["size"]["value"].ToString()); + } + } + + internal override void Poll() => this.PollSub(); + + internal override void SetUpdate(JsonData json, Match match) { + if (json.Keys.Contains("val") && json["val"].Keys.Contains("value") && json["val"]["value"] != null && + json.Keys.Contains("size") && json["size"].Keys.Contains("value") && + this.CheckSetUpdateTime(json)) { + this._level = Int64.Parse(json["val"]["value"].ToString()); + this.Size = Int32.Parse(json["size"]["value"].ToString()); + this.Update?.Invoke(this, new DeviceUpdateEvent(new Tuple(this.Level, this.Size), this.LastUpdate, this)); + } + } + + public override String ToString() { + return "Configuration " + this.Name + " [" + this.Id + "]: " + this.Level; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + { "size", this.Size }, + }; + } + } +} \ No newline at end of file diff --git a/Zway/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs new file mode 100644 index 0000000..a4aa3c2 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + public class Metersub : ACommandClass { + public override event UpdatedValue Update; + + public String Type { get; private set; } + public Double Level { get; private set; } + public String Scale { get; private set; } + public Metersub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasReset = true; + this.IsSub = true; + InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("sensorTypeString") && + json["sensorTypeString"].Keys.Contains("value") && + json.Keys.Contains("val") && + json["val"].Keys.Contains("value") && + json.Keys.Contains("scaleString") && + json["scaleString"].Keys.Contains("value")) { + this.Type = json["sensorTypeString"]["value"].ToString(); + this.Level = Double.Parse(json["val"]["value"].ToString()); + this.Scale = json["scaleString"]["value"].ToString(); + } + } + + internal override void SetUpdate(JsonData json, Match match) { + if (json.Keys.Contains("val") && json["val"].Keys.Contains("value") && + json.Keys.Contains("sensorTypeString") && json["sensorTypeString"].Keys.Contains("value") && + json.Keys.Contains("scaleString") && json["scaleString"].Keys.Contains("value") && + this.CheckSetUpdateTime(json)) { + this.Level = Double.Parse(json["val"]["value"].ToString()); + this.Type = json["sensorTypeString"]["value"].ToString(); + this.Scale = json["scaleString"]["value"].ToString(); + this.Update?.Invoke(this, new DeviceUpdateEvent(new Tuple(this.Type, this.Scale, this.Level), this.LastUpdate, this)); + } + } + + public override String ToString() { + return "Meter " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale; + } + + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + { "type", this.Type }, + { "scale", this.Scale }, + }; + } + } +} \ No newline at end of file diff --git a/Zway/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs new file mode 100644 index 0000000..52001d0 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + public class Sensormultilevelsub : ACommandClass { + public override event UpdatedValue Update; + + public String Type { get; private set; } + public Double Level { get; private set; } + public String Scale { get; private set; } + + public Sensormultilevelsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; + InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("sensorTypeString") && + json["sensorTypeString"].Keys.Contains("value") && + json.Keys.Contains("val") && + json["val"].Keys.Contains("value") && + json.Keys.Contains("scaleString") && + json["scaleString"].Keys.Contains("value")) { + this.Type = json["sensorTypeString"]["value"].ToString(); + this.Level = Double.Parse(json["val"]["value"].ToString()); + this.Scale = json["scaleString"]["value"].ToString(); + } + } + + internal override void SetUpdate(JsonData json, Match match) { + if(json.Keys.Contains("val") && json["val"].Keys.Contains("value") && + json.Keys.Contains("sensorTypeString") && json["sensorTypeString"].Keys.Contains("value") && + json.Keys.Contains("scaleString") && json["scaleString"].Keys.Contains("value") && + this.CheckSetUpdateTime(json)) { + this.Level = Double.Parse(json["val"]["value"].ToString()); + this.Type = json["sensorTypeString"]["value"].ToString(); + this.Scale = json["scaleString"]["value"].ToString(); + this.Update?.Invoke(this, new DeviceUpdateEvent(new Tuple(this.Type, this.Scale, this.Level), this.LastUpdate, this)); + } + } + + public override String ToString() { + return "SensorMultilevel " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale; + } + + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + { "type", this.Type }, + { "scale", this.Scale }, + }; + } + } +} \ No newline at end of file diff --git a/Zway/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs new file mode 100644 index 0000000..85ec304 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + class Thermostatsetpointsub : ACommandClass { + public override event UpdatedValue Update; + + private Double _level; + public Double Level { + get { + return this._level; + } + set { + if (!this.HasMinMax || (this.HasMinMax && value >= this.TempMin && value <= this.TempMax)) { + this.SetTuple(this.SensorId, (Double)Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2); + } + } + } + public String Scale { get; private set; } + public Double TempMax { get; private set; } + public Double TempMin { get; private set; } + public Boolean HasMinMax { get; private set; } + public String Type { get; private set; } + + public Thermostatsetpointsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; + InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("modeName") && json["modeName"].Keys.Contains("value") && + json.Keys.Contains("val") && json["val"].Keys.Contains("value") && + json.Keys.Contains("deviceScaleString") && json["deviceScaleString"].Keys.Contains("value")) { + this.Type = json["modeName"]["value"].ToString(); + this._level = Double.Parse(json["val"]["value"].ToString()); + this.Scale = json["deviceScaleString"]["value"].ToString(); + } + if (json.Keys.Contains("min") && json["min"].Keys.Contains("value") && + json.Keys.Contains("max") && json["max"].Keys.Contains("value")) { + this.TempMin = Double.Parse(json["min"]["value"].ToString()); + this.TempMax = Double.Parse(json["max"]["value"].ToString()); + this.HasMinMax = true; + } else { + this.HasMinMax = false; + } + } + + internal override void SetUpdate(JsonData json, Match match) { + Boolean ret = false; + if (json.Keys.Contains("val") && json["val"].Keys.Contains("value") && + json.Keys.Contains("deviceScaleString") && json["deviceScaleString"].Keys.Contains("value") && + json.Keys.Contains("modeName") && json["modeName"].Keys.Contains("value")) { + this._level = Double.Parse(json["val"]["value"].ToString()); + this.Scale = json["deviceScaleString"]["value"].ToString(); + this.Type = json["modeName"]["value"].ToString(); + ret = true; + } + if (json.Keys.Contains("min") && json["val"].Keys.Contains("value") && + json.Keys.Contains("max") && json["max"].Keys.Contains("value")) { + this.TempMin = Double.Parse(json["min"]["value"].ToString()); + this.TempMax = Double.Parse(json["max"]["value"].ToString()); + this.HasMinMax = true; + ret = true; + } else { + this.HasMinMax = false; + } + if (ret && this.CheckSetUpdateTime(json)) { + this.Update?.Invoke(this, new DeviceUpdateEvent(new Tuple(this.Type, this.Scale, this.Level, this.TempMin, this.TempMax, this.HasMinMax), this.LastUpdate, this)); + } + } + + public override String ToString() { + return "ThermostatSetPoint " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale + " [" + this.TempMin + "," + this.TempMax + "," + this.HasMinMax + "]"; + } + + internal override void Poll() => this.PollSub(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + { "type", this.Type }, + { "scale", this.Scale }, + { "tempmax", this.TempMax }, + { "tempmin", this.TempMin }, + { "hasminmax", this.HasMinMax }, + }; + } + } +} \ No newline at end of file diff --git a/Zway/Zway/Devices/CommandClasses/Configuration.cs b/Zway/Zway/Devices/CommandClasses/Configuration.cs new file mode 100644 index 0000000..6410555 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/Configuration.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 112 = Configuration + /// + class Configuration : ACommandClass { + public override event UpdatedValue Update; + + public Configuration(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasSub = true; + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + Dictionary subs = new Dictionary(); + foreach (String item in data.Keys) { + if (Int32.TryParse(item, out Int32 subid) && + data[item].Keys.Contains("size") && + data[item].Keys.Contains("val") && + data[item]["val"].Keys.Contains("value") && + data[item]["val"]["value"] != null) { + subs.Add(subid, new Configurationsub(data[item], new Tuple(this.DeviceId, this.Instance, this.Commandclass, subid), this.http, this.Polling)); + } + } + this.Sub = new ReadOnlyDictionary(subs); + } + } + + internal override void SetUpdate(JsonData json, Match match) { + if (match.Groups[4].Value.StartsWith(".data.")) { + Int32 subid = Int32.Parse(match.Groups[5].Value); + if (this.Sub.ContainsKey(subid)) { + this.Sub[subid].SetUpdate(json, match); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + internal override void Poll() => this.PollPerSub(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); + } +} diff --git a/Zway/Zway/Devices/CommandClasses/Indicator.cs b/Zway/Zway/Devices/CommandClasses/Indicator.cs new file mode 100644 index 0000000..9cb15ab --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/Indicator.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 135 = Indicator + /// + class Indicator : ACommandClass { + private Boolean _level; + + public override event UpdatedValue Update; + public Boolean Level { + get { + return this._level; + } + set { + this.SetInt(value ? 255 : 0); + } + } + + public Indicator(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data") && json["data"].Keys.Contains("stat") && json["data"]["stat"].Keys.Contains("value")) { + this._level = Int32.Parse(json["data"]["stat"]["value"].ToString()) == 255; + } + } + + internal override void SetUpdate(JsonData json, Match match) { + if(match.Groups[4].Value == ".data.stat") { + if (json.Keys.Contains("value") && this.CheckSetUpdateTime(json)) { + this._level = Int32.Parse(json["value"].ToString()) == 255; + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate, this)); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + public override String ToString() { + return "Indicator " + this.Name + " [" + this.Id + "]: " + this.Level; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level }, + }; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/Meter.cs b/Zway/Zway/Devices/CommandClasses/Meter.cs new file mode 100644 index 0000000..9cb8115 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/Meter.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 50 = Meter + /// + public class Meter : ACommandClass { + public override event UpdatedValue Update; + + public Meter(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasSub = true; + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + internal override void SetUpdate(JsonData json, Match match) { + if (match.Groups[4].Value.StartsWith(".data.")) { + Int32 subid = Int32.Parse(match.Groups[5].Value); + if (this.Sub.ContainsKey(subid)) { + this.Sub[subid].SetUpdate(json, match); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + Dictionary subs = new Dictionary(); + foreach (String item in data.Keys) { + if (Int32.TryParse(item, out Int32 subid) && + data[item].Keys.Contains("sensorTypeString") && + data[item].Keys.Contains("val") && + data[item].Keys.Contains("scaleString")) { + subs.Add(subid, new Metersub(data[item], new Tuple(this.DeviceId, this.Instance, this.Commandclass, subid), this.http, this.Polling)); + } + } + this.Sub = new ReadOnlyDictionary(subs); + } + } + + internal override void Poll() => this.PollSubGlobal(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); + } +} diff --git a/Zway/Zway/Devices/CommandClasses/SensorMultilevel.cs b/Zway/Zway/Devices/CommandClasses/SensorMultilevel.cs new file mode 100644 index 0000000..53d974c --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/SensorMultilevel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 49 = SensorMultilevel + /// + public class Sensormultilevel : ACommandClass { + public override event UpdatedValue Update; + + public Sensormultilevel(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasSub = true; + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + internal override void SetUpdate(JsonData json, Match match) { + if (match.Groups[4].Value.StartsWith(".data.")) { + Int32 subid = Int32.Parse(match.Groups[5].Value); + if (this.Sub.ContainsKey(subid)) { + this.Sub[subid].SetUpdate(json, match); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + Dictionary subs = new Dictionary(); + foreach (String item in data.Keys) { + if (Int32.TryParse(item, out Int32 subid) && + data[item].Keys.Contains("sensorTypeString") && + data[item].Keys.Contains("val") && + data[item].Keys.Contains("scaleString")) { + subs.Add(subid, new Sensormultilevelsub(data[item], new Tuple(this.DeviceId, this.Instance, this.Commandclass, subid), this.http, this.Polling)); + } + } + this.Sub = new ReadOnlyDictionary(subs); + } + } + + internal override void Poll() => this.PollSubGlobal(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); + } +} diff --git a/Zway/Zway/Devices/CommandClasses/SwitchBinary.cs b/Zway/Zway/Devices/CommandClasses/SwitchBinary.cs new file mode 100644 index 0000000..a2d7dbb --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/SwitchBinary.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 37 = SwitchBinary + /// + public class Switchbinary : ACommandClass { + private Boolean _level; + + public override event UpdatedValue Update; + + public Boolean Level { + get { + return this._level; + } + set { + this.SetInt(value ? 255 : 0); + } + } + + public Switchbinary(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.InitComplex(json); + } + + internal override void SetUpdate(JsonData json, Match match) { + if(match.Groups[4].Value == ".data.level") { + if (json.Keys.Contains("value") && json["value"].IsBoolean && this.CheckSetUpdateTime(json)) { + this._level = (Boolean)json["value"]; + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate, this)); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data") && json["data"].Keys.Contains("level") && json["data"]["level"].Keys.Contains("value") && json["data"]["level"]["value"].IsBoolean) { + this._level = (Boolean)json["data"]["level"]["value"]; + } + } + + public override String ToString() { + return "SwitchBinary " + this.Name + " [" + this.Id + "]: " + this.Level; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level } + }; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/SwitchMultilevel.cs b/Zway/Zway/Devices/CommandClasses/SwitchMultilevel.cs new file mode 100644 index 0000000..dcb3069 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/SwitchMultilevel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 38 = SwitchMultilevel + /// + public class Switchmultilevel : ACommandClass { + private Int32 _level; + + public override event UpdatedValue Update; + + public Int32 Level { + get { + return this._level; + } + set { + if(value == 0 || (value > 0 && value <= 99) || value == 255) { + this.SetTuple(value, 0); + } + } + } + + public Switchmultilevel(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.InitComplex(json); + } + + internal override void SetUpdate(JsonData json, Match match) { + if(match.Groups[4].Value == ".data.level") { + if(json.Keys.Contains("value") && this.CheckSetUpdateTime(json)) { + this._level = Int32.Parse(json["value"].ToString()); + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate, this)); + } + } else if (match.Groups[4].Value == ".data.prevLevel") { + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data") && json["data"].Keys.Contains("level") && json["data"]["level"].Keys.Contains("value")) { + this._level = Int32.Parse(json["data"]["level"]["value"].ToString()); + } + } + + public override String ToString() { + return "SwitchMultilevel " + this.Name + " [" + this.Id + "]: " + this.Level; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level } + }; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/ThermostatMode.cs b/Zway/Zway/Devices/CommandClasses/ThermostatMode.cs new file mode 100644 index 0000000..09f41ba --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/ThermostatMode.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 64 = ThermostatMode + /// + public class Thermostatmode : ACommandClass { + private Int32 _level; + + public override event UpdatedValue Update; + + public ReadOnlyDictionary ValidModes { get; private set; } + public Int32 Level { + get { return this._level; } + set { + if (this.ValidModes.ContainsKey(value)) { + this.SetInt(value); + } + } + } + + public Thermostatmode(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.ValidModes = new ReadOnlyDictionary(new Dictionary()); + this.InitComplex(json); + } + + internal override void SetUpdate(JsonData json, Match match) { + if(match.Groups[4].Value == ".data.mode") { + if(json.Keys.Contains("value") && this.CheckSetUpdateTime(json)) { + this._level = Int32.Parse(json["value"].ToString()); + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate, this)); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + Dictionary modes = new Dictionary(); + foreach (String item in data.Keys) { + if (Int32.TryParse(item, out Int32 mode) && + data[item].Keys.Contains("modeName") && + data[item]["modeName"].Keys.Contains("value")) { + modes.Add(mode, data[item]["modeName"]["value"].ToString()); + } + } + this.ValidModes = new ReadOnlyDictionary(modes); + if (data.Keys.Contains("mode") && + data["mode"].Keys.Contains("value")) { + this._level = Int32.Parse(data["mode"]["value"].ToString()); + } + } + } + + public override String ToString() { + return "ThermostatMode " + this.Name + " [" + this.Id + "]: " + this.ValidModes[this.Level]; + } + + public override Dictionary ToDictionary() { + Dictionary modes = new Dictionary(); + foreach (KeyValuePair item in this.ValidModes) { + modes.Add(item.Key.ToString(), item.Value); + } + Dictionary json = new Dictionary { + { "level", this.Level }, + { "modes", modes } + }; + return json; + } + } +} diff --git a/Zway/Zway/Devices/CommandClasses/ThermostatSetPoint.cs b/Zway/Zway/Devices/CommandClasses/ThermostatSetPoint.cs new file mode 100644 index 0000000..eef0824 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/ThermostatSetPoint.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 67 = ThermostatSetPoint + /// + public class Thermostatsetpoint : ACommandClass { + public override event UpdatedValue Update; + + public Thermostatsetpoint(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasSub = true; + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + internal override void SetUpdate(JsonData json, Match match) { + if (match.Groups[4].Value.StartsWith(".data.")) { + Int32 subid = Int32.Parse(match.Groups[5].Value); + if (this.Sub.ContainsKey(subid)) { + this.Sub[subid].SetUpdate(json, match); + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + Dictionary subs = new Dictionary(); + foreach (String item in data.Keys) { + if (Int32.TryParse(item, out Int32 subid) && + data[item].Keys.Contains("modeName") && + data[item].Keys.Contains("val") && + data[item].Keys.Contains("deviceScaleString")) { + subs.Add(subid, new Thermostatsetpointsub(data[item], new Tuple(this.DeviceId, this.Instance, this.Commandclass, subid), this.http, this.Polling)); + } + } + this.Sub = new ReadOnlyDictionary(subs); + } + } + + internal override void Poll() => this.PollPerSub(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); + } +} diff --git a/Zway/Zway/Devices/CommandClasses/Wakeup.cs b/Zway/Zway/Devices/CommandClasses/Wakeup.cs new file mode 100644 index 0000000..016c740 --- /dev/null +++ b/Zway/Zway/Devices/CommandClasses/Wakeup.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + /// + /// 132 = Wakeup + /// + class Wakeup : ACommandClass { + private Int32 _interval; + private Int32 _againstNode; + + public override event UpdatedValue Update; + public Int32 Interval { + get { + return this._interval; + } + set { + if(value >= this.WakeupMin && value <= this.WakeupMax) { + this.SetTuple(value, this.AgainstNode); + } + } + } + public Int32 AgainstNode { get { + return this._againstNode; + } + set { + this.SetTuple(this.Interval, value); + } + } + public Int32 WakeupMin { get; private set; } + public Int32 WakeupMax { get; private set; } + public Int32 WakeupDefault { get; private set; } + public DateTime LastWakeup { get; private set; } + public DateTime LastSleep { get; private set; } + + public Wakeup(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.InitComplex(json); + } + + private void InitComplex(JsonData json) { + if (json.Keys.Contains("data")) { + JsonData data = json["data"]; + if(data.Keys.Contains("interval") && data["interval"].Keys.Contains("value")) { + this._interval = Int32.Parse(data["interval"]["value"].ToString()); + } + if (data.Keys.Contains("nodeId") && data["nodeId"].Keys.Contains("value")) { + this._againstNode = Int32.Parse(data["nodeId"]["value"].ToString()); + } + if (data.Keys.Contains("min") && data["min"].Keys.Contains("value")) { + this.WakeupMin = Int32.Parse(data["min"]["value"].ToString()); + } + if (data.Keys.Contains("max") && data["max"].Keys.Contains("value")) { + this.WakeupMax = Int32.Parse(data["max"]["value"].ToString()); + } + if (data.Keys.Contains("default") && data["default"].Keys.Contains("value")) { + this.WakeupDefault = Int32.Parse(data["default"]["value"].ToString()); + } + if (data.Keys.Contains("lastWakeup") && data["lastWakeup"].Keys.Contains("value")) { + this.LastWakeup = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(data["lastWakeup"]["value"].ToString())).DateTime.ToLocalTime(); + } + if (data.Keys.Contains("lastSleep") && data["lastSleep"].Keys.Contains("value")) { + this.LastSleep = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(data["lastSleep"]["value"].ToString())).DateTime.ToLocalTime(); + } + } + } + + internal override void SetUpdate(JsonData json, Match match) { + Boolean success = false; + if (match.Groups[4].Value == ".data.lastWakeup") { + if (json.Keys.Contains("value") && (json["value"].IsInt || json["value"].IsLong)) { + this.LastWakeup = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["value"].ToString())).DateTime.ToLocalTime(); + success = true; + } + } else if (match.Groups[4].Value == ".data.lastSleep") { + if (json.Keys.Contains("value") && (json["value"].IsInt || json["value"].IsLong)) { + this.LastSleep = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["value"].ToString())).DateTime.ToLocalTime(); + success = true; + } + } else if (match.Groups[4].Value == ".data") { + if (json.Keys.Contains("interval") && json["interval"].Keys.Contains("value")) { + this._interval = Int32.Parse(json["interval"]["value"].ToString()); + success = true; + } + if (json.Keys.Contains("nodeId") && json["nodeId"].Keys.Contains("value")) { + this._againstNode = Int32.Parse(json["nodeId"]["value"].ToString()); + success = true; + } + if (json.Keys.Contains("min") && json["min"].Keys.Contains("value")) { + this.WakeupMin = Int32.Parse(json["min"]["value"].ToString()); + success = true; + } + if (json.Keys.Contains("max") && json["max"].Keys.Contains("value")) { + this.WakeupMax = Int32.Parse(json["max"]["value"].ToString()); + success = true; + } + if (json.Keys.Contains("default") && json["default"].Keys.Contains("value")) { + this.WakeupDefault = Int32.Parse(json["default"]["value"].ToString()); + success = true; + } + if (json.Keys.Contains("lastWakeup") && json["lastWakeup"].Keys.Contains("value")) { + this.LastWakeup = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["lastWakeup"]["value"].ToString())).DateTime.ToLocalTime(); + success = true; + } + if (json.Keys.Contains("lastSleep") && json["lastSleep"].Keys.Contains("value")) { + this.LastSleep = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["lastSleep"]["value"].ToString())).DateTime.ToLocalTime(); + success = true; + } + } else { + Helper.WriteError("Kenne in " + this.Name + " [" + this.Id + "] " + match.Groups[4].Value + " nicht!"); + } + if (success && this.CheckSetUpdateTime(json)) { + this.Update?.Invoke(this, new DeviceUpdateEvent(0, this.LastUpdate, this)); + } + } + + public override String ToString() { + return "Wakeup " + this.Name + " [" + this.Id + "]: " + this.LastWakeup + "-" + this.LastSleep + " " + this.Interval + " [" + this.WakeupMin + "," + this.WakeupMax + "," + this.WakeupDefault + "," + this.AgainstNode + "]"; + } + + public override Dictionary ToDictionary() { + return new Dictionary { + {"interval", this.Interval }, + {"againstnode", this.AgainstNode }, + { "wakeupmin", this.WakeupMin }, + { "wakeupmax", this.WakeupMax }, + { "wakeupdefault", this.WakeupDefault }, + { "lastwakeup", this.LastWakeup.ToString() }, + { "lastsleep", this.LastSleep.ToString() } + }; + } + } +} diff --git a/Zway/Zway/Devices/Device.cs b/Zway/Zway/Devices/Device.cs new file mode 100644 index 0000000..2a37a08 --- /dev/null +++ b/Zway/Zway/Devices/Device.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + public class Device { + private Boolean polling; + + public delegate void UpdatedDevice(Object sender, DeviceUpdateEvent e); + public event UpdatedDevice Update; + + public String Name { get; } + public Int32 Id { get; } + public ReadOnlyDictionary Instances { get; private set; } + public DateTime LastUpdate { get; private set; } + + private Device(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + this.Id = id.Item1; + this.polling = polling; + this.CreateInstances(json["instances"], http); + if (json["data"].Keys.Contains("givenName") && json["data"]["givenName"].Keys.Contains("value")) { + this.Name = json["data"]["givenName"]["value"].ToString(); + } else { + this.Name = "Unknown"; + } + foreach (KeyValuePair item in this.Instances) { + item.Value.Update += this.InstanceUpdate; + } + } + + protected Boolean CheckSetUpdateTime(JsonData json) { + if (json.Keys.Contains("updateTime") && (json["updateTime"].IsInt || json["updateTime"].IsLong)) { + DateTime newdate = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["updateTime"].ToString())).ToLocalTime().DateTime; + if (newdate > this.LastUpdate) { + this.LastUpdate = newdate; + return true; + } else { + return false; + } + } else { + return true; + } + } + + private void InstanceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + private void CreateInstances(JsonData json, HttpConnection http) { + Dictionary instances = new Dictionary(); + foreach (String instanceid in json.Keys) { + Instance i = Instance.CreateInstance(json[instanceid], new Tuple(this.Id, Int32.Parse(instanceid)), http, this.polling); + if (i != null) { + instances.Add(Int32.Parse(instanceid), i); + } + } + this.Instances = new ReadOnlyDictionary(instances); + } + + internal static Device CreateDevice(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + if(json.Keys.Contains("instances") && + json["instances"].Count > 0 && + json.Keys.Contains("data") && id.Item1 != 1) { + return new Device(json, id, http, polling); + } + return null; + } + + public override String ToString() { + return this.Name+"_"+this.Id+" ["+this.Instances.Count+"]"; + } + + internal void SetUpdate(JsonData json, Match match) { + if (match.Groups[2].Value == "data.lastReceived") { + if (this.CheckSetUpdateTime(json)) { + //this.Update?.Invoke(this, new DeviceUpdateEvent(this.LastUpdate, this.LastUpdate)); + } + } + } + + internal void Poll() { + foreach (KeyValuePair item in this.Instances) { + item.Value.Poll(); + } + } + } +} diff --git a/Zway/Zway/Devices/Instance.cs b/Zway/Zway/Devices/Instance.cs new file mode 100644 index 0000000..b0f1f02 --- /dev/null +++ b/Zway/Zway/Devices/Instance.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using BlubbFish.IoT.Zway.Devices.CommandClasses; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + public class Instance { + private DateTime nextwakeup; + private Boolean polling; + + public delegate void UpdatedInstance(Object sender, DeviceUpdateEvent e); + public event UpdatedInstance Update; + + public Int32 DeviceId { get; } + public Int32 InstanceId { get; } + public ReadOnlyDictionary CommandClasses { get; private set; } + + private Instance(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + this.DeviceId = id.Item1; + this.InstanceId = id.Item2; + this.polling = polling; + this.CreateInstances(json["commandClasses"], http); + foreach (KeyValuePair item in this.CommandClasses) { + item.Value.Update += this.ClassUpdate; + } + this.MakePolltimer(); + } + + private void MakePolltimer() { + if(this.CommandClasses.ContainsKey(ACommandClass.Classes.Wakeup)) { + this.nextwakeup = ((Wakeup)this.CommandClasses[ACommandClass.Classes.Wakeup]).LastWakeup.AddSeconds(((Wakeup)this.CommandClasses[ACommandClass.Classes.Wakeup]).Interval).AddSeconds(-20); + } else { + this.nextwakeup = DateTime.Now.AddSeconds(60); + } + } + + private void ClassUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(this, e); + } + + private void CreateInstances(JsonData json, HttpConnection http) { + Dictionary commands = new Dictionary(); + foreach (String commandid in json.Keys) { + ACommandClass c = ACommandClass.CreateInstance(json[commandid], new Tuple(this.DeviceId, this.InstanceId, (ACommandClass.Classes)Int32.Parse(commandid)), http, this.polling); + if (c != null) { + commands.Add((ACommandClass.Classes)Int32.Parse(commandid), c); + } + } + this.CommandClasses = new ReadOnlyDictionary(commands); + } + + internal static Instance CreateInstance(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + if (json.Keys.Contains("commandClasses") && + json["commandClasses"].Count > 0 && + json.Keys.Contains("data")) { + return new Instance(json, id, http, polling); + } + return null; + } + + public override String ToString() { + return "Instance: " + this.DeviceId + "-" + this.InstanceId + " [" + this.CommandClasses.Count + "]"; + } + + internal void Poll() { + if(DateTime.Now > this.nextwakeup) { + this.MakePolltimer(); + foreach (KeyValuePair item in this.CommandClasses) { + item.Value.Poll(); + } + } + } + } +} diff --git a/Zway/Zway/Events/DeviceUpdateEvent.cs b/Zway/Zway/Events/DeviceUpdateEvent.cs new file mode 100644 index 0000000..78c5938 --- /dev/null +++ b/Zway/Zway/Events/DeviceUpdateEvent.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlubbFish.IoT.Zway.Events { + public class DeviceUpdateEvent : EventArgs { + + public DeviceUpdateEvent() { + } + + public DeviceUpdateEvent(Double value, DateTime time, Object parent) { + this.GetDouble = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public DeviceUpdateEvent(Boolean value, DateTime time, Object parent) { + this.GetBoolean = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public DeviceUpdateEvent(DateTime value, DateTime time, Object parent) { + this.GetDate = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public DeviceUpdateEvent(Tuple value, DateTime time, Object parent) { + this.GetStringStringDoubleTuple = value; + this.UpdateTime = time; + this.Parent = parent; + } + public DeviceUpdateEvent(Tuple value, DateTime time, Object parent) { + this.GetStringStringDoubleDoubleDoubleDoubleBooleanTuple = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public DeviceUpdateEvent(Tuple value, DateTime time, Object parent) { + this.GetIntegerTuple = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public DeviceUpdateEvent(Tuple value, DateTime time, Object parent) { + this.GetLongIntegerTuple = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public Double GetDouble { get; } + public DateTime GetDate { get; } + public Tuple GetStringStringDoubleTuple { get; } + public Tuple GetStringStringDoubleDoubleDoubleDoubleBooleanTuple { get; } + public Tuple GetLongIntegerTuple { get; } + public DateTime UpdateTime { get; } + public Object Parent { get; private set; } + public Boolean GetBoolean { get; } + public Tuple GetIntegerTuple { get; } + } +} \ No newline at end of file diff --git a/Zway/Zway/Exceptions/ZwayExceptions.cs b/Zway/Zway/Exceptions/ZwayExceptions.cs new file mode 100644 index 0000000..9c15199 --- /dev/null +++ b/Zway/Zway/Exceptions/ZwayExceptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.Serialization; + +namespace BlubbFish.IoT.Zway.Exceptions { + [Serializable] + public class ConnectionException : Exception { + public ConnectionException() { } + public ConnectionException(String message) : base(message) { } + public ConnectionException(String message, Exception innerException) : base(message, innerException) { } + protected ConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Zway/Zway/Interfaces/ACommandClass.cs b/Zway/Zway/Interfaces/ACommandClass.cs new file mode 100644 index 0000000..05cb788 --- /dev/null +++ b/Zway/Zway/Interfaces/ACommandClass.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Interfaces { + public abstract class ACommandClass { + protected HttpConnection http; + + public delegate void UpdatedValue(Object sender, DeviceUpdateEvent e); + public abstract event UpdatedValue Update; + protected enum IgnoredClasses : Int32 { + Basic = 32, + ControllerReplication = 33, + ApplicationStatus = 34, + SensorBinary = 48, + SwitchColor = 51, + MeterPulse = 53, + MultiChannel = 96, + Alarm = 113, + ManufacturerSpecific = 114, + PowerLevel = 115, + Protection = 117, + NodeNaming = 119, + FirmwareUpdate = 122, + Association = 133, + Version = 134, + MultiChannelAssociation = 142, + MultiCmd = 143 + } + + public enum Classes : Int32 { + SwitchBinary = 37, + SwitchMultilevel = 38, + SensorMultilevel = 49, + Meter = 50, + ThermostatMode = 64, + ThermostatSetPoint = 67, + CentralScene = 91, + Configuration = 112, + Battery = 128, + Wakeup = 132, + Indicator = 135 + } + + public Int32 DeviceId { get; } + public Int32 Instance { get; } + public Classes Commandclass { get; } + public String Id { get; } + public Int32 SensorId { get; } + public DateTime LastUpdate { get; protected set; } + public String Name { get; } + public Boolean Polling { get; set; } + public Boolean PollOnce { get; set; } + public ReadOnlyDictionary Sub { get; protected set; } + public Boolean HasSub { get; protected set; } + public Boolean IsSub { get; protected set; } + public Boolean HasReset { get; protected set; } + + #region Constructor + + protected ACommandClass(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + this.DeviceId = id.Item1; + this.Instance = id.Item2; + this.Commandclass = id.Item3; + this.SensorId = id.Item4; + this.http = http; + this.LastUpdate = DateTime.Now; + this.Polling = polling; + this.HasSub = false; + this.HasReset = false; + this.IsSub = false; + this.Id = this.DeviceId + "-" + this.Instance + "-" + (Int32)this.Commandclass + "-" + this.SensorId; + if (ZwayController.namelist.ContainsKey(this.Id)) { + this.Name = ZwayController.namelist[this.Id]; + } + } + + protected ACommandClass(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + this.DeviceId = id.Item1; + this.Instance = id.Item2; + this.Commandclass = id.Item3; + this.http = http; + this.LastUpdate = DateTime.Now; + this.Polling = polling; + this.HasSub = false; + this.HasReset = false; + this.IsSub = false; + this.Id = this.DeviceId + "-" + this.Instance + "-" + (Int32)this.Commandclass; + if (ZwayController.namelist.ContainsKey(this.Id)) { + this.Name = ZwayController.namelist[this.Id]; + } + } + + internal static ACommandClass CreateInstance(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + if (json.Keys.Contains("name") && + json.Keys.Contains("data") && + json["data"].Keys.Contains("supported") && + json["data"]["supported"].Keys.Contains("value") && + Boolean.Parse(json["data"]["supported"]["value"].ToString()) && + !Enum.IsDefined(typeof(IgnoredClasses), (Int32)id.Item3) && Enum.IsDefined(typeof(Classes), id.Item3)) { + String name = id.Item3.ToString(); + String objectName = "BlubbFish.IoT.Zway.Devices.CommandClasses." + name[0].ToString().ToUpper() + name.Substring(1).ToLower(); + return GetInstanceConcrete(objectName, json, http, id, polling); + } + if (!Enum.IsDefined(typeof(IgnoredClasses), (Int32)id.Item3) && !Enum.IsDefined(typeof(Classes), id.Item3)) { + Helper.WriteError("CommandClass " + id.Item3 + " not exist."); + } + return null; + } + + private static ACommandClass GetInstanceConcrete(String objectName, JsonData json, HttpConnection http, Tuple id, Boolean polling) { + Type t = null; + try { + t = Type.GetType(objectName, true); + } catch (TypeLoadException) { + Console.Error.WriteLine("Konnte Type " + objectName + " nicht laden!"); + return null; + } + return (ACommandClass)t.GetConstructor(new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean) }).Invoke(new Object[] { json, id, http, polling }); + } + + #endregion + + #region Polling + + internal virtual void Poll() { + if (this.Polling || this.PollOnce) { + this.PollOnce = false; + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Get()"); + } + } + + protected void PollNone() { + this.PollOnce = false; + } + + protected void PollSub() { + if (this.Polling || this.PollOnce) { + this.PollOnce = false; + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Get(" + this.SensorId + ")"); + } + } + + protected void PollPerSub() { + foreach (KeyValuePair item in this.Sub) { + item.Value.Poll(); + } + } + + protected void PollSubGlobal() { + Boolean poll = false; + foreach (KeyValuePair item in this.Sub) { + if (item.Value.Polling) { + poll = true; + break; + } + } + if (poll) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Get()"); + } + } + + #endregion + + #region SetValues + + protected void SetInt(Int32 value) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Set(" + value + ")"); + } + + protected void SetTuple(Double value1, Double value2) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Set(" + value1 + "," + value2 + ")"); + } + + protected void SetTriple(Double v1, Double v2, Double v3) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Set(" + v1 + "," + v2 + "," + v3 + ")"); + } + + protected Boolean CheckSetUpdateTime(JsonData json) { + if (json.Keys.Contains("updateTime") && (json["updateTime"].IsInt || json["updateTime"].IsLong)) { + DateTime newdate = DateTimeOffset.FromUnixTimeSeconds(Int64.Parse(json["updateTime"].ToString())).ToLocalTime().DateTime; + if (newdate > this.LastUpdate) { + this.LastUpdate = newdate; + return true; + } else { + return false; + } + } else { + return true; + } + } + + public virtual void Reset() { + if (this.HasReset) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Reset()"); + } + } + + #endregion + + #region Output + + public String MqttTopic() { + return this.DeviceId + "/" + this.Instance + "/" + ((Int32)this.Commandclass).ToString() + (this.IsSub ? "/" + this.SensorId : ""); + } + + public String ToJson() { + Dictionary json = this.ToDictionary(); + json.Add("date", this.LastUpdate.ToString()); + json.Add("name", this.Name); + json.Add("class", this.Commandclass.ToString()); + return JsonMapper.ToJson(json); + } + + public abstract Dictionary ToDictionary(); + + protected Dictionary ToDictionarySub() { + Dictionary json = new Dictionary(); + foreach (KeyValuePair item in this.Sub) { + json.Add(item.Key.ToString(), item.Value.ToDictionary()); + } + return json; + } + + #endregion + + internal abstract void SetUpdate(JsonData json, Match match); + } +} diff --git a/Zway/Zway/Properties/AssemblyInfo.cs b/Zway/Zway/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a4e2ce0 --- /dev/null +++ b/Zway/Zway/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Zway")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Zway")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("166258ed-cb3d-43f5-8e8d-3a993b64d022")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.3.2.0")] +[assembly: AssemblyFileVersion("1.3.2.0")] diff --git a/Zway/Zway/Zway.csproj b/Zway/Zway/Zway.csproj new file mode 100644 index 0000000..6da4ab8 --- /dev/null +++ b/Zway/Zway/Zway.csproj @@ -0,0 +1,76 @@ + + + + + Debug + AnyCPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022} + Library + Properties + BlubbFish.IoT.Zway + Zway + v4.6.1 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\IoT-Bot\packages\LitJson.0.9.0\lib\LitJson.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Zway/Zway/ZwayController.cs b/Zway/Zway/ZwayController.cs new file mode 100644 index 0000000..00467ce --- /dev/null +++ b/Zway/Zway/ZwayController.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using BlubbFish.IoT.Zway.Devices; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway { + public class ZwayController : IDisposable { + private HttpConnection http; + private Thread updatethread; + private Thread pollthread; + public static ReadOnlyDictionary namelist; + private Boolean polling; + + public delegate void DataUpdate(Object sender, DeviceUpdateEvent e); + public event DataUpdate Update; + + public ReadOnlyDictionary Devices { get; private set; } + + public ZwayController(String server, String user, String pass, Dictionary names, Boolean enablePoll = true) { + namelist = new ReadOnlyDictionary(names); + this.polling = enablePoll; + this.Connect(server, user, pass); + } + + public ZwayController(Dictionary settings, Dictionary names, Boolean enablePoll = true) { + namelist = new ReadOnlyDictionary(names); + this.polling = enablePoll; + this.Connect(settings["server"], settings["user"], settings["pass"]); + } + + private void Connect(String server, String user, String pass) { + this.http = new HttpConnection(server, user, pass); + CultureInfo info = new CultureInfo("de-DE"); + info.NumberFormat.NumberDecimalSeparator = "."; + CultureInfo.DefaultThreadCurrentCulture = info; + CultureInfo.DefaultThreadCurrentUICulture = info; + Thread.CurrentThread.CurrentCulture = info; + Thread.CurrentThread.CurrentUICulture = info; + this.CreateDevices(this.http.GetJson("ZWaveAPI/Run/devices")); + this.updatethread = new Thread(this.Updater); + this.updatethread.Start(); + this.pollthread = new Thread(this.Poll); + this.pollthread.Start(); + foreach (KeyValuePair item in this.Devices) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(sender, e); + } + + private void Poll() { + CultureInfo info = new CultureInfo("de-DE"); + info.NumberFormat.NumberDecimalSeparator = "."; + CultureInfo.DefaultThreadCurrentCulture = info; + CultureInfo.DefaultThreadCurrentUICulture = info; + Thread.CurrentThread.CurrentCulture = info; + Thread.CurrentThread.CurrentUICulture = info; + while(true) { + foreach (KeyValuePair item in this.Devices) { + item.Value.Poll(); + } + Thread.Sleep(1000); + } + } + + public ACommandClass GetCommandClass(String id) { + String[] addr = id.Split('-'); + if (addr.Length == 3 || addr.Length == 4) { + return this.GetCommandClass(Int32.Parse(addr[0]), Int32.Parse(addr[1]), (ACommandClass.Classes)Int32.Parse(addr[2]), (addr.Length == 4 ? Int32.Parse(addr[3]) : -1)); + } + return null; + } + + public ACommandClass GetCommandClass(Int32 deviceid, Int32 instanceid, ACommandClass.Classes classid, Int32 subcid = -1) { + if(this.Devices.ContainsKey(deviceid) && this.Devices[deviceid].Instances.ContainsKey(instanceid) && this.Devices[deviceid].Instances[instanceid].CommandClasses.ContainsKey(classid)) { + ACommandClass commandclass = this.Devices[deviceid].Instances[instanceid].CommandClasses[classid]; + if(subcid == -1) { + return commandclass; + } + if (commandclass != null) { + if(commandclass.HasSub) { + if(commandclass.Sub.ContainsKey(subcid)) { + return commandclass.Sub[subcid]; + } + } + } + } + return null; + } + + private void Updater() { + CultureInfo info = new CultureInfo("de-DE"); + info.NumberFormat.NumberDecimalSeparator = "."; + CultureInfo.DefaultThreadCurrentCulture = info; + CultureInfo.DefaultThreadCurrentUICulture = info; + Thread.CurrentThread.CurrentCulture = info; + Thread.CurrentThread.CurrentUICulture = info; + while (true) { + Int64 date = ((DateTimeOffset)DateTime.Now.AddSeconds(-2)).ToUnixTimeSeconds(); + JsonData notifications = this.http.GetJson("ZWave.zway/Data/"+date); + foreach (String item in notifications.Keys) { + Match match = new Regex("^devices\\.([0-9]+)\\.instances\\.([0-9]+)\\.commandClasses\\.([0-9]+)(\\.data\\.([0-9]+)|\\.data\\.[a-z].*|\\.data)$", RegexOptions.IgnoreCase).Match(item); + if(match.Success) { + Int32 deviceid = Int32.Parse(match.Groups[1].Value); + Int32 instanceid = Int32.Parse(match.Groups[2].Value); + ACommandClass.Classes commandid = (ACommandClass.Classes)Int32.Parse(match.Groups[3].Value); + if (this.Devices.ContainsKey(deviceid) && this.Devices[deviceid].Instances.ContainsKey(instanceid) && this.Devices[deviceid].Instances[instanceid].CommandClasses.ContainsKey(commandid)) { + this.Devices[deviceid].Instances[instanceid].CommandClasses[commandid].SetUpdate(notifications[item], match); + } + } /*else { + if(item.StartsWith("controller.")) { } + else if(item == "updateTime") { } + else { + match = new Regex("^devices\\.([0-9]+)\\.(data.*)", RegexOptions.IgnoreCase).Match(item); + if(!match.Success) { + + } + } + }*/ + match = new Regex("^devices\\.([0-9]+)\\.(data.*)", RegexOptions.IgnoreCase).Match(item); + if(match.Success) { + Int32 deviceid = Int32.Parse(match.Groups[1].Value); + if (this.Devices.ContainsKey(deviceid)) { + this.Devices[deviceid].SetUpdate(notifications[item], match); + } + } + } + Thread.Sleep(1500); + } + } + + private void CreateDevices(JsonData json) { + Dictionary devices = new Dictionary(); + foreach (String deviceid in json.Keys) { + Device d = Device.CreateDevice(json[deviceid], new Tuple(Int32.Parse(deviceid)), this.http, this.polling); + if (d != null) { + devices.Add(Int32.Parse(deviceid), d); + } + } + this.Devices = new ReadOnlyDictionary(devices); + } + + public Dictionary GetCommandClasses(ACommandClass.Classes classes) { + Dictionary ret = new Dictionary(); + foreach (KeyValuePair device in this.Devices) { + foreach (KeyValuePair instance in device.Value.Instances) { + foreach (KeyValuePair commandclass in instance.Value.CommandClasses) { + if (commandclass.Key == classes) { + ret.Add(commandclass.Value.Id, commandclass.Value); + } + } + } + } + return ret; + } + + #region IDisposable Support + private Boolean disposedValue = false; + + protected virtual void Dispose(Boolean disposing) { + if (!this.disposedValue) { + if (disposing) { + this.updatethread.Abort(); + this.pollthread.Abort(); + } + this.updatethread = null; + this.pollthread = null; + this.Devices = null; + this.disposedValue = true; + } + } + + ~ZwayController() { + Dispose(false); + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/Zway/Zway/bin/Release/LitJson.dll b/Zway/Zway/bin/Release/LitJson.dll new file mode 100644 index 0000000..f00f11f Binary files /dev/null and b/Zway/Zway/bin/Release/LitJson.dll differ diff --git a/Zway/Zway/bin/Release/Zway.dll b/Zway/Zway/bin/Release/Zway.dll new file mode 100644 index 0000000..fc9ccf3 Binary files /dev/null and b/Zway/Zway/bin/Release/Zway.dll differ diff --git a/Zway/Zway/lib/Helper.cs b/Zway/Zway/lib/Helper.cs new file mode 100644 index 0000000..cf6df21 --- /dev/null +++ b/Zway/Zway/lib/Helper.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlubbFish.IoT.Zway.lib { + class Helper { + internal static void WriteError(String text) { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("ERROR: "+text); + Console.ResetColor(); + } + } +} diff --git a/Zway/Zway/lib/HttpClient.cs b/Zway/Zway/lib/HttpClient.cs new file mode 100644 index 0000000..72569cc --- /dev/null +++ b/Zway/Zway/lib/HttpClient.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using LitJson; + +namespace BlubbFish.IoT.Zway.lib { + public class HttpConnection { + private String auth; + private String server; + private Object getLock = new Object(); + + internal HttpConnection(String server, String user, String pass) { + this.auth = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + pass)); + this.server = "http://" + server + ":8083/"; + this.Init(); + } + + private void Init() { + this.GetString("ZAutomation/api/v1/status"); + } + + internal JsonData GetJson(String v) { + String text = this.GetString(v); + if(text == null) { + return new JsonData(); + } + try { + return JsonMapper.ToObject(text); + } catch(Exception) { + return new JsonData(); + } + } + + internal void GetVoid(String v) { + this.GetString(v, false); + } + + private String GetString(String v, Boolean withoutput = true) { + String ret = null; + lock (this.getLock) { + HttpWebRequest request = WebRequest.CreateHttp(this.server + v); + request.Timeout = 5000; + request.Headers.Add(HttpRequestHeader.Authorization, this.auth); + try { + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { + if (response.StatusCode == HttpStatusCode.Unauthorized) { + Console.Error.WriteLine("Benutzer oder Passwort falsch!"); + throw new Exceptions.ConnectionException("Benutzer oder Passwort falsch!"); + } + if (withoutput) { + StreamReader reader = new StreamReader(response.GetResponseStream()); + ret = reader.ReadToEnd(); + } + } + } catch (Exception e) { + Helper.WriteError("Konnte keine Verbindung zum Razzbery Server herstellen. Resource: \"" + this.server + v + "\" Fehler: " + e.Message); + return null; + //throw new Exceptions.ConnectionException("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); + } + } + return ret; + } + } +} diff --git a/Zway/Zway/packages.config b/Zway/Zway/packages.config new file mode 100644 index 0000000..eb9ba23 --- /dev/null +++ b/Zway/Zway/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file