From d43947105157f28bd62776024a95a818af95fd77 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Sun, 17 Dec 2017 20:05:03 +0000 Subject: [PATCH] [NF] v1.3.0.0 Remove Interface ICommandClass.cs and dublicated classes CommandClass.cs and CommandClassSub.cs, put them into the abstracted class ACommandClass.cs [NF] v1.2.0.0 Change classid to enum, so its more readable [NF] v1.1.0.0 Rename a lot of internal functions --- Zway/Devices/CommandClass.cs | 103 -------- Zway/Devices/CommandClassSub.cs | 52 ---- Zway/Devices/CommandClasses/Battery.cs | 11 +- Zway/Devices/CommandClasses/CentralScene.cs | 12 +- .../CommandClassSubs/Configurationsub.cs | 22 +- .../CommandClassSubs/MeterSub.cs | 23 +- .../CommandClassSubs/SensorMultilevelSub.cs | 17 +- .../CommandClassSubs/ThermostatSetPointSub.cs | 29 ++- Zway/Devices/CommandClasses/Configuration.cs | 22 +- Zway/Devices/CommandClasses/Indicator.cs | 11 +- Zway/Devices/CommandClasses/Meter.cs | 30 +-- .../CommandClasses/SensorMultilevel.cs | 30 +-- Zway/Devices/CommandClasses/SwitchBinary.cs | 11 +- .../CommandClasses/SwitchMultilevel.cs | 13 +- Zway/Devices/CommandClasses/ThermostatMode.cs | 16 +- .../CommandClasses/ThermostatSetPoint.cs | 23 +- Zway/Devices/CommandClasses/Wakeup.cs | 19 +- Zway/Devices/Instance.cs | 19 +- Zway/Interfaces/ACommandClass.cs | 231 ++++++++++++++++++ Zway/Interfaces/ICommandClass.cs | 14 -- Zway/Properties/AssemblyInfo.cs | 4 +- Zway/Zway.csproj | 4 +- Zway/ZwayController.cs | 39 +-- Zway/bin/Release/Zway.dll | Bin 48640 -> 50176 bytes 24 files changed, 439 insertions(+), 316 deletions(-) delete mode 100644 Zway/Devices/CommandClass.cs delete mode 100644 Zway/Devices/CommandClassSub.cs create mode 100644 Zway/Interfaces/ACommandClass.cs delete mode 100644 Zway/Interfaces/ICommandClass.cs diff --git a/Zway/Devices/CommandClass.cs b/Zway/Devices/CommandClass.cs deleted file mode 100644 index b2449fb..0000000 --- a/Zway/Devices/CommandClass.cs +++ /dev/null @@ -1,103 +0,0 @@ -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 { - public abstract class CommandClass : ICommandClass { - protected HttpConnection http; - - public abstract event UpdatedValue Update; - private enum Ignored { - Basic = 32, - ManufacturerSpecific = 114, - PowerLevel = 115, - Protection = 117, - NodeNaming = 119, - FirmwareUpdate = 122, - Association = 133, - Version = 134, - MultiChannelAssociation = 142, - MultiCmd = 143 - } - - public Int32 DeviceId { get; } - public Int32 Instance { get; } - public Int32 Commandclass { get; } - public String Id { get; } - public DateTime LastUpdate { get; private set; } - public String Name { get; } - public Boolean Polling { get; set; } - - protected CommandClass(JsonData json, HttpConnection http, Tuple id, 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.Id = this.DeviceId + "-" + this.Instance + "-" + this.Commandclass; - if (ZwayController.namelist.ContainsKey(this.Id)) { - this.Name = ZwayController.namelist[this.Id]; - } - } - - 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; - } - } - - internal static CommandClass 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(Ignored), id.Item3)) { - String name = json["name"].ToString(); - String objectName = "BlubbFish.IoT.Zway.Devices.CommandClasses." + name[0].ToString().ToUpper() + name.Substring(1).ToLower(); - return GetInstanceConcrete(objectName, json, http, id, polling); - } - return null; - } - - private static CommandClass 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 (CommandClass)t.GetConstructor(new Type[] { typeof(JsonData), typeof(HttpConnection), typeof(Tuple), typeof(Boolean) }).Invoke(new Object[] { json, http, id, polling }); - } - - internal abstract void SetUpdate(JsonData json, Match match); - - internal virtual void Poll() { - if(this.Polling) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get()"); - } - } - - protected void SetInt(Int32 value) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Set("+value+")"); - } - - protected void SetIntTuple(Int32 value1, Int32 value2) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Set(" + value1 + "," + value2 + ")"); - } - } -} \ No newline at end of file diff --git a/Zway/Devices/CommandClassSub.cs b/Zway/Devices/CommandClassSub.cs deleted file mode 100644 index 708a4fd..0000000 --- a/Zway/Devices/CommandClassSub.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using BlubbFish.IoT.Zway.Interfaces; -using BlubbFish.IoT.Zway.lib; -using LitJson; - -namespace BlubbFish.IoT.Zway.Devices { - public abstract class CommandClassSub : ICommandClass { - protected HttpConnection http; - - public abstract event UpdatedValue Update; - public String Id { get; } - public Int32 DeviceId { get; } - public Int32 Instance { get; } - public Int32 Commandclass { get; } - public Int32 SensorId { get; } - public String Name { get; } - public DateTime LastUpdate { get; private set; } - public Boolean Polling { get; set; } - - protected CommandClassSub(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.Id = this.DeviceId + "-" + this.Instance + "-" + this.Commandclass + "-" + this.SensorId; - if (ZwayController.namelist.ContainsKey(this.Id)) { - this.Name = ZwayController.namelist[this.Id]; - } - } - - 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; - } - } - internal abstract void SetUpdate(JsonData json, Match match); - - internal abstract void Poll(); - } -} diff --git a/Zway/Devices/CommandClasses/Battery.cs b/Zway/Devices/CommandClasses/Battery.cs index 54fb186..4b830dc 100644 --- a/Zway/Devices/CommandClasses/Battery.cs +++ b/Zway/Devices/CommandClasses/Battery.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -9,13 +10,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 128 = Battery /// - public class Battery : CommandClass { + public class Battery : ACommandClass { public Single Level { get; private set; } public override event UpdatedValue Update; - public Battery(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Battery(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.InitComplex(json); } @@ -50,5 +51,11 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { public override String ToString() { return "Battery " + this.Name + " [" + this.Id + "]: " + this.Level; } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + }; + } } } diff --git a/Zway/Devices/CommandClasses/CentralScene.cs b/Zway/Devices/CommandClasses/CentralScene.cs index 15535a3..c189899 100644 --- a/Zway/Devices/CommandClasses/CentralScene.cs +++ b/Zway/Devices/CommandClasses/CentralScene.cs @@ -11,14 +11,14 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 91 = CentralScene /// - public class Centralscene : CommandClass { + 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, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Centralscene(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.ValidScenesModes = new ReadOnlyDictionary>(new Dictionary>()); this.InitComplex(json); } @@ -74,7 +74,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { return "CentralScene " + this.Name + " [" + this.Id + "]: " + this.Scene+"-"+this.Key; } - internal override void Poll() { + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "scene", this.Scene.ToString() }, + { "key", this.Key.ToString() }, + }; } } } diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs index 022f1f7..89cc335 100644 --- a/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs +++ b/Zway/Devices/CommandClasses/CommandClassSubs/Configurationsub.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -6,10 +7,9 @@ using BlubbFish.IoT.Zway.lib; using LitJson; namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { - class Configurationsub : CommandClassSub { - private Int64 _level; - + class Configurationsub : ACommandClass { public override event UpdatedValue Update; + private Int64 _level; public Int64 Level { get { @@ -27,7 +27,8 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { } public Int32 Size { get; private set; } - public Configurationsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + public Configurationsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; InitComplex(json); } @@ -42,11 +43,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { } } - internal override void Poll() { - if(this.Polling) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get("+this.SensorId+")"); - } - } + 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 && @@ -61,5 +58,12 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { public override String ToString() { return "Configuration " + this.Name + " [" + this.Id + "]: " + this.Level; } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + { "size", this.Size.ToString() }, + }; + } } } \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs index 3527205..519fd90 100644 --- a/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs +++ b/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -6,20 +7,18 @@ using BlubbFish.IoT.Zway.lib; using LitJson; namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { - public class Metersub : CommandClassSub { - + public class Metersub : ACommandClass { public override event UpdatedValue Update; + public String Type { get; private set; } public Single 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) { + public Metersub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.HasReset = true; + this.IsSub = true; InitComplex(json); } - public void Reset() { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Reset()"); - } - private void InitComplex(JsonData json) { if (json.Keys.Contains("sensorTypeString") && json["sensorTypeString"].Keys.Contains("value") && @@ -49,6 +48,14 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { return "Meter " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale; } - internal override void Poll() { } + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + { "type", this.Type }, + { "scale", this.Scale }, + }; + } } } \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs index 3aaa04a..a065352 100644 --- a/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs +++ b/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -6,13 +7,15 @@ using BlubbFish.IoT.Zway.lib; using LitJson; namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { - public class Sensormultilevelsub : CommandClassSub { + public class Sensormultilevelsub : ACommandClass { public override event UpdatedValue Update; + public String Type { get; private set; } public Single 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) { + public Sensormultilevelsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; InitComplex(json); } @@ -45,6 +48,14 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { return "SensorMultilevel " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale; } - internal override void Poll() { } + internal override void Poll() => this.PollNone(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + { "type", this.Type }, + { "scale", this.Scale }, + }; + } } } \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs index 5702100..dc87b7c 100644 --- a/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs +++ b/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -6,19 +7,17 @@ using BlubbFish.IoT.Zway.lib; using LitJson; namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { - class Thermostatsetpointsub : CommandClassSub { - private Single _level; - + class Thermostatsetpointsub : ACommandClass { public override event UpdatedValue Update; + private Single _level; public Single Level { get { return this._level; } set { if (!this.HasMinMax || (this.HasMinMax && value >= this.TempMin && value <= this.TempMax)) { - Single t = (Single)Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2; - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Set(" + this.SensorId + "," + t + ")"); + this.SetTuple(this.SensorId, (Single)Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2); } } } @@ -28,7 +27,8 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { 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) { + public Thermostatsetpointsub(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.IsSub = true; InitComplex(json); } @@ -40,7 +40,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { this._level = Single.Parse(json["val"]["value"].ToString()); this.Scale = json["deviceScaleString"]["value"].ToString(); } - if(json.Keys.Contains("min") && json["min"].Keys.Contains("value") && + if (json.Keys.Contains("min") && json["min"].Keys.Contains("value") && json.Keys.Contains("max") && json["max"].Keys.Contains("value")) { this.TempMin = Single.Parse(json["min"]["value"].ToString()); this.TempMax = Single.Parse(json["max"]["value"].ToString()); @@ -78,10 +78,17 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { return "ThermostatSetPoint " + this.Name + " [" + this.Id + "]: " + this.Type + " " + this.Level + "" + this.Scale + " [" + this.TempMin + "," + this.TempMax + "," + this.HasMinMax + "]"; } - internal override void Poll() { - if (this.Polling) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get(" + this.SensorId + ")"); - } + internal override void Poll() => this.PollSub(); + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + { "type", this.Type }, + { "scale", this.Scale }, + { "tempmax", this.TempMax.ToString() }, + { "tempmin", this.TempMin.ToString() }, + { "hasminmax", this.HasMinMax.ToString() }, + }; } } } \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/Configuration.cs b/Zway/Devices/CommandClasses/Configuration.cs index bc44751..ec99a4b 100644 --- a/Zway/Devices/CommandClasses/Configuration.cs +++ b/Zway/Devices/CommandClasses/Configuration.cs @@ -12,13 +12,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 112 = Configuration /// - class Configuration : CommandClass { + class Configuration : ACommandClass { public override event UpdatedValue Update; - public ReadOnlyDictionary Sub { get; private set; } - public Configuration(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + 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) { + foreach (KeyValuePair item in this.Sub) { item.Value.Update += this.DeviceUpdate; } } @@ -30,17 +30,17 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { private void InitComplex(JsonData json) { if (json.Keys.Contains("data")) { JsonData data = json["data"]; - Dictionary subs = new Dictionary(); + 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)); + 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); + this.Sub = new ReadOnlyDictionary(subs); } } @@ -55,10 +55,8 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } } - internal override void Poll() { - foreach (KeyValuePair item in this.Sub) { - item.Value.Poll(); - } - } + internal override void Poll() => this.PollPerSub(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); } } diff --git a/Zway/Devices/CommandClasses/Indicator.cs b/Zway/Devices/CommandClasses/Indicator.cs index b068258..2746daa 100644 --- a/Zway/Devices/CommandClasses/Indicator.cs +++ b/Zway/Devices/CommandClasses/Indicator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -9,7 +10,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 135 = Indicator /// - class Indicator : CommandClass { + class Indicator : ACommandClass { private Boolean _level; public override event UpdatedValue Update; @@ -22,7 +23,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } } - public Indicator(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Indicator(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.InitComplex(json); } @@ -46,5 +47,11 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { public override String ToString() { return "Indicator " + this.Name + " [" + this.Id + "]: " + this.Level; } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() }, + }; + } } } diff --git a/Zway/Devices/CommandClasses/Meter.cs b/Zway/Devices/CommandClasses/Meter.cs index 49cd895..c0836f0 100644 --- a/Zway/Devices/CommandClasses/Meter.cs +++ b/Zway/Devices/CommandClasses/Meter.cs @@ -12,15 +12,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 50 = Meter /// - public class Meter : CommandClass { + public class Meter : ACommandClass { public override event UpdatedValue Update; - public ReadOnlyDictionary Sub { get; private set; } - - - public Meter(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + 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) { + foreach (KeyValuePair item in this.Sub) { item.Value.Update += this.DeviceUpdate; } } @@ -43,29 +41,21 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { private void InitComplex(JsonData json) { if (json.Keys.Contains("data")) { JsonData data = json["data"]; - Dictionary subs = new Dictionary(); + 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)); + 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); + this.Sub = new ReadOnlyDictionary(subs); } } - internal override void Poll() { - Boolean poll = false; - foreach (KeyValuePair item in this.Sub) { - if (item.Value.Polling) { - poll = true; - } - } - if (poll) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get()"); - } - } + internal override void Poll() => this.PollSubGlobal(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); } } diff --git a/Zway/Devices/CommandClasses/SensorMultilevel.cs b/Zway/Devices/CommandClasses/SensorMultilevel.cs index 26df0da..fd10cd4 100644 --- a/Zway/Devices/CommandClasses/SensorMultilevel.cs +++ b/Zway/Devices/CommandClasses/SensorMultilevel.cs @@ -12,15 +12,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 49 = SensorMultilevel /// - public class Sensormultilevel : CommandClass { + public class Sensormultilevel : ACommandClass { public override event UpdatedValue Update; - public ReadOnlyDictionary Sub { get; private set; } - - - public Sensormultilevel(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + 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) { + foreach (KeyValuePair item in this.Sub) { item.Value.Update += this.DeviceUpdate; } } @@ -43,29 +41,21 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { private void InitComplex(JsonData json) { if (json.Keys.Contains("data")) { JsonData data = json["data"]; - Dictionary subs = new Dictionary(); + 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)); + 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); + this.Sub = new ReadOnlyDictionary(subs); } } - internal override void Poll() { - Boolean poll = false; - foreach (KeyValuePair item in this.Sub) { - if (item.Value.Polling) { - poll = true; - } - } - if (poll) { - this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get()"); - } - } + internal override void Poll() => this.PollSubGlobal(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); } } diff --git a/Zway/Devices/CommandClasses/SwitchBinary.cs b/Zway/Devices/CommandClasses/SwitchBinary.cs index 84465ed..b3a2f04 100644 --- a/Zway/Devices/CommandClasses/SwitchBinary.cs +++ b/Zway/Devices/CommandClasses/SwitchBinary.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -9,7 +10,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 37 = SwitchBinary /// - public class Switchbinary : CommandClass { + public class Switchbinary : ACommandClass { private Boolean _level; public override event UpdatedValue Update; @@ -23,7 +24,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } } - public Switchbinary(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Switchbinary(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.InitComplex(json); } @@ -47,5 +48,11 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { public override String ToString() { return "SwitchBinary " + this.Name + " [" + this.Id + "]: " + this.Level; } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() } + }; + } } } diff --git a/Zway/Devices/CommandClasses/SwitchMultilevel.cs b/Zway/Devices/CommandClasses/SwitchMultilevel.cs index 525bc26..e1d960b 100644 --- a/Zway/Devices/CommandClasses/SwitchMultilevel.cs +++ b/Zway/Devices/CommandClasses/SwitchMultilevel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -9,7 +10,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 38 = SwitchMultilevel /// - public class Switchmultilevel : CommandClass { + public class Switchmultilevel : ACommandClass { private Int32 _level; public override event UpdatedValue Update; @@ -20,12 +21,12 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } set { if(value == 0 || (value > 0 && value <= 99) || value == 255) { - this.SetIntTuple(value, 0); + this.SetTuple(value, 0); } } } - public Switchmultilevel(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Switchmultilevel(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.InitComplex(json); } @@ -50,5 +51,11 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { public override String ToString() { return "SwitchMultilevel " + this.Name + " [" + this.Id + "]: " + this.Level; } + + public override Dictionary ToDictionary() { + return new Dictionary { + { "level", this.Level.ToString() } + }; + } } } diff --git a/Zway/Devices/CommandClasses/ThermostatMode.cs b/Zway/Devices/CommandClasses/ThermostatMode.cs index 6362ac4..4900a64 100644 --- a/Zway/Devices/CommandClasses/ThermostatMode.cs +++ b/Zway/Devices/CommandClasses/ThermostatMode.cs @@ -11,7 +11,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 64 = ThermostatMode /// - public class Thermostatmode : CommandClass { + public class Thermostatmode : ACommandClass { private Int32 _level; public override event UpdatedValue Update; @@ -26,7 +26,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } } - public Thermostatmode(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Thermostatmode(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.ValidModes = new ReadOnlyDictionary(new Dictionary()); this.InitComplex(json); } @@ -64,5 +64,17 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { 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.ToString() }, + { "modes", modes } + }; + return json; + } } } diff --git a/Zway/Devices/CommandClasses/ThermostatSetPoint.cs b/Zway/Devices/CommandClasses/ThermostatSetPoint.cs index 1e38eac..dd1d66d 100644 --- a/Zway/Devices/CommandClasses/ThermostatSetPoint.cs +++ b/Zway/Devices/CommandClasses/ThermostatSetPoint.cs @@ -12,14 +12,13 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 67 = ThermostatSetPoint /// - public class Thermostatsetpoint : CommandClass { + public class Thermostatsetpoint : ACommandClass { public override event UpdatedValue Update; - public ReadOnlyDictionary Sub { get; private set; } - - public Thermostatsetpoint(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + 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) { + foreach (KeyValuePair item in this.Sub) { item.Value.Update += this.DeviceUpdate; } } @@ -42,23 +41,21 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { private void InitComplex(JsonData json) { if (json.Keys.Contains("data")) { JsonData data = json["data"]; - Dictionary subs = new Dictionary(); + 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)); + 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); + this.Sub = new ReadOnlyDictionary(subs); } } - internal override void Poll() { - foreach (KeyValuePair item in this.Sub) { - item.Value.Poll(); - } - } + internal override void Poll() => this.PollPerSub(); + + public override Dictionary ToDictionary() => this.ToDictionarySub(); } } diff --git a/Zway/Devices/CommandClasses/Wakeup.cs b/Zway/Devices/CommandClasses/Wakeup.cs index 17ae0a0..e7adb35 100644 --- a/Zway/Devices/CommandClasses/Wakeup.cs +++ b/Zway/Devices/CommandClasses/Wakeup.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.Interfaces; @@ -9,7 +10,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { /// /// 132 = Wakeup /// - class Wakeup : CommandClass { + class Wakeup : ACommandClass { private Int32 _interval; private Int32 _againstNode; @@ -20,7 +21,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { } set { if(value >= this.WakeupMin && value <= this.WakeupMax) { - this.SetIntTuple(value, this.AgainstNode); + this.SetTuple(value, this.AgainstNode); } } } @@ -28,7 +29,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { return this._againstNode; } set { - this.SetIntTuple(this.Interval, value); + this.SetTuple(this.Interval, value); } } public Int32 WakeupMin { get; private set; } @@ -37,7 +38,7 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { public DateTime LastWakeup { get; private set; } public DateTime LastSleep { get; private set; } - public Wakeup(JsonData json, HttpConnection http, Tuple id, Boolean polling) : base(json, http, id, polling) { + public Wakeup(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { this.InitComplex(json); } @@ -120,5 +121,15 @@ namespace BlubbFish.IoT.Zway.Devices.CommandClasses { 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 { + { "wakeupmin", this.WakeupMin.ToString() }, + { "wakeupmax", this.WakeupMax.ToString() }, + { "wakeupdefault", this.WakeupDefault.ToString() }, + { "lastwakeup", this.LastWakeup.ToString() }, + { "lastsleep", this.LastSleep.ToString() } + }; + } } } diff --git a/Zway/Devices/Instance.cs b/Zway/Devices/Instance.cs index e796f82..72a75a6 100644 --- a/Zway/Devices/Instance.cs +++ b/Zway/Devices/Instance.cs @@ -3,6 +3,7 @@ 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; @@ -16,22 +17,22 @@ namespace BlubbFish.IoT.Zway.Devices { public Int32 DeviceId { get; } public Int32 InstanceId { get; } - public ReadOnlyDictionary CommandClasses { get; private set; } + 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) { + foreach (KeyValuePair item in this.CommandClasses) { item.Value.Update += this.ClassUpdate; } this.MakePolltimer(); } private void MakePolltimer() { - if(this.CommandClasses.ContainsKey(132)) { - this.nextwakeup = ((Wakeup)this.CommandClasses[132]).LastWakeup.AddSeconds(((Wakeup)this.CommandClasses[132]).Interval).AddSeconds(-20); + 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); } @@ -42,14 +43,14 @@ namespace BlubbFish.IoT.Zway.Devices { } private void CreateInstances(JsonData json, HttpConnection http) { - Dictionary commands = new Dictionary(); + Dictionary commands = new Dictionary(); foreach (String commandid in json.Keys) { - CommandClass c = CommandClass.CreateInstance(json[commandid], new Tuple(this.DeviceId, this.InstanceId, Int32.Parse(commandid)), http, this.polling); + 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(Int32.Parse(commandid), c); + commands.Add((ACommandClass.Classes)Int32.Parse(commandid), c); } } - this.CommandClasses = new ReadOnlyDictionary(commands); + this.CommandClasses = new ReadOnlyDictionary(commands); } internal static Instance CreateInstance(JsonData json, Tuple id, HttpConnection http, Boolean polling) { @@ -68,7 +69,7 @@ namespace BlubbFish.IoT.Zway.Devices { internal void Poll() { if(DateTime.Now > this.nextwakeup) { this.MakePolltimer(); - foreach (KeyValuePair item in this.CommandClasses) { + foreach (KeyValuePair item in this.CommandClasses) { item.Value.Poll(); } } diff --git a/Zway/Interfaces/ACommandClass.cs b/Zway/Interfaces/ACommandClass.cs new file mode 100644 index 0000000..f1a765d --- /dev/null +++ b/Zway/Interfaces/ACommandClass.cs @@ -0,0 +1,231 @@ +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(Single value1, Single value2) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + ((Int32)this.Commandclass).ToString() + "].Set(" + value1 + "," + value2 + ")"); + } + + protected void SetTuple(Int32 v1, Int32 v2) => this.SetTuple(v1, (Single)v2); + + 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/Interfaces/ICommandClass.cs b/Zway/Interfaces/ICommandClass.cs deleted file mode 100644 index 33e31df..0000000 --- a/Zway/Interfaces/ICommandClass.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using BlubbFish.IoT.Zway.Events; - -namespace BlubbFish.IoT.Zway.Interfaces { - public delegate void UpdatedValue(Object sender, DeviceUpdateEvent e); - public interface ICommandClass { - String Name { get; } - String Id { get; } - DateTime LastUpdate { get; } - Boolean Polling { get; set; } - - event UpdatedValue Update; - } -} diff --git a/Zway/Properties/AssemblyInfo.cs b/Zway/Properties/AssemblyInfo.cs index 7c83d8c..e00e39e 100644 --- a/Zway/Properties/AssemblyInfo.cs +++ b/Zway/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // 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.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/Zway/Zway.csproj b/Zway/Zway.csproj index bb32775..380b95f 100644 --- a/Zway/Zway.csproj +++ b/Zway/Zway.csproj @@ -44,7 +44,6 @@ - @@ -53,9 +52,8 @@ - - + diff --git a/Zway/ZwayController.cs b/Zway/ZwayController.cs index 40e1742..d79234e 100644 --- a/Zway/ZwayController.cs +++ b/Zway/ZwayController.cs @@ -73,23 +73,24 @@ namespace BlubbFish.IoT.Zway { } } - public ICommandClass GetCommandClass(Int32 deviceid, Int32 instanceid, Int32 classid) { - if(this.Devices.ContainsKey(deviceid) && this.Devices[deviceid].Instances.ContainsKey(instanceid) && this.Devices[deviceid].Instances[instanceid].CommandClasses.ContainsKey(classid)) { - return this.Devices[deviceid].Instances[instanceid].CommandClasses[classid]; + 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 ICommandClass GetCommandClassSub(Int32 deviceid, Int32 instanceid, Int32 classid, Int32 subcid) { - ICommandClass comandclass = this.GetCommandClass(deviceid, instanceid, classid); - if (comandclass != null) { - foreach (PropertyInfo item in comandclass.GetType().GetProperties()) { - if (item.Name == "Sub") { - if (comandclass.GetType().GetProperty("Sub").PropertyType == typeof(ReadOnlyDictionary)) { - ReadOnlyDictionary commandclasssub = (ReadOnlyDictionary)comandclass.GetType().GetProperty("Sub").GetValue(comandclass); - if (commandclasssub.ContainsKey(subcid)) { - return commandclasssub[subcid]; - } + 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]; } } } @@ -112,7 +113,7 @@ namespace BlubbFish.IoT.Zway { if(match.Success) { Int32 deviceid = Int32.Parse(match.Groups[1].Value); Int32 instanceid = Int32.Parse(match.Groups[2].Value); - Int32 commandid = Int32.Parse(match.Groups[3].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); } @@ -149,13 +150,13 @@ namespace BlubbFish.IoT.Zway { this.Devices = new ReadOnlyDictionary(devices); } - public List GetCommandClasses() { - List ret = new List(); + 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.Value is T) { - ret.Add((T)Convert.ChangeType(commandclass.Value, typeof(T))); + foreach (KeyValuePair commandclass in instance.Value.CommandClasses) { + if (commandclass.Key == classes) { + ret.Add(commandclass.Value.Id, commandclass.Value); } } } diff --git a/Zway/bin/Release/Zway.dll b/Zway/bin/Release/Zway.dll index dd67fc566952d69c1bef0874c104e462360c2d47..d8bc8c7aab8c0dea5682b28cd058abea81400a56 100644 GIT binary patch literal 50176 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 literal 48640 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 |7}qxU`24F&e~p}H|PP} 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 P*Et-n|M&U-ody0MX_Rme