diff --git a/Zway/Devices/CommandClass.cs b/Zway/Devices/CommandClass.cs new file mode 100644 index 0000000..612ed9c --- /dev/null +++ b/Zway/Devices/CommandClass.cs @@ -0,0 +1,97 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + public abstract class CommandClass { + protected HttpConnection http; + private enum Ignored { + Basic = 32, + Configuration = 112, + 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 Name { get; } + + public DateTime LastUpdate { get; private set; } + + protected CommandClass(JsonData json, HttpConnection http, Tuple id) { + this.DeviceId = id.Item1; + this.Instance = id.Item2; + this.Commandclass = id.Item3; + this.Name = this.DeviceId + "-" + this.Instance + "-" + this.Commandclass; + this.http = http; + this.LastUpdate = DateTime.Now; + } + + 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) { + 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); + } + return null; + } + + private static CommandClass GetInstanceConcrete(String objectName, JsonData json, HttpConnection http, Tuple id) { + 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) }).Invoke(new Object[] { json, http, id }); + } + + protected abstract void InitComplex(JsonData json); + internal abstract void SetUpdate(JsonData json, Match match); + + public delegate void UpdatedValue(Object sender, DeviceUpdateEvent e); + public abstract event UpdatedValue Update; + + internal virtual void Poll() { + 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 new file mode 100644 index 0000000..891fbd9 --- /dev/null +++ b/Zway/Devices/CommandClassSub.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + public abstract class CommandClassSub { + protected HttpConnection http; + + 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; } + + protected CommandClassSub(JsonData json, Tuple id, HttpConnection http) { + this.DeviceId = id.Item1; + this.Instance = id.Item2; + this.Commandclass = id.Item3; + this.SensorId = id.Item4; + this.http = http; + this.Name = this.DeviceId + "-" + this.Instance + "-" + this.Commandclass + "-" + this.SensorId; + } + + 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; + } + } + + protected abstract void InitComplex(JsonData json); + internal abstract void SetUpdate(JsonData json, Match match); + + public delegate void UpdatedValue(Object sender, DeviceUpdateEvent e); + public abstract event UpdatedValue Update; + } +} diff --git a/Zway/Devices/CommandClasses/Battery.cs b/Zway/Devices/CommandClasses/Battery.cs new file mode 100644 index 0000000..93bd69f --- /dev/null +++ b/Zway/Devices/CommandClasses/Battery.cs @@ -0,0 +1,50 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Battery : CommandClass { + + public Single Level { get; private set; } + + public override event UpdatedValue Update; + + public Battery(JsonData json, HttpConnection http, Tuple id) : base(json, http, id) { + 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 = Single.Parse(json["last"]["value"].ToString()); + success = true; + } + } else if (match.Groups[4].Value == ".data.last") { + if (json.Keys.Contains("value")) { + this.Level = Single.Parse(json["value"].ToString()); + success = true; + } + } else if (match.Groups[4].Value.StartsWith(".data.history.")) { + } else if (match.Groups[4].Value == ".data.lastChange") { + } else { + throw new NotImplementedException(); + } + if (success && this.CheckSetUpdateTime(json)) { + this.Update?.Invoke(this, new DeviceUpdateEvent(this.Level, this.LastUpdate)); + } + } + + protected override void InitComplex(JsonData json) { + if(json.Keys.Contains("data") && json["data"].Keys.Contains("last") && json["data"]["last"].Keys.Contains("value")) { + this.Level = Single.Parse(json["data"]["last"]["value"].ToString()); + } + } + + public override String ToString() { + return "Battery " + this.Name + ": " + this.Level; + } + } +} diff --git a/Zway/Devices/CommandClasses/CentralScene.cs b/Zway/Devices/CommandClasses/CentralScene.cs new file mode 100644 index 0000000..aa68df4 --- /dev/null +++ b/Zway/Devices/CommandClasses/CentralScene.cs @@ -0,0 +1,76 @@ +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.CommandClasses { + public class Centralscene : CommandClass { + 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) : base(json, http, id) { + 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)); + } + } else { + throw new NotImplementedException(); + } + } + + protected override 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.Scene+"-"+this.Key; + } + + internal override void Poll() { + } + } +} diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs new file mode 100644 index 0000000..8c87280 --- /dev/null +++ b/Zway/Devices/CommandClasses/CommandClassSubs/MeterSub.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + class Metersub : CommandClassSub { + + 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) : base(json, id, http) { + InitComplex(json); + } + + public void Reset() { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Reset()"); + } + + protected override 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 = Single.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 = Single.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)); + } + } + + public override String ToString() { + return "Meter " + this.Name + ": " + this.Type + " " + this.Level + "" + this.Scale; + } + } +} \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs new file mode 100644 index 0000000..d19150a --- /dev/null +++ b/Zway/Devices/CommandClasses/CommandClassSubs/SensorMultilevelSub.cs @@ -0,0 +1,49 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + public class Sensormultilevelsub : CommandClassSub { + 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) : base(json, id, http) { + InitComplex(json); + } + + + + protected override 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 = Single.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 = Single.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)); + } + } + + public override String ToString() { + return "SensorMultilevel " + this.Name + ": " + this.Type + " " + this.Level + "" + this.Scale; + } + } +} \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs b/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs new file mode 100644 index 0000000..6a00477 --- /dev/null +++ b/Zway/Devices/CommandClasses/CommandClassSubs/ThermostatSetPointSub.cs @@ -0,0 +1,80 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs { + class Thermostatsetpointsub : CommandClassSub { + private Single _level; + + public override event UpdatedValue Update; + + 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 + ")"); + } + } + } + public String Scale { get; private set; } + public Single TempMax { get; private set; } + public Single TempMin { get; private set; } + public Boolean HasMinMax { get; private set; } + public String Type { get; private set; } + + public Thermostatsetpointsub(JsonData json, Tuple id, HttpConnection http) : base(json, id, http) { + InitComplex(json); + } + + protected override 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 = Single.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 = Single.Parse(json["min"]["value"].ToString()); + this.TempMax = Single.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 = Single.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 = Single.Parse(json["min"]["value"].ToString()); + this.TempMax = Single.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)); + } + } + + public override String ToString() { + return "ThermostatSetPoint " + this.Name + ": " + this.Type + " " + this.Level + "" + this.Scale + " [" + this.TempMin + "," + this.TempMax + "," + this.HasMinMax + "]"; + } + } +} \ No newline at end of file diff --git a/Zway/Devices/CommandClasses/Indicator.cs b/Zway/Devices/CommandClasses/Indicator.cs new file mode 100644 index 0000000..e69d497 --- /dev/null +++ b/Zway/Devices/CommandClasses/Indicator.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + class Indicator : CommandClass { + 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, HttpConnection http, Tuple id) : base(json, http, id) { + this.InitComplex(json); + } + + protected override 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)); + } + } else { + throw new NotImplementedException(); + } + } + + public override String ToString() { + return "Indicator " + this.Name + ": " + this.Level; + } + } +} diff --git a/Zway/Devices/CommandClasses/Meter.cs b/Zway/Devices/CommandClasses/Meter.cs new file mode 100644 index 0000000..588d03c --- /dev/null +++ b/Zway/Devices/CommandClasses/Meter.cs @@ -0,0 +1,55 @@ +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.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Meter : CommandClass { + public override event UpdatedValue Update; + + public ReadOnlyDictionary Sub { get; private set; } + + + public Meter(JsonData json, HttpConnection http, Tuple id) : base(json, http, id) { + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(sender, 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 { + throw new NotImplementedException(); + } + } + + protected override 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.Sub = new ReadOnlyDictionary(subs); + } + } + } +} diff --git a/Zway/Devices/CommandClasses/SensorMultilevel.cs b/Zway/Devices/CommandClasses/SensorMultilevel.cs new file mode 100644 index 0000000..9592ba3 --- /dev/null +++ b/Zway/Devices/CommandClasses/SensorMultilevel.cs @@ -0,0 +1,55 @@ +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.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Sensormultilevel : CommandClass { + public override event UpdatedValue Update; + + public ReadOnlyDictionary Sub { get; private set; } + + + public Sensormultilevel(JsonData json, HttpConnection http, Tuple id) : base(json, http, id) { + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(sender, 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 { + throw new NotImplementedException(); + } + } + + protected override 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.Sub = new ReadOnlyDictionary(subs); + } + } + } +} diff --git a/Zway/Devices/CommandClasses/SwitchBinary.cs b/Zway/Devices/CommandClasses/SwitchBinary.cs new file mode 100644 index 0000000..10a00fe --- /dev/null +++ b/Zway/Devices/CommandClasses/SwitchBinary.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Switchbinary : CommandClass { + 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, HttpConnection http, Tuple id) : base(json, http, id) { + 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)); + } + } else { + throw new NotImplementedException(); + } + } + + protected override 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.Level; + } + } +} diff --git a/Zway/Devices/CommandClasses/SwitchMultilevel.cs b/Zway/Devices/CommandClasses/SwitchMultilevel.cs new file mode 100644 index 0000000..f941077 --- /dev/null +++ b/Zway/Devices/CommandClasses/SwitchMultilevel.cs @@ -0,0 +1,50 @@ +using System; +using System.Text.RegularExpressions; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Switchmultilevel : CommandClass { + 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.SetIntTuple(value, 0); + } + } + } + + public Switchmultilevel(JsonData json, HttpConnection http, Tuple id) : base(json, http, id) { + 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)); + } + } else if (match.Groups[4].Value == ".data.prevLevel") { + } else { + throw new NotImplementedException(); + } + } + + protected override 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.Level; + } + } +} diff --git a/Zway/Devices/CommandClasses/ThermostatMode.cs b/Zway/Devices/CommandClasses/ThermostatMode.cs new file mode 100644 index 0000000..feb0745 --- /dev/null +++ b/Zway/Devices/CommandClasses/ThermostatMode.cs @@ -0,0 +1,64 @@ +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.CommandClasses { + public class Thermostatmode : CommandClass { + 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, HttpConnection http, Tuple id) : base(json, http, id) { + 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)); + } + } else { + throw new NotImplementedException(); + } + } + + protected override 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.ValidModes[this.Level]; + } + } +} diff --git a/Zway/Devices/CommandClasses/ThermostatSetPoint.cs b/Zway/Devices/CommandClasses/ThermostatSetPoint.cs new file mode 100644 index 0000000..b532a42 --- /dev/null +++ b/Zway/Devices/CommandClasses/ThermostatSetPoint.cs @@ -0,0 +1,60 @@ +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.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + public class Thermostatsetpoint : CommandClass { + public override event UpdatedValue Update; + + public ReadOnlyDictionary Sub { get; private set; } + + public Thermostatsetpoint(JsonData json, HttpConnection http, Tuple id) : base(json, http, id) { + this.InitComplex(json); + foreach (KeyValuePair item in this.Sub) { + item.Value.Update += this.DeviceUpdate; + } + } + + private void DeviceUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(sender, 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 { + throw new NotImplementedException(); + } + } + + protected override 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.Sub = new ReadOnlyDictionary(subs); + } + } + + internal override void Poll() { + foreach (KeyValuePair item in this.Sub) { + this.http.GetVoid("ZWave.zway/Run/devices[" + this.DeviceId + "].instances[" + this.Instance + "].commandClasses[" + this.Commandclass + "].Get(" + item.Key + ")"); + } + } + } +} diff --git a/Zway/Devices/CommandClasses/Wakeup.cs b/Zway/Devices/CommandClasses/Wakeup.cs new file mode 100644 index 0000000..1399dc1 --- /dev/null +++ b/Zway/Devices/CommandClasses/Wakeup.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices.CommandClasses { + class Wakeup : CommandClass { + 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.SetIntTuple(value, this.AgainstNode); + } + } + } + public Int32 AgainstNode { get { + return this._againstNode; + } + set { + this.SetIntTuple(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, HttpConnection http, Tuple id) : base(json, http, id) { + this.InitComplex(json); + } + + protected override 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 { + throw new NotImplementedException(); + } + if (success && this.CheckSetUpdateTime(json)) { + this.Update?.Invoke(this, new DeviceUpdateEvent(0, this.LastUpdate)); + } + } + + public override String ToString() { + return "Wakeup " + this.Name + ": " + this.LastWakeup + "-" + this.LastSleep + " " + this.Interval + " [" + this.WakeupMin + "," + this.WakeupMax + "," + this.WakeupDefault + "," + this.AgainstNode + "]"; + } + } +} diff --git a/Zway/Devices/Device.cs b/Zway/Devices/Device.cs new file mode 100644 index 0000000..d077ca5 --- /dev/null +++ b/Zway/Devices/Device.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + class Device { + private Dictionary _instances = new Dictionary(); + + public delegate void UpdatedDevice(Object sender, DeviceUpdateEvent e); + public event UpdatedDevice Update; + + public String Name { get; } + public Int32 Id { get; } + public Dictionary Instances { get { return this._instances; } } + public DateTime LastUpdate { get; private set; } + + private Device(JsonData json, Tuple id, HttpConnection http) { + this.Id = id.Item1; + foreach (String instanceid in json["instances"].Keys) { + this.CreateInstances(Int32.Parse(instanceid), json["instances"][instanceid], 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(sender, e); + } + + private void CreateInstances(Int32 instanceid, JsonData json, HttpConnection http) { + Instance i = Instance.CreateInstance(json, new Tuple(this.Id, instanceid), http); + if (i != null) { + this._instances.Add(instanceid, i); + } + } + + internal static Device CreateDevice(JsonData json, Tuple id, HttpConnection http) { + if(json.Keys.Contains("instances") && + json["instances"].Count > 0 && + json.Keys.Contains("data") && id.Item1 != 1) { + return new Device(json, id, http); + } + 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/Devices/Instance.cs b/Zway/Devices/Instance.cs new file mode 100644 index 0000000..892e03d --- /dev/null +++ b/Zway/Devices/Instance.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using BlubbFish.IoT.Zway.Devices.CommandClasses; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.lib; +using LitJson; + +namespace BlubbFish.IoT.Zway.Devices { + class Instance { + private Dictionary _commands = new Dictionary(); + private DateTime nextwakeup; + + public delegate void UpdatedInstance(Object sender, DeviceUpdateEvent e); + public event UpdatedInstance Update; + + public Dictionary CommandClasses { get { return this._commands; } } + + private Instance(JsonData json, Tuple id, HttpConnection http) { + this.Id = id.Item1; + this.Instanceid = id.Item2; + foreach (String commandid in json["commandClasses"].Keys) { + this.CreateInstances(Int32.Parse(commandid), json["commandClasses"][commandid], http); + } + 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); + } else { + this.nextwakeup = DateTime.Now.AddSeconds(60); + } + } + + private void ClassUpdate(Object sender, DeviceUpdateEvent e) { + this.Update?.Invoke(sender, e); + } + + private void CreateInstances(Int32 commandid, JsonData json, HttpConnection http) { + CommandClass c = CommandClass.CreateInstance(json, new Tuple(this.Id, this.Instanceid, commandid), http); + if (c != null) { + this._commands.Add(commandid, c); + } + } + + public Int32 Id { get; } + public Int32 Instanceid { get; } + + internal static Instance CreateInstance(JsonData json, Tuple id, HttpConnection http) { + if (json.Keys.Contains("commandClasses") && + json["commandClasses"].Count > 0 && + json.Keys.Contains("data")) { + return new Instance(json, id, http); + } + return null; + } + + public override String ToString() { + return "Instance: " + this.Id + "-" + this.Instanceid + " [" + this._commands.Count + "]"; + } + + internal void Poll() { + if(DateTime.Now > this.nextwakeup) { + this.MakePolltimer(); + foreach (KeyValuePair item in this.CommandClasses) { + item.Value.Poll(); + } + } + } + } +} diff --git a/Zway/Events/DeviceUpdateEvent.cs b/Zway/Events/DeviceUpdateEvent.cs new file mode 100644 index 0000000..9365702 --- /dev/null +++ b/Zway/Events/DeviceUpdateEvent.cs @@ -0,0 +1,50 @@ +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(Single value, DateTime time) { + this.GetSingle = value; + this.UpdateTime = time; + } + + public DeviceUpdateEvent(Boolean level, DateTime time) { + this.GetBoolean = level; + this.UpdateTime = time; + } + + public DeviceUpdateEvent(DateTime value, DateTime time) { + this.GetDate = value; + this.UpdateTime = time; + } + + public DeviceUpdateEvent(Tuple value, DateTime time) { + this.GetStringStringSingleTuple = value; + this.UpdateTime = time; + } + public DeviceUpdateEvent(Tuple value, DateTime time) { + this.GetStringStringSingleSingleSingleSingleBooleanTuple = value; + this.UpdateTime = time; + } + + public DeviceUpdateEvent(Tuple value, DateTime time) { + this.GetIntegerTuple = value; + this.UpdateTime = time; + } + + public Single GetSingle { get; } + public DateTime GetDate { get; } + public Tuple GetStringStringSingleTuple { get; } + public Tuple GetStringStringSingleSingleSingleSingleBooleanTuple { get; } + public DateTime UpdateTime { get; } + public Boolean GetBoolean { get; } + public Tuple GetIntegerTuple { get; } + } +} \ No newline at end of file diff --git a/Zway/Zway.csproj b/Zway/Zway.csproj index 0599ad0..b8405fd 100644 --- a/Zway/Zway.csproj +++ b/Zway/Zway.csproj @@ -30,6 +30,9 @@ 4 + + ..\..\IoT-Bot\packages\LitJson.0.9.0\lib\LitJson.dll + @@ -40,11 +43,32 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Zway/ZwayController.cs b/Zway/ZwayController.cs index 86aaf9e..aa13d5a 100644 --- a/Zway/ZwayController.cs +++ b/Zway/ZwayController.cs @@ -1,13 +1,22 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Threading; +using BlubbFish.IoT.Zway.Devices; +using BlubbFish.IoT.Zway.Events; using BlubbFish.IoT.Zway.lib; +using LitJson; namespace BlubbFish.IoT.Zway { - public class ZwayController { + public class ZwayController : IDisposable { private HttpConnection http; + private Dictionary devices = new Dictionary(); + private Thread updatethread; + private Thread pollthread; + + public delegate void DataUpdate(Object sender, DeviceUpdateEvent e); + public event DataUpdate Update; public ZwayController(String server, String user, String pass) { this.Connect(server, user, pass); @@ -19,6 +28,133 @@ namespace BlubbFish.IoT.Zway { 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.ParseNamespace(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); + } + } + + 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); + Int32 commandid = 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 ParseNamespace(JsonData json) { + foreach (String deviceid in json.Keys) { + this.CreateDevices(Int32.Parse(deviceid), json[deviceid]); + } + } + + private void CreateDevices(Int32 deviceid, JsonData json) { + Device d = Device.CreateDevice(json, new Tuple(deviceid), this.http); + if(d != null) { + this.devices.Add(deviceid, d); + } + } + + public List GetCommandClasses() { + List ret = new List(); + 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))); + } + } + } + } + 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.Clear(); + this.disposedValue = true; + } + } + + ~ZwayController() { + Dispose(false); + } + + void IDisposable.Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion } } diff --git a/Zway/lib/HttpClient.cs b/Zway/lib/HttpClient.cs index 5a46ac6..09a1a51 100644 --- a/Zway/lib/HttpClient.cs +++ b/Zway/lib/HttpClient.cs @@ -1,40 +1,65 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; +using System.Net; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using LitJson; namespace BlubbFish.IoT.Zway.lib { - internal class HttpConnection { - private HttpClient http; + public class HttpConnection { + private String auth; private String server; + private Object getLock = new Object(); internal HttpConnection(String server, String user, String pass) { - this.http = new HttpClient(); - //this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + pass))); - this.server = "http://" + server + ":8083"; + this.auth = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + pass)); + this.server = "http://" + server + ":8083/"; this.Init(); } - private async void Init() { - await this.GetString("/ZAutomation/api/v1/status"); + private void Init() { + this.GetString("ZAutomation/api/v1/status"); } - private async Task GetString(String v) { + internal JsonData GetJson(String v) { + String text = this.GetString(v); try { - HttpResponseMessage response = await this.http.GetAsync(this.server + v); - if(response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - Console.Error.WriteLine("Benutzer oder Passwort falsch!"); - throw new Exceptions.ConnectionException("Benutzer oder Passwort falsch!"); - } - HttpContent content = response.Content; - return await content.ReadAsStringAsync(); - } catch(HttpRequestException e) { - Console.Error.WriteLine("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); - throw new Exceptions.ConnectionException("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); + 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) { + Console.Error.WriteLine("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); + throw new Exceptions.ConnectionException("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); + } + } + return ret; + } } } diff --git a/Zway/packages.config b/Zway/packages.config new file mode 100644 index 0000000..1d1f601 --- /dev/null +++ b/Zway/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file