From 4178079f4d626866ff84c1b41cd5815593fc3f6f Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Tue, 1 May 2018 21:46:56 +0000 Subject: [PATCH] [NF] Hue-Lib fertig [DW] Hue-Bot beginn Arbeit --- Hue/Devices/Configs/Config.cs | 368 ++++++++++++++++++++++++++++ Hue/Devices/Configs/Portalstate.cs | 71 ++++++ Hue/Devices/Configs/SwUpdate.cs | 108 ++++++++ Hue/Devices/Configs/Whitelist.cs | 62 +++++ Hue/Devices/Groups/Room.cs | 54 ++-- Hue/Devices/Lights/Dimmablelight.cs | 51 +++- Hue/Devices/Scenes/LightState.cs | 49 ++-- Hue/Devices/Scenes/Scene.cs | 111 +++++++-- Hue/Devices/Sensors/Daylight.cs | 121 +++++++++ Hue/Events/UpdateEvent.cs | 14 ++ Hue/Hue.csproj | 7 + Hue/HueController.cs | 76 +++--- Hue/Interfaces/AConfig.cs | 34 +++ Hue/Interfaces/AConnector.cs | 74 +++++- Hue/Interfaces/AGroup.cs | 116 +++++---- Hue/Interfaces/ALight.cs | 56 +++-- Hue/Interfaces/AScene.cs | 82 +------ Hue/Interfaces/ASensor.cs | 127 ++++++++++ Hue/Interfaces/IMqtt.cs | 10 +- Hue/bin/Release/Hue.dll | Bin 0 -> 52736 bytes Hue/bin/Release/litjson.dll | Bin 0 -> 50688 bytes Hue/lib/Helper.cs | 30 +++ 22 files changed, 1376 insertions(+), 245 deletions(-) create mode 100644 Hue/Devices/Configs/Config.cs create mode 100644 Hue/Devices/Configs/Portalstate.cs create mode 100644 Hue/Devices/Configs/SwUpdate.cs create mode 100644 Hue/Devices/Configs/Whitelist.cs create mode 100644 Hue/Devices/Sensors/Daylight.cs create mode 100644 Hue/Interfaces/AConfig.cs create mode 100644 Hue/Interfaces/ASensor.cs create mode 100644 Hue/bin/Release/Hue.dll create mode 100644 Hue/bin/Release/litjson.dll diff --git a/Hue/Devices/Configs/Config.cs b/Hue/Devices/Configs/Config.cs new file mode 100644 index 0000000..5674980 --- /dev/null +++ b/Hue/Devices/Configs/Config.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Devices.Configs { + public class Config : AConfig { + #region Properties + private String _name; + public String Name { + get { + return this._name; + } + set { + if(this.SetConfig(new Dictionary() { { "name", value } })) { + this._name = value; + this.NotifyClient(value); + } + } + } + private Boolean _linkbutton; + public Boolean Linkbutton { + get { + return this._linkbutton; + } + set { + if (this.SetConfig(new Dictionary() { { "linkbutton", value } })) { + this._linkbutton = value; + this.NotifyClient(value); + } + } + } + private String _ipaddress; + public String Ipaddress { + get { + return this._ipaddress; + } + set { + if (this.SetConfig(new Dictionary() { { "ipaddress", value } })) { + this._ipaddress = value; + this.NotifyClient(value); + } + } + } + private String _netmask; + public String Netmask { + get { + return this._netmask; + } + set { + if (this.SetConfig(new Dictionary() { { "netmask", value } })) { + this._netmask = value; + this.NotifyClient(value); + } + } + } + private String _gateway; + public String Gateway { + get { + return this._gateway; + } + set { + if (this.SetConfig(new Dictionary() { { "gateway", value } })) { + this._gateway = value; + this.NotifyClient(value); + } + } + } + private Boolean _dhcp; + public Boolean Dhcp { + get { + return this._dhcp; + } + set { + if (this.SetConfig(new Dictionary() { { "dhcp", value } })) { + this._dhcp = value; + this.NotifyClient(value); + } + } + } + private DateTime _utc; + public DateTime Utc { + get { + return this._utc; + } + set { + if (this.SetConfig(new Dictionary() { { "UTC", value } })) { + this._utc = value; + this.NotifyClient(value); + } + } + } + private String _timezone; + public String Timezone { + get { + return this._timezone; + } + set { + if (this.SetConfig(new Dictionary() { { "timezone", value } })) { + this._timezone = value; + this.NotifyClient(value); + } + } + } + private Byte _zigbeechannel; + public Byte Zigbeechannel { + get { + return this._zigbeechannel; + } + set { + if (this.SetConfig(new Dictionary() { { "zigbeechannel", value } })) { + this._zigbeechannel = value; + this.NotifyClient(value); + } + } + } + public String Apiversion { get; private set; } + public String Swversion { get; private set; } + public String Mac { get; private set; } + public Boolean Portalservices { get; private set; } + public DateTime Localtime { get; private set; } + public String Modelid { get; private set; } + public String Bridgeid { get; private set; } + public String Replacesbridgeid { get; private set; } + public Boolean Factorynew { get; private set; } + public String Datastoreversion { get; private set; } + public String Starterkitid { get; private set; } + public SwUpdate SwUpdate2 { get; private set; } + public Portalstate Portalstate { get; private set; } + public ReadOnlyDictionary Whitelist { get; private set; } + #endregion + + #region Constructor + public Config(JsonData json, HttpConnection http, Boolean polling) : base(json, http, polling) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("name") && json["name"].IsString) { + this._name = json["name"].ToString(); + } + if (json.ContainsKey("apiversion") && json["apiversion"].IsString) { + this.Apiversion = json["apiversion"].ToString(); + } + if (json.ContainsKey("swversion") && json["swversion"].IsString) { + this.Swversion = json["swversion"].ToString(); + } + if (json.ContainsKey("linkbutton") && json["linkbutton"].IsBoolean) { + this._linkbutton = (Boolean)json["linkbutton"]; + } + if (json.ContainsKey("ipaddress") && json["ipaddress"].IsString) { + this._ipaddress = json["ipaddress"].ToString(); + } + if (json.ContainsKey("mac") && json["mac"].IsString) { + this.Mac = json["mac"].ToString(); + } + if (json.ContainsKey("netmask") && json["netmask"].IsString) { + this._netmask = json["netmask"].ToString(); + } + if (json.ContainsKey("gateway") && json["gateway"].IsString) { + this._gateway = json["gateway"].ToString(); + } + if (json.ContainsKey("dhcp") && json["dhcp"].IsBoolean) { + this._dhcp = (Boolean)json["dhcp"]; + } + if (json.ContainsKey("portalservices") && json["portalservices"].IsBoolean) { + this.Portalservices = (Boolean)json["portalservices"]; + } + if (json.ContainsKey("UTC") && json["UTC"].IsString) { + this._utc = DateTime.Parse(json["UTC"].ToString()); + } + if (json.ContainsKey("localtime") && json["localtime"].IsString) { + this.Localtime = DateTime.Parse(json["localtime"].ToString()); + } + if (json.ContainsKey("timezone") && json["timezone"].IsString) { + this._timezone = json["timezone"].ToString(); + } + if (json.ContainsKey("zigbeechannel") && json["zigbeechannel"].IsInt) { + this._zigbeechannel = (Byte)json["zigbeechannel"]; + } + if (json.ContainsKey("modelid") && json["modelid"].IsString) { + this.Modelid = json["modelid"].ToString(); + } + if (json.ContainsKey("bridgeid") && json["bridgeid"].IsString) { + this.Bridgeid = json["bridgeid"].ToString(); + } + if (json.ContainsKey("replacesbridgeid") && json["replacesbridgeid"] != null && json["replacesbridgeid"].IsString) { + this.Replacesbridgeid = json["replacesbridgeid"].ToString(); + } + if (json.ContainsKey("factorynew") && json["factorynew"].IsBoolean) { + this.Factorynew = (Boolean)json["factorynew"]; + } + if (json.ContainsKey("datastoreversion") && json["datastoreversion"].IsString) { + this.Datastoreversion = json["datastoreversion"].ToString(); + } + if (json.ContainsKey("starterkitid") && json["starterkitid"].IsString) { + this.Starterkitid = json["starterkitid"].ToString(); + } + if (json.ContainsKey("swupdate2") && json["swupdate2"].IsObject) { + this.SwUpdate2 = new SwUpdate(json["swupdate2"], this.http, this.Polling); + this.SwUpdate2.Update += this.NotifyClientChildren; + } + if (json.ContainsKey("whitelist") && json["whitelist"].IsObject) { + this.CreateWhitelist(json["whitelist"]); + } + if (json.ContainsKey("portalstate") && json["portalstate"].IsObject) { + this.Portalstate = new Portalstate(json["portalstate"], this.http, this.Polling); + this.Portalstate.Update += this.NotifyClientChildren; + } + } + + private void CreateWhitelist(JsonData json) { + Dictionary whitelists = new Dictionary(); + foreach (String item in json.Keys) { + Whitelist w = new Whitelist(json[item], new Tuple(item), this.http, this.Polling); + w.Update += this.NotifyClientChildren; + whitelists.Add(item, w); + } + this.Whitelist = new ReadOnlyDictionary(whitelists); + } + #endregion + + #region AConnector + public override String ToString() { + return "Config: " + this.Name + " Ip-" + this.Ipaddress + " DHCP-" + this.Dhcp + " Time-" + this.Localtime + " User-" + this.Whitelist.Count; + } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("name") && json["name"].IsString) { + if (this.Name != json["name"].ToString()) { + this._name = json["name"].ToString(); + this.NotifyClient(this.Name); + } + } + if (json.ContainsKey("apiversion") && json["apiversion"].IsString) { + if (this.Apiversion != json["apiversion"].ToString()) { + this.Apiversion = json["apiversion"].ToString(); + this.NotifyClient(this.Apiversion); + } + } + if (json.ContainsKey("swversion") && json["swversion"].IsString) { + if (this.Swversion != json["swversion"].ToString()) { + this.Swversion = json["swversion"].ToString(); + this.NotifyClient(this.Swversion); + } + } + if (json.ContainsKey("linkbutton") && json["linkbutton"].IsBoolean) { + if (this.Linkbutton != (Boolean)json["linkbutton"]) { + this._linkbutton = (Boolean)json["linkbutton"]; + this.NotifyClient(this.Linkbutton); + } + } + if (json.ContainsKey("ipaddress") && json["ipaddress"].IsString) { + if (this.Ipaddress != json["ipaddress"].ToString()) { + this._ipaddress = json["ipaddress"].ToString(); + this.NotifyClient(this.Ipaddress); + } + } + if (json.ContainsKey("mac") && json["mac"].IsString) { + if (this.Mac != json["mac"].ToString()) { + this.Mac = json["mac"].ToString(); + this.NotifyClient(this.Mac); + } + } + if (json.ContainsKey("netmask") && json["netmask"].IsString) { + if (this.Netmask != json["netmask"].ToString()) { + this._netmask = json["netmask"].ToString(); + this.NotifyClient(this.Netmask); + } + } + if (json.ContainsKey("gateway") && json["gateway"].IsString) { + if (this.Gateway != json["gateway"].ToString()) { + this._gateway = json["gateway"].ToString(); + this.NotifyClient(this.Gateway); + } + } + if (json.ContainsKey("dhcp") && json["dhcp"].IsBoolean) { + if (this.Dhcp != (Boolean)json["dhcp"]) { + this._dhcp = (Boolean)json["dhcp"]; + this.NotifyClient(this.Dhcp); + } + } + if (json.ContainsKey("portalservices") && json["portalservices"].IsBoolean) { + if (this.Portalservices != (Boolean)json["portalservices"]) { + this.Portalservices = (Boolean)json["portalservices"]; + this.NotifyClient(this.Portalservices); + } + } + if (json.ContainsKey("UTC") && json["UTC"].IsString) { + if (this.Utc != DateTime.Parse(json["UTC"].ToString())) { + this._utc = DateTime.Parse(json["UTC"].ToString()); + this.NotifyClient(this.Utc); + } + } + if (json.ContainsKey("localtime") && json["localtime"].IsString) { + if (this.Localtime != DateTime.Parse(json["localtime"].ToString())) { + this.Localtime = DateTime.Parse(json["localtime"].ToString()); + this.NotifyClient(this.Localtime); + } + } + if (json.ContainsKey("timezone") && json["timezone"].IsString) { + if (this.Timezone != json["timezone"].ToString()) { + this._timezone = json["timezone"].ToString(); + this.NotifyClient(this.Timezone); + } + } + if (json.ContainsKey("zigbeechannel") && json["zigbeechannel"].IsInt) { + if (this.Zigbeechannel != (Byte)json["zigbeechannel"]) { + this._zigbeechannel = (Byte)json["zigbeechannel"]; + this.NotifyClient(this.Zigbeechannel); + } + } + if (json.ContainsKey("modelid") && json["modelid"].IsString) { + if (this.Modelid != json["modelid"].ToString()) { + this.Modelid = json["modelid"].ToString(); + this.NotifyClient(this.Modelid); + } + } + if (json.ContainsKey("bridgeid") && json["bridgeid"].IsString) { + if (this.Bridgeid != json["bridgeid"].ToString()) { + this.Bridgeid = json["bridgeid"].ToString(); + this.NotifyClient(this.Bridgeid); + } + } + if (json.ContainsKey("replacesbridgeid") && json["replacesbridgeid"] != null && json["replacesbridgeid"].IsString) { + if (this.Replacesbridgeid != json["replacesbridgeid"].ToString()) { + this.Replacesbridgeid = json["replacesbridgeid"].ToString(); + this.NotifyClient(this.Replacesbridgeid); + } + } + if (json.ContainsKey("factorynew") && json["factorynew"].IsBoolean) { + if (this.Factorynew != (Boolean)json["factorynew"]) { + this.Factorynew = (Boolean)json["factorynew"]; + this.NotifyClient(this.Factorynew); + } + } + if (json.ContainsKey("datastoreversion") && json["datastoreversion"].IsString) { + if (this.Datastoreversion != json["datastoreversion"].ToString()) { + this.Datastoreversion = json["datastoreversion"].ToString(); + this.NotifyClient(this.Datastoreversion); + } + } + if (json.ContainsKey("starterkitid") && json["starterkitid"].IsString) { + if (this.Starterkitid != json["starterkitid"].ToString()) { + this.Starterkitid = json["starterkitid"].ToString(); + this.NotifyClient(this.Starterkitid); + } + } + if (json.ContainsKey("swupdate2") && json["swupdate2"].IsObject) { + this.SwUpdate2?.SetUpdate(json["swupdate2"]); + } + if (json.ContainsKey("whitelist") && json["whitelist"].IsObject) { + foreach (String item in json["whitelist"].Keys) { + if(this.Whitelist.ContainsKey(item)) { + this.Whitelist[item].SetUpdate(json["whitelist"][item]); + } + } + } + if (json.ContainsKey("portalstate") && json["portalstate"].IsObject) { + this.Portalstate?.SetUpdate(json["portalstate"]); + } + } + #endregion + } +} diff --git a/Hue/Devices/Configs/Portalstate.cs b/Hue/Devices/Configs/Portalstate.cs new file mode 100644 index 0000000..9351161 --- /dev/null +++ b/Hue/Devices/Configs/Portalstate.cs @@ -0,0 +1,71 @@ +using System; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Devices.Configs { + public class Portalstate : AConfig { + + #region Properties + public Boolean Signedon { get; private set; } + public Boolean Incoming { get; private set; } + public Boolean Outgoing { get; private set; } + public String Communication { get; private set; } + #endregion + + #region Constrctor + public Portalstate(JsonData json, HttpConnection http, Boolean polling) : base(json, http, polling) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("signedon") && json["signedon"].IsBoolean) { + this.Signedon = (Boolean)json["signedon"]; + } + if (json.ContainsKey("incoming") && json["incoming"].IsBoolean) { + this.Incoming = (Boolean)json["incoming"]; + } + if (json.ContainsKey("outgoing") && json["outgoing"].IsBoolean) { + this.Outgoing = (Boolean)json["outgoing"]; + } + if (json.ContainsKey("communication") && json["communication"].IsString) { + this.Communication = json["communication"].ToString(); + } + } + #endregion + + #region AConnector + public override String ToString() { + return "Portalstate: On-" + this.Signedon + " In-" + this.Incoming + " Out-" + this.Outgoing + " Communication-" + this.Communication; + } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("signedon") && json["signedon"].IsBoolean) { + if (this.Signedon != (Boolean)json["signedon"]) { + this.Signedon = (Boolean)json["signedon"]; + this.NotifyClient(this.Signedon); + } + } + if (json.ContainsKey("incoming") && json["incoming"].IsBoolean) { + if (this.Incoming != (Boolean)json["incoming"]) { + this.Incoming = (Boolean)json["incoming"]; + this.NotifyClient(this.Incoming); + } + } + if (json.ContainsKey("outgoing") && json["outgoing"].IsBoolean) { + if (this.Outgoing != (Boolean)json["outgoing"]) { + this.Outgoing = (Boolean)json["outgoing"]; + this.NotifyClient(this.Outgoing); + } + } + if (json.ContainsKey("communication") && json["communication"].IsString) { + if (this.Communication != json["communication"].ToString()) { + this.Communication = json["communication"].ToString(); + this.NotifyClient(this.Communication); + } + } + } + #endregion + } +} diff --git a/Hue/Devices/Configs/SwUpdate.cs b/Hue/Devices/Configs/SwUpdate.cs new file mode 100644 index 0000000..fa40245 --- /dev/null +++ b/Hue/Devices/Configs/SwUpdate.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Devices.Configs { + public class SwUpdate : AConfig { + #region Properties + public Boolean Checkforupdate { get; private set; } + public DateTime Lastchange { get; private set; } + public String Bridgestate { get; private set; } + public DateTime Bridgelastinstall { get; private set; } + public String State { get; private set; } + public String Autoinstallupdatetime { get; private set; } + public Boolean Autoinstallon { get; private set; } + #endregion + + #region Constructor + public SwUpdate(JsonData json, HttpConnection http, Boolean polling) : base(json, http, polling) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if(json.ContainsKey("checkforupdate") && json["checkforupdate"].IsBoolean) { + this.Checkforupdate = (Boolean)json["checkforupdate"]; + } + if (json.ContainsKey("lastchange") && json["lastchange"].IsString) { + this.Lastchange = DateTime.Parse(json["lastchange"].ToString()).ToLocalTime(); + } + if (json.ContainsKey("bridge") && json["bridge"].IsObject) { + if (json["bridge"].ContainsKey("state") && json["bridge"]["state"].IsString) { + this.Bridgestate = json["bridge"]["state"].ToString(); + } + if (json["bridge"].ContainsKey("lastinstall") && json["bridge"]["lastinstall"].IsString) { + this.Bridgelastinstall = DateTime.Parse(json["bridge"]["lastinstall"].ToString()).ToLocalTime(); + } + } + if (json.ContainsKey("state") && json["state"].IsString) { + this.State = json["state"].ToString(); + } + if (json.ContainsKey("autoinstall") && json["autoinstall"].IsObject) { + if (json["autoinstall"].ContainsKey("updatetime") && json["autoinstall"]["updatetime"].IsString) { + this.Autoinstallupdatetime = json["autoinstall"]["updatetime"].ToString(); + } + if (json["autoinstall"].ContainsKey("on") && json["autoinstall"]["on"].IsBoolean) { + this.Autoinstallon = (Boolean)json["autoinstall"]["on"]; + } + } + } + #endregion + + #region AConnector + public override String ToString() { + return "Updates: Check-" + this.Checkforupdate + " LatestCheck-" + this.Lastchange + " Updates-" + this.Bridgestate + " Installed-" + this.Bridgelastinstall + " State-" + this.State + " AutoTime-" + this.Autoinstallupdatetime + " AutoOn-" + this.Autoinstallon; + } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("checkforupdate") && json["checkforupdate"].IsBoolean) { + if (this.Checkforupdate != (Boolean)json["checkforupdate"]) { + this.Checkforupdate = (Boolean)json["checkforupdate"]; + this.NotifyClient(this.Checkforupdate); + } + } + if (json.ContainsKey("lastchange") && json["lastchange"].IsString) { + if (this.Lastchange != DateTime.Parse(json["lastchange"].ToString()).ToLocalTime()) { + this.Lastchange = DateTime.Parse(json["lastchange"].ToString()).ToLocalTime(); + this.NotifyClient(this.Lastchange); + } + } + if (json.ContainsKey("bridge") && json["bridge"].ContainsKey("state") && json["bridge"]["state"].IsString) { + if (this.Bridgestate != json["bridge"]["state"].ToString()) { + this.Bridgestate = json["bridge"]["state"].ToString(); + this.NotifyClient(this.Bridgestate); + } + } + if (json.ContainsKey("bridge") && json["bridge"].ContainsKey("lastinstall") && json["bridge"]["lastinstall"].IsString) { + if (this.Bridgelastinstall != DateTime.Parse(json["bridge"]["lastinstall"].ToString()).ToLocalTime()) { + this.Bridgelastinstall = DateTime.Parse(json["bridge"]["lastinstall"].ToString()).ToLocalTime(); + this.NotifyClient(this.Bridgelastinstall); + } + } + if (json.ContainsKey("state") && json["state"].IsString) { + if (this.State != json["state"].ToString()) { + this.State = json["state"].ToString(); + this.NotifyClient(this.State); + } + } + if (json.ContainsKey("autoinstall") && json["autoinstall"].ContainsKey("updatetime") && json["autoinstall"]["updatetime"].IsString) { + if (this.Autoinstallupdatetime != json["autoinstall"]["updatetime"].ToString()) { + this.Autoinstallupdatetime = json["autoinstall"]["updatetime"].ToString(); + this.NotifyClient(this.Autoinstallupdatetime); + } + } + if (json.ContainsKey("autoinstall") && json["autoinstall"].ContainsKey("on") && json["autoinstall"]["on"].IsBoolean) { + if (this.Autoinstallon != (Boolean)json["autoinstall"]["on"]) { + this.Autoinstallon = (Boolean)json["autoinstall"]["on"]; + this.NotifyClient(this.Autoinstallon); + } + } + } + #endregion + } +} diff --git a/Hue/Devices/Configs/Whitelist.cs b/Hue/Devices/Configs/Whitelist.cs new file mode 100644 index 0000000..5fdb47b --- /dev/null +++ b/Hue/Devices/Configs/Whitelist.cs @@ -0,0 +1,62 @@ +using System; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Devices.Configs { + public class Whitelist : AConfig { + #region Properties + public DateTime Lastusedate { get; private set; } + public DateTime Createdate { get; private set; } + public String Name { get; private set; } + public String UserId { get; } + #endregion + + #region Constructor + public Whitelist(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, http, polling) { + this.UserId = id.Item1; + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("last use date") && json["last use date"].IsString) { + this.Lastusedate = DateTime.Parse(json["last use date"].ToString()).ToLocalTime(); + } + if (json.ContainsKey("create date") && json["create date"].IsString) { + this.Createdate = DateTime.Parse(json["create date"].ToString()).ToLocalTime(); + } + if (json.ContainsKey("name") && json["name"].IsString) { + this.Name = json["name"].ToString(); + } + } + #endregion + + #region AConnector + public override String ToString() { + return "User [" + this.UserId + "]: " + this.Name + " (Create-" + this.Createdate + " LastOn-" + this.Lastusedate + ")"; + } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("last use date") && json["last use date"].IsString) { + if (this.Lastusedate != DateTime.Parse(json["last use date"].ToString()).ToLocalTime()) { + this.Lastusedate = DateTime.Parse(json["last use date"].ToString()).ToLocalTime(); + this.NotifyClient(this.Lastusedate); + } + } + if (json.ContainsKey("create date") && json["create date"].IsString) { + if (this.Createdate != DateTime.Parse(json["create date"].ToString()).ToLocalTime()) { + this.Createdate = DateTime.Parse(json["create date"].ToString()).ToLocalTime(); + this.NotifyClient(this.Createdate); + } + } + if (json.ContainsKey("name") && json["name"].IsString) { + if (this.Name != json["name"].ToString()) { + this.Name = json["name"].ToString(); + this.NotifyClient(this.Name); + } + } + } + #endregion + } +} diff --git a/Hue/Devices/Groups/Room.cs b/Hue/Devices/Groups/Room.cs index 40249e6..5bb8fb0 100644 --- a/Hue/Devices/Groups/Room.cs +++ b/Hue/Devices/Groups/Room.cs @@ -1,28 +1,32 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.Interfaces; using BlubbFish.IoT.Hue.lib; using LitJson; namespace BlubbFish.IoT.Hue.Devices.Groups { public class Room : AGroup { - public override event UpdatedValue Update; - - #region Properties - private AlertEffect _alert; public enum AlertEffect { none, select, lselect } + + #region Properties + private AlertEffect _alert; + private Boolean _state; public Boolean State { get { return this._state; } set { - this.SetGroupAction(new Dictionary() { { "on", value } }); + if(this.SetGroupAction(new Dictionary() { { "on", value } })) { + this._state = value; + this.NotifyClient(value); + } } } private Byte _brightness; @@ -31,7 +35,10 @@ namespace BlubbFish.IoT.Hue.Devices.Groups { return this._brightness; } set { - this.SetGroupAction(new Dictionary() { { "bri", value } }); + if(this.SetGroupAction(new Dictionary() { { "bri", value } })) { + this._brightness = value; + this.NotifyClient(value); + } } } public AlertEffect Alert { @@ -39,7 +46,10 @@ namespace BlubbFish.IoT.Hue.Devices.Groups { return this._alert; } set { - this.SetGroupAction(new Dictionary() { { "alert", value } }); + if(this.SetGroupAction(new Dictionary() { { "alert", value } })) { + this._alert = value; + this.NotifyClient(value); + } } } #endregion @@ -66,14 +76,6 @@ namespace BlubbFish.IoT.Hue.Devices.Groups { #endregion #region AConnector - public override Dictionary ToDictionary() { - return new Dictionary { - { "State", this.State }, - { "Brightness", this.Brightness }, - { "Alert", this.Alert.ToString() } - }; - } - public override String ToString() { List lid = new List(); foreach (KeyValuePair item in this.Lights) { @@ -81,6 +83,28 @@ namespace BlubbFish.IoT.Hue.Devices.Groups { } return "Group " + this.Name + " [" + this.GroupId + "]: (Class-"+ Helper.GetEnumDescription(this.Class) + ", ["+String.Join(",",lid.ToArray())+"]) On-" + this.State.ToString() + " Bri-" + this.Brightness + " Alert-" + this.Alert.ToString() + " AllOn-" + this.StateAllOn.ToString() + " AnyOn-" + this.StateAnyOn.ToString(); } + + public override void SetUpdate(JsonData json) { + base.SetUpdate(json); + if (json.ContainsKey("on") && json["on"].IsBoolean) { + if (this.State != (Boolean)json["on"]) { + this._state = (Boolean)json["on"]; + this.NotifyClient(this.State); + } + } + if (json.ContainsKey("bri") && json["bri"].IsInt) { + if (this.Brightness != (Byte)json["bri"]) { + this._brightness = (Byte)json["bri"]; + this.NotifyClient(this.Brightness); + } + } + if (json.ContainsKey("alert") && json["alert"].IsString) { + if (this.Alert != (AlertEffect)Enum.Parse(typeof(AlertEffect), json["alert"].ToString())) { + this._alert = (AlertEffect)Enum.Parse(typeof(AlertEffect), json["alert"].ToString()); + this.NotifyClient(this.Alert); + } + } + } #endregion } } diff --git a/Hue/Devices/Lights/Dimmablelight.cs b/Hue/Devices/Lights/Dimmablelight.cs index 5d823a0..688c409 100644 --- a/Hue/Devices/Lights/Dimmablelight.cs +++ b/Hue/Devices/Lights/Dimmablelight.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.Interfaces; using BlubbFish.IoT.Hue.lib; using LitJson; namespace BlubbFish.IoT.Hue.Devices.Lights { public class Dimmablelight : ALight { - public override event UpdatedValue Update; public enum AlertEffect { none, select, @@ -20,7 +20,10 @@ namespace BlubbFish.IoT.Hue.Devices.Lights { return this._state; } set { - this.SetLightState(new Dictionary() { { "on", value } }); + if(this.SetLightStateAttribute(new Dictionary() { { "on", value } })) { + this._state = value; + this.NotifyClient(value); + } } } private Byte _brightness; @@ -29,7 +32,10 @@ namespace BlubbFish.IoT.Hue.Devices.Lights { return this._brightness; } set { - this.SetLightState(new Dictionary() { { "bri", value } }); + if(this.SetLightStateAttribute(new Dictionary() { { "bri", value } })) { + this._brightness = value; + this.NotifyClient(value); + } } } private AlertEffect _alert; @@ -38,7 +44,10 @@ namespace BlubbFish.IoT.Hue.Devices.Lights { return this._alert; } set { - this.SetLightState(new Dictionary() { { "alert", value } }); + if(this.SetLightStateAttribute(new Dictionary() { { "alert", value } })) { + this._alert = value; + this.NotifyClient(value); + } } } public Boolean Reachable { get; private set; } @@ -72,14 +81,32 @@ namespace BlubbFish.IoT.Hue.Devices.Lights { public override String ToString() { return "Light " + this.Name + " [" + this.LightId + "]: On-" + this.State.ToString() + " Bri-" + this.Brightness + " Alert-" + this.Alert.ToString() + " Reachable-" + this.Reachable.ToString(); } - - public override Dictionary ToDictionary() { - return new Dictionary { - { "State", this.State }, - { "Brightness", this.Brightness }, - { "Alert", this.Alert.ToString() }, - { "Reachable", this.Reachable } - }; + public override void SetUpdate(JsonData json) { + base.SetUpdate(json); + if (json.ContainsKey("state") && json["state"].ContainsKey("on") && json["state"]["on"].IsBoolean) { + if (this.State != (Boolean)json["state"]["on"]) { + this._state = (Boolean)json["state"]["on"]; + this.NotifyClient(this.State); + } + } + if (json.ContainsKey("state") && json["state"].ContainsKey("bri") && json["state"]["bri"].IsInt) { + if (this.Brightness != (Byte)json["state"]["bri"]) { + this._brightness = (Byte)json["state"]["bri"]; + this.NotifyClient(this.Brightness); + } + } + if (json.ContainsKey("state") && json["state"].ContainsKey("alert") && json["state"]["alert"].IsString) { + if (this.Alert != (AlertEffect)Enum.Parse(typeof(AlertEffect), json["state"]["alert"].ToString())) { + this._alert = (AlertEffect)Enum.Parse(typeof(AlertEffect), json["state"]["alert"].ToString()); + this.NotifyClient(this.Alert); + } + } + if (json.ContainsKey("state") && json["state"].ContainsKey("reachable") && json["state"]["reachable"].IsBoolean) { + if (this.Reachable != (Boolean)json["state"]["reachable"]) { + this.Reachable = (Boolean)json["state"]["reachable"]; + this.NotifyClient(this.Reachable); + } + } } #endregion } diff --git a/Hue/Devices/Scenes/LightState.cs b/Hue/Devices/Scenes/LightState.cs index c168d74..18b350e 100644 --- a/Hue/Devices/Scenes/LightState.cs +++ b/Hue/Devices/Scenes/LightState.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.Interfaces; using BlubbFish.IoT.Hue.lib; using LitJson; namespace BlubbFish.IoT.Hue.Devices.Scenes { - public class LightState : AConnector { - public override event UpdatedValue Update; - + public class LightState : AScene { #region Properties private Boolean _state; public Boolean State { @@ -15,7 +15,10 @@ namespace BlubbFish.IoT.Hue.Devices.Scenes { return this._state; } set { - this.SetLightScene(new Dictionary() { { "on", value } }); + if (this.SetLightSceneAttribute(new Dictionary() { { "on", value } })) { + this._state = value; + this.NotifyClient(value); + } } } private Byte _brightness; @@ -24,18 +27,16 @@ namespace BlubbFish.IoT.Hue.Devices.Scenes { return this._brightness; } set { - this.SetLightScene(new Dictionary() { { "bri", value } }); + if(this.SetLightSceneAttribute(new Dictionary() { { "bri", value } })) { + this._brightness = value; + this.NotifyClient(value); + } } } - public String SceneId { get; } - public Int32 LightStateId { get; } #endregion #region Constructor - public LightState(JsonData json, Tuple id, HttpConnection http) { - this.http = http; - this.SceneId = id.Item1; - this.LightStateId = id.Item2; + public LightState(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(json, id, http, polling, lights){ this.ComplexInit(json); } @@ -49,21 +50,25 @@ namespace BlubbFish.IoT.Hue.Devices.Scenes { } #endregion - private void SetLightScene(Dictionary value) { - this.PutDictionary("scenes/" + this.SceneId + "/lights/" + this.LightStateId + "/state", value); - } - #region AConnector - public override Dictionary ToDictionary() { - return new Dictionary { - { "State", this.State.ToString() }, - { "Brightness", this.Brightness } - }; - } - public override String ToString() { return "Scene LightState " + this.SceneId + " [" + this.LightStateId + "]: On-" + this.State.ToString() + " Bri-" + this.Brightness; } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("on") && json["on"].IsBoolean) { + if (this.State != (Boolean)json["on"]) { + this._state = (Boolean)json["on"]; + this.NotifyClient(this.State); + } + } + if (json.ContainsKey("bri") && json["bri"].IsInt) { + if (this.Brightness != (Byte)json["bri"]) { + this._brightness = (Byte)json["bri"]; + this.NotifyClient(this.Brightness); + } + } + } #endregion } } diff --git a/Hue/Devices/Scenes/Scene.cs b/Hue/Devices/Scenes/Scene.cs index 8006242..a9dcd39 100644 --- a/Hue/Devices/Scenes/Scene.cs +++ b/Hue/Devices/Scenes/Scene.cs @@ -1,30 +1,46 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.Interfaces; using BlubbFish.IoT.Hue.lib; using LitJson; namespace BlubbFish.IoT.Hue.Devices.Scenes { class Scene : AScene { - public override event UpdatedValue Update; - #region Properties + private String _name; + public String Name { + get { + return this._name; + } + set { + if (this.SetSceneAttribute(new Dictionary() { { "name", value } })) { + this._name = value; + this.NotifyClient(value); + } + } + } public String Owner { get; private set; } public Boolean Recycle { get; private set; } public Boolean Locked { get; private set; } public String Picture { get; private set; } public DateTime Lastupdated { get; private set; } public Int32 Version { get; private set; } + public ReadOnlyDictionary Lights { get; private set; } + public ReadOnlyDictionary LightStates { get; private set; } #endregion #region Constructor - public Scene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(json, id, http, polling, lights) { - this.ComplexInit(json); + public Scene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(json, new Tuple(id.Item1, 0), http, polling, lights) { + this.ComplexInit(json, lights); } - private void ComplexInit(JsonData json) { - if(json.ContainsKey("owner") && json["owner"].IsString) { + private void ComplexInit(JsonData json, ReadOnlyDictionary lights) { + if (json.ContainsKey("name") && json["name"].IsString) { + this._name = json["name"].ToString(); + } + if (json.ContainsKey("owner") && json["owner"].IsString) { this.Owner = json["owner"].ToString(); } if (json.ContainsKey("recycle") && json["recycle"].IsBoolean) { @@ -42,21 +58,30 @@ namespace BlubbFish.IoT.Hue.Devices.Scenes { if (json.ContainsKey("version") && json["version"].IsInt) { this.Version = (Int32)json["version"]; } + if (json.ContainsKey("lights") && json["lights"].IsArray) { + Dictionary lightdict = new Dictionary(); + foreach (JsonData item in json["lights"]) { + if (Int32.TryParse(item.ToString(), out Int32 lampid)) { + if (lights.ContainsKey(lampid)) { + lightdict.Add(lampid, lights[lampid]); + } + } + } + this.Lights = new ReadOnlyDictionary(lightdict); + } + if (json.ContainsKey("lightstates") && json["lightstates"].IsObject) { + Dictionary lightstates = new Dictionary(); + foreach (String item in json["lightstates"].Keys) { + LightState l = new LightState(json["lightstates"][item], new Tuple(this.SceneId, Int32.Parse(item)), this.http, this.Polling, lights); + l.Update += this.NotifyClientChildren; + lightstates.Add(Int32.Parse(item), l); + } + this.LightStates = new ReadOnlyDictionary(lightstates); + } } #endregion #region AConnector - public override Dictionary ToDictionary() { - return new Dictionary { - { "owner", this.Owner }, - { "recycle", this.Recycle }, - { "locked", this.Locked }, - { "picture", this.Picture }, - { "lastupdated", this.Lastupdated }, - { "version", this.Version } - }; - } - public override String ToString() { List lid = new List(); foreach (KeyValuePair item in this.Lights) { @@ -68,6 +93,58 @@ namespace BlubbFish.IoT.Hue.Devices.Scenes { } return "Scene " + this.Name + " [" + this.SceneId + "]: (["+ String.Join(",", lid.ToArray()) +"]) Light-States ["+ String.Join(",", lst.ToArray())+"]"; } + + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("name") && json["name"].IsString) { + if (this.Name != json["name"].ToString()) { + this._name = json["name"].ToString(); + this.NotifyClient(this.Name); + } + } + if (json.ContainsKey("owner") && json["owner"].IsString) { + if (this.Owner != json["owner"].ToString()) { + this.Owner = json["owner"].ToString(); + this.NotifyClient(this.Owner); + } + } + if (json.ContainsKey("recycle") && json["recycle"].IsBoolean) { + if (this.Recycle != (Boolean)json["recycle"]) { + this.Recycle = (Boolean)json["recycle"]; + this.NotifyClient(this.Recycle); + } + } + if (json.ContainsKey("locked") && json["locked"].IsBoolean) { + if (this.Locked != (Boolean)json["locked"]) { + this.Locked = (Boolean)json["locked"]; + this.NotifyClient(this.Locked); + } + } + if (json.ContainsKey("picture") && json["picture"].IsString) { + if (this.Picture != json["picture"].ToString()) { + this.Picture = json["picture"].ToString(); + this.NotifyClient(this.Picture); + } + } + if (json.ContainsKey("lastupdated") && json["lastupdated"].IsString) { + if (this.Lastupdated != DateTime.Parse(json["lastupdated"].ToString())) { + this.Lastupdated = DateTime.Parse(json["lastupdated"].ToString()); + this.NotifyClient(this.Lastupdated); + } + } + if (json.ContainsKey("version") && json["version"].IsInt) { + if (this.Version != (Int32)json["version"]) { + this.Version = (Int32)json["version"]; + this.NotifyClient(this.Version); + } + } + if (json.ContainsKey("lightstates") && json["lightstates"].IsObject) { + foreach (String item in json["lightstates"].Keys) { + if(this.LightStates.ContainsKey(Int32.Parse(item))) { + this.LightStates[Int32.Parse(item)].SetUpdate(json["lightstates"][Int32.Parse(item)]); + } + } + } + } #endregion } } diff --git a/Hue/Devices/Sensors/Daylight.cs b/Hue/Devices/Sensors/Daylight.cs new file mode 100644 index 0000000..3788885 --- /dev/null +++ b/Hue/Devices/Sensors/Daylight.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Devices.Sensors { + class Daylight : ASensor { + #region Properties + private Boolean _on; + public Boolean On { + get { + return this._on; + } + set { + if(this.SetSensorConfig(new Dictionary() { { "on", value } })) { + this._on = value; + this.NotifyClient(value); + } + } + } + public Boolean Configured { get; private set; } + private Int32 _sunriseoffset; + public Int32 Sunriseoffset { + get { + return this._sunriseoffset; + } + set { + if(this.SetSensorConfig(new Dictionary() { { "sunriseoffset", value } })) { + this._sunriseoffset = value; + this.NotifyClient(value); + } + } + } + private Int32 _sunsetoffset; + public Int32 Sunsetoffset { + get { + return this._sunsetoffset; + } + set { + if(this.SetSensorConfig(new Dictionary() { { "sunsetoffset", value } })) { + this._sunsetoffset = value; + this.NotifyClient(value); + } + } + } + public Boolean IsDaylight { get; private set; } + #endregion + + #region Constructor + public Daylight(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if(json.ContainsKey("config")) { + JsonData config = json["config"]; + if(config.ContainsKey("on") && config["on"].IsBoolean) { + this._on = (Boolean)config["on"]; + } + if(config.ContainsKey("configured") && config["configured"].IsBoolean) { + this.Configured = (Boolean)config["configured"]; + } + if(config.ContainsKey("sunriseoffset") && config["sunriseoffset"].IsInt) { + this._sunriseoffset = (Int32)config["sunriseoffset"]; + } + if(config.ContainsKey("sunsetoffset") && config["sunsetoffset"].IsInt) { + this._sunsetoffset = (Int32)config["sunsetoffset"]; + } + } + if(json.ContainsKey("state")) { + JsonData state = json["state"]; + if(state.ContainsKey("daylight") && state["daylight"].IsBoolean) { + this.IsDaylight = (Boolean)state["daylight"]; + } + } + } + #endregion + + #region AConnector + public override String ToString() { + return "Daylight " + this.Name + " [" + this.SensorId + "]: IsDaylight-" + this.IsDaylight.ToString() + " On-" + this.On.ToString() + " Configured-" + this.Configured.ToString() + " Sunriseoffset-" + this.Sunriseoffset.ToString() + " Sunsetoffset-" + this.Sunsetoffset.ToString(); + } + + public override void SetUpdate(JsonData json) { + base.SetUpdate(json); + if (json.ContainsKey("config") && json["config"].ContainsKey("on") && json["config"]["on"].IsBoolean) { + if (this.On != (Boolean)json["config"]["on"]) { + this._on = (Boolean)json["config"]["on"]; + this.NotifyClient(this.On); + } + } + if (json.ContainsKey("config") && json["config"].ContainsKey("configured") && json["config"]["configured"].IsBoolean) { + if (this.Configured != (Boolean)json["config"]["configured"]) { + this.Configured = (Boolean)json["config"]["configured"]; + this.NotifyClient(this.Configured); + } + } + if (json.ContainsKey("config") && json["config"].ContainsKey("sunriseoffset") && json["config"]["sunriseoffset"].IsInt) { + if (this.Sunriseoffset != (Int32)json["config"]["sunriseoffset"]) { + this._sunriseoffset = (Int32)json["config"]["sunriseoffset"]; + this.NotifyClient(this.Sunriseoffset); + } + } + if (json.ContainsKey("config") && json["config"].ContainsKey("sunsetoffset") && json["config"]["sunsetoffset"].IsInt) { + if (this.Sunsetoffset != (Int32)json["config"]["sunsetoffset"]) { + this._sunsetoffset = (Int32)json["config"]["sunsetoffset"]; + this.NotifyClient(this.Sunsetoffset); + } + } + if (json.ContainsKey("state") && json["state"].ContainsKey("daylight") && json["state"]["daylight"].IsBoolean) { + if (this.IsDaylight != (Boolean)json["state"]["daylight"]) { + this.IsDaylight = (Boolean)json["state"]["daylight"]; + this.NotifyClient(this.IsDaylight); + } + } + } + #endregion + } +} diff --git a/Hue/Events/UpdateEvent.cs b/Hue/Events/UpdateEvent.cs index 00432d4..117e8e6 100644 --- a/Hue/Events/UpdateEvent.cs +++ b/Hue/Events/UpdateEvent.cs @@ -42,4 +42,18 @@ namespace BlubbFish.IoT.Hue.Events { public SceneUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { } } + public class SensorUpdateEvent : AllUpdateEvent { + public SensorUpdateEvent() { + } + + public SensorUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { + } + } + public class ConfigUpdateEvent : AllUpdateEvent { + public ConfigUpdateEvent() { + } + + public ConfigUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { + } + } } diff --git a/Hue/Hue.csproj b/Hue/Hue.csproj index 4fecf08..40eaaf9 100644 --- a/Hue/Hue.csproj +++ b/Hue/Hue.csproj @@ -41,8 +41,14 @@ + + + + + + @@ -51,6 +57,7 @@ + diff --git a/Hue/HueController.cs b/Hue/HueController.cs index 995d448..cc68f8a 100644 --- a/Hue/HueController.cs +++ b/Hue/HueController.cs @@ -21,6 +21,8 @@ namespace BlubbFish.IoT.Hue { public ReadOnlyDictionary Lights { get; private set; } public ReadOnlyDictionary Groups { get; private set; } public ReadOnlyDictionary Scenes { get; private set; } + public ReadOnlyDictionary Sensors { get; private set; } + public AConfig Config { get; private set; } public HueController(Dictionary settings, Boolean enablePoll = true) { this.polling = enablePoll; @@ -50,10 +52,10 @@ namespace BlubbFish.IoT.Hue { foreach (KeyValuePair item in this.Scenes) { item.Value.Update += this.AllUpdate; } - /*foreach (KeyValuePair item in this.Sensors) { + foreach (KeyValuePair item in this.Sensors) { item.Value.Update += this.AllUpdate; } - this.Config.Update += this.AllUpdate;*/ + this.Config.Update += this.AllUpdate; } private void Poll() { @@ -65,25 +67,59 @@ namespace BlubbFish.IoT.Hue { } private void Updater() { - //throw new NotImplementedException(); + while (true) { + Thread.Sleep(1000 * 10); //Every Minute + JsonData update = this.http.GetJson(""); + Helper.SetUpdate(this.Lights, update, "lights"); + Helper.SetUpdate(this.Groups, update, "groups"); + Helper.SetUpdate(this.Sensors, update, "sensors"); + Helper.SetUpdate(this.Scenes, update, "scenes"); + if (update.ContainsKey("config")) { + this.Config.SetUpdate(update); + } + } } + + private void CreateAll(JsonData json) { if(json.ContainsKey("lights")) { - this.CreateLights(json["lights"]); + this.Lights = new ReadOnlyDictionary(this.CreateItemsInt32(json["lights"], false)); } if(json.ContainsKey("groups")) { - this.CreateGroups(json["groups"]); + this.Groups = new ReadOnlyDictionary(this.CreateItemsInt32(json["groups"])); } - if(json.ContainsKey("scenes")) { + if (json.ContainsKey("sensors")) { + this.Sensors = new ReadOnlyDictionary(this.CreateItemsInt32(json["sensors"], false)); + } + if (json.ContainsKey("scenes")) { this.CreateScenes(json["scenes"]); } + if(json.ContainsKey("config")) { + this.Config = AConfig.CreateInstanceConcrete(json["config"], this.http, this.polling); + } + } + + private Dictionary CreateItemsInt32(JsonData json, Boolean withLights = true) { + Dictionary items = new Dictionary(); + foreach (String itemid in json.Keys) { + T item = default(T); + if (withLights) { + item = (T)AConnector.CreateInstance(typeof(T), json[itemid], new Tuple(Int32.Parse(itemid)), this.http, this.polling, this.Lights); + } else { + item = (T)AConnector.CreateInstance(typeof(T), json[itemid], new Tuple(Int32.Parse(itemid)), this.http, this.polling); + } + if(item != null) { + items.Add(Int32.Parse(itemid), item); + } + } + return items; } private void CreateScenes(JsonData json) { Dictionary scenes = new Dictionary(); foreach (String sceneid in json.Keys) { - AScene s = AScene.CreateScene(json[sceneid], new Tuple(sceneid), this.http, this.polling, this.Lights); + AScene s = AScene.CreateInstanceConcrete(json[sceneid], new Tuple(sceneid), this.http, this.polling, this.Lights); if (s != null) { scenes.Add(sceneid, s); } @@ -91,28 +127,6 @@ namespace BlubbFish.IoT.Hue { this.Scenes = new ReadOnlyDictionary(scenes); } - private void CreateGroups(JsonData json) { - Dictionary groups = new Dictionary(); - foreach (String groupid in json.Keys) { - AGroup g = AGroup.CreateGroup(json[groupid], new Tuple(Int32.Parse(groupid)), this.http, this.polling, this.Lights); - if (g != null) { - groups.Add(Int32.Parse(groupid), g); - } - } - this.Groups = new ReadOnlyDictionary(groups); - } - - private void CreateLights(JsonData json) { - Dictionary lights = new Dictionary(); - foreach (String lightid in json.Keys) { - ALight l = ALight.CreateLight(json[lightid], new Tuple(Int32.Parse(lightid)), this.http, this.polling); - if(l != null) { - lights.Add(Int32.Parse(lightid), l); - } - } - this.Lights = new ReadOnlyDictionary(lights); - } - private void CreateKey(String server) { this.http = new HttpConnection(server); JsonData json = this.http.PostJson("", "{\"devicetype\": \"Huelib#BlubbFish\"}"); @@ -148,8 +162,8 @@ namespace BlubbFish.IoT.Hue { this.Lights = null; this.Groups = null; this.Scenes = null; - /*this.Sensors = null; - this.Config = null;*/ + this.Sensors = null; + this.Config = null; this.disposedValue = true; } } diff --git a/Hue/Interfaces/AConfig.cs b/Hue/Interfaces/AConfig.cs new file mode 100644 index 0000000..3fc018e --- /dev/null +++ b/Hue/Interfaces/AConfig.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BlubbFish.IoT.Hue.Devices.Configs; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Interfaces { + public abstract class AConfig : AConnector, IMqtt { + #region Constructor + public AConfig(JsonData json, HttpConnection http, Boolean polling) : base(http, polling){ + } + + internal static AConfig CreateInstanceConcrete(JsonData json, HttpConnection http, Boolean polling) { + return new Config(json, http, polling); + } + #endregion + + protected Boolean SetConfig(Dictionary dictionary) { + return this.PutDictionary("config", dictionary); + } + + #region IMqtt + public String MqttTopic() { + return "config"; + } + public String ToJson() { + return JsonMapper.ToJson(this.ToDictionary()); + } + #endregion + } +} diff --git a/Hue/Interfaces/AConnector.cs b/Hue/Interfaces/AConnector.cs index 28bcef5..b4872fc 100644 --- a/Hue/Interfaces/AConnector.cs +++ b/Hue/Interfaces/AConnector.cs @@ -1,5 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.lib; using LitJson; @@ -7,14 +10,75 @@ using LitJson; namespace BlubbFish.IoT.Hue.Interfaces { public abstract class AConnector { protected HttpConnection http; + public delegate void UpdatedValue(Object sender, AllUpdateEvent e); + public event UpdatedValue Update; - protected void PutDictionary(String address, Dictionary value) { - this.http.PutJson(address, JsonMapper.ToJson(value)); + #region Properties + public DateTime LastUpdate { get; private set; } + public Boolean Polling { get; set; } + public Boolean PollOnce { get; set; } + #endregion + + #region Constructor + public AConnector(HttpConnection http, Boolean polling) { + this.http = http; + this.Polling = polling; + this.LastUpdate = DateTime.Now; } - public delegate void UpdatedValue(Object sender, AllUpdateEvent e); + internal static Object CreateInstance(Type type, JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + return type.GetMethod("CreateInstanceConcrete", new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean), typeof(ReadOnlyDictionary) }).Invoke(null, new Object[] { json, id, http, polling, lights }); + } + + internal static Object CreateInstance(Type type, JsonData json, Tuple id, HttpConnection http, Boolean polling) { + return type.GetMethod("CreateInstanceConcrete", new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean) }).Invoke(null, new Object[] { json, id, http, polling }); + } + #endregion + + protected Boolean PutDictionary(String address, Dictionary value) { + JsonData json = this.http.PutJson(address, JsonMapper.ToJson(value)); + if (json.IsArray) { + return json[0].ContainsKey("success"); + } + return false; + } + + public virtual Dictionary ToDictionary() { + Dictionary dictionary = new Dictionary(); + foreach (PropertyInfo item in this.GetType().GetProperties()) { + if (item.CanRead && item.GetValue(this) != null) { + if (item.GetValue(this).GetType().GetMethod("ToDictionary") != null) { + dictionary.Add(item.Name, item.GetValue(this).GetType().GetMethod("ToDictionary").Invoke(item.GetValue(this), null)); + } else if (Helper.HasInterface(item.GetValue(this).GetType(), "IDictionary")) { + Dictionary subdict = new Dictionary(); + foreach (DictionaryEntry subitem in (IDictionary)item.GetValue(this)) { + if (subitem.Value.GetType().GetMethod("ToDictionary") != null) { + subdict.Add(subitem.Key.ToString(), subitem.Value.GetType().GetMethod("ToDictionary").Invoke(subitem.Value, null)); + } + } + dictionary.Add(item.Name, subdict); + } else if (item.GetValue(this).GetType().BaseType == typeof(Enum)) { + dictionary.Add(item.Name, Helper.GetEnumDescription((Enum)item.GetValue(this))); + } else { + dictionary.Add(item.Name, item.GetValue(this)); + } + } + } + return dictionary; + } + + protected void NotifyClient(Object value) { + this.LastUpdate = DateTime.Now; + this.Update?.Invoke(this, (AllUpdateEvent)Activator.CreateInstance(typeof(T), new Object[] { value, this.LastUpdate, this })); + } + + protected void NotifyClientChildren(Object sender, AllUpdateEvent e) { + this.Update?.Invoke(sender, e); + } + + #region Abstracts public abstract override String ToString(); - public abstract Dictionary ToDictionary(); - public abstract event UpdatedValue Update; + public abstract void SetUpdate(JsonData json); + #endregion } } diff --git a/Hue/Interfaces/AGroup.cs b/Hue/Interfaces/AGroup.cs index f3731e2..ad78e53 100644 --- a/Hue/Interfaces/AGroup.cs +++ b/Hue/Interfaces/AGroup.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using BlubbFish.IoT.Hue.Events; using BlubbFish.IoT.Hue.lib; using LitJson; @@ -45,42 +46,67 @@ namespace BlubbFish.IoT.Hue.Interfaces { } #region Properties - public DateTime LastUpdate { get; protected set; } - public Boolean Polling { get; set; } public String Name { get { return this._name; } set { - this._name = value; - this.SetGroupAttribute(new Dictionary() { { "name", value } }); + if(this.SetGroupAttribute(new Dictionary() { { "name", value } })) { + this._name = value; + this.NotifyClient(value); + } } } public Int32 GroupId { get; } public Types Groupclass { get; } public Boolean StateAnyOn { get; protected set; } public Boolean StateAllOn { get; protected set; } - public Dictionary Lights { get; protected set; } + public ReadOnlyDictionary Lights { get; protected set; } public GroupClass Class { get { return this._class; } set { - this._class = value; - this.SetGroupAttribute(new Dictionary() { { "class", Helper.GetEnumDescription(value) } }); + if(this.SetGroupAttribute(new Dictionary() { { "class", Helper.GetEnumDescription(value) } })) { + this._class = value; + this.NotifyClient(value); + } } } #endregion #region Constructor - protected AGroup(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + protected AGroup(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(http, polling) { this.GroupId = id.Item1; this.Groupclass = id.Item2; - this.http = http; - this.LastUpdate = DateTime.Now; - this.Polling = polling; this.ComplexInit(json, lights); } - internal static AGroup CreateGroup(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + private void ComplexInit(JsonData json, ReadOnlyDictionary lights) { + if (json.ContainsKey("lights") && json["lights"].IsArray) { + Dictionary lightdict = new Dictionary(); + foreach (JsonData item in json["lights"]) { + if (Int32.TryParse(item.ToString(), out Int32 lampid)) { + if (lights.ContainsKey(lampid)) { + lightdict.Add(lampid, lights[lampid]); + } + } + } + this.Lights = new ReadOnlyDictionary(lightdict); + } + if (json.ContainsKey("name") && json["name"].IsString) { + this._name = json["name"].ToString(); + } + if (json.ContainsKey("state") && json["state"].ContainsKey("any_on") && json["state"]["any_on"].IsBoolean) { + this.StateAnyOn = (Boolean)json["state"]["any_on"]; + } + if (json.ContainsKey("state") && json["state"].ContainsKey("all_on") && json["state"]["all_on"].IsBoolean) { + this.StateAllOn = (Boolean)json["state"]["all_on"]; + } + if (json.ContainsKey("class") && json["class"].IsString) { + this._class = (GroupClass)Enum.Parse(typeof(GroupClass), json["class"].ToString().ToEnumString()); + } + } + + public static AGroup CreateInstanceConcrete(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { String type = ""; if (json.ContainsKey("type")) { type = json["type"].ToString().ToEnumString(); @@ -107,54 +133,46 @@ namespace BlubbFish.IoT.Hue.Interfaces { } #endregion - private void ComplexInit(JsonData json, ReadOnlyDictionary lights) { - if (json.ContainsKey("lights") && json["lights"].IsArray) { - this.Lights = new Dictionary(); - foreach (JsonData item in json["lights"]) { - if (Int32.TryParse(item.ToString(), out Int32 lampid)) { - if (lights.ContainsKey(lampid)) { - this.Lights.Add(lampid, lights[lampid]); - } - } + protected Boolean SetGroupAttribute(Dictionary value) { + return this.PutDictionary("groups/" + this.GroupId, value); + } + + protected Boolean SetGroupAction(Dictionary value) { + return this.PutDictionary("groups/" + this.GroupId + "/action", value); + } + + #region AConnector + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("name") && json["name"].IsString) { + if (this.Name != json["name"].ToString()) { + this._name = json["name"].ToString(); + this.NotifyClient(this.Name); } } - if (json.ContainsKey("name")) { - this._name = json["name"].ToString(); - } if (json.ContainsKey("state") && json["state"].ContainsKey("any_on") && json["state"]["any_on"].IsBoolean) { - this.StateAnyOn = (Boolean)json["state"]["any_on"]; + if (this.StateAnyOn != (Boolean)json["state"]["any_on"]) { + this.StateAnyOn = (Boolean)json["state"]["any_on"]; + this.NotifyClient(this.StateAnyOn); + } } if (json.ContainsKey("state") && json["state"].ContainsKey("all_on") && json["state"]["all_on"].IsBoolean) { - this.StateAllOn = (Boolean)json["state"]["all_on"]; + if (this.StateAllOn != (Boolean)json["state"]["all_on"]) { + this.StateAllOn = (Boolean)json["state"]["all_on"]; + this.NotifyClient(this.StateAllOn); + } } - if (json.ContainsKey("class")) { - this._class = (GroupClass)Enum.Parse(typeof(GroupClass), json["class"].ToString().ToEnumString()); + if (json.ContainsKey("class") && json["class"].IsString) { + if (this.Class != (GroupClass)Enum.Parse(typeof(GroupClass), json["class"].ToString().ToEnumString())) { + this._class = (GroupClass)Enum.Parse(typeof(GroupClass), json["class"].ToString().ToEnumString()); + this.NotifyClient(this.Class); + } } } - - protected void SetGroupAttribute(Dictionary value) { - this.PutDictionary("groups/" + this.GroupId, value); - } - - protected void SetGroupAction(Dictionary value) { - this.PutDictionary("groups/" + this.GroupId + "/action", value); - } + #endregion #region IMqtt public String ToJson() { - Dictionary json = this.ToDictionary(); - json.Add("StateAnyOn", this.StateAnyOn.ToString()); - json.Add("StateAllOn", this.StateAllOn.ToString()); - json.Add("Class", Helper.GetEnumDescription(this.Class)); - json.Add("LastUpdate", this.LastUpdate.ToString()); - json.Add("Name", this.Name); - json.Add("Lightclass", this.Groupclass.ToString()); - Dictionary lights = new Dictionary(); - foreach (KeyValuePair item in this.Lights) { - lights.Add(item.Key.ToString(), item.Value.ToDictionary()); - } - json.Add("lights", lights); - return JsonMapper.ToJson(json); + return JsonMapper.ToJson(this.ToDictionary()); } public String MqttTopic() { diff --git a/Hue/Interfaces/ALight.cs b/Hue/Interfaces/ALight.cs index 6c042bf..53d6e31 100644 --- a/Hue/Interfaces/ALight.cs +++ b/Hue/Interfaces/ALight.cs @@ -17,23 +17,33 @@ namespace BlubbFish.IoT.Hue.Interfaces { #region Properties public Int32 LightId { get; } public Types Lightclass { get; } - public DateTime LastUpdate { get; protected set; } - public String Name { get; protected set; } - public Boolean Polling { get; set; } - public Boolean PollOnce { get; set; } + private String _name; + public String Name { + get { + return this._name; + } + set { + if (this.SetLightAttribute(new Dictionary() { { "name", value } })) { + this._name = value; + this.NotifyClient(value); + } + } + } #endregion #region Constructor - protected ALight(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + protected ALight(JsonData json, Tuple id, HttpConnection http, Boolean polling): base(http, polling) { this.LightId = id.Item1; this.Lightclass = id.Item2; - this.http = http; - this.LastUpdate = DateTime.Now; - this.Polling = polling; this.ComplexInit(json); } + private void ComplexInit(JsonData json) { + if (json.ContainsKey("name")) { + this._name = json["name"].ToString(); + } + } - internal static ALight CreateLight(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + public static ALight CreateInstanceConcrete(JsonData json, Tuple id, HttpConnection http, Boolean polling) { String type = ""; if (json.ContainsKey("type")) { type = json["type"].ToString().ToEnumString(); @@ -60,14 +70,11 @@ namespace BlubbFish.IoT.Hue.Interfaces { } #endregion - private void ComplexInit(JsonData json) { - if (json.ContainsKey("name")) { - this.Name = json["name"].ToString(); - } + protected Boolean SetLightStateAttribute(Dictionary value) { + return this.PutDictionary("lights/" + this.LightId + "/state", value); } - - protected void SetLightState(Dictionary value) { - this.PutDictionary("lights/" + this.LightId + "/state", value); + protected Boolean SetLightAttribute(Dictionary value) { + return this.PutDictionary("lights/" + this.LightId, value); } #region IMqtt @@ -76,11 +83,18 @@ namespace BlubbFish.IoT.Hue.Interfaces { } public String ToJson() { - Dictionary json = this.ToDictionary(); - json.Add("LastUpdate", this.LastUpdate.ToString()); - json.Add("Name", this.Name); - json.Add("Lightclass", this.Lightclass.ToString()); - return JsonMapper.ToJson(json); + return JsonMapper.ToJson(this.ToDictionary()); + } + #endregion + + #region AConnector + public override void SetUpdate(JsonData json) { + if(json.ContainsKey("name") && json["name"].IsString) { + if(this.Name != json["name"].ToString()) { + this._name = json["name"].ToString(); + this.NotifyClient(this.Name); + } + } } #endregion } diff --git a/Hue/Interfaces/AScene.cs b/Hue/Interfaces/AScene.cs index 3fbe906..35fc321 100644 --- a/Hue/Interfaces/AScene.cs +++ b/Hue/Interfaces/AScene.cs @@ -7,94 +7,32 @@ using LitJson; namespace BlubbFish.IoT.Hue.Interfaces { public abstract class AScene : AConnector, IMqtt { - private Boolean Polling; - private String _name; - #region Properties public String SceneId { get; } - public DateTime LastUpdate { get; } - public Dictionary Lights { get; private set; } - public Dictionary LightStates { get; private set; } - public String Name { - get { - return this._name; - } - set { - this._name = value; - this.SetSceneAttribute(new Dictionary() { { "name", value } }); - } - } + public Int32 LightStateId { get; } #endregion #region Constructor - protected AScene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + protected AScene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(http, polling) { this.SceneId = id.Item1; - this.http = http; - this.LastUpdate = DateTime.Now; - this.Polling = polling; - this.ComplexInit(json, lights); + this.LightStateId = id.Item2; } - internal static AScene CreateScene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { - String name = "BlubbFish.IoT.Hue.Devices.Scenes.Scene"; - JsonData scenedata = http.GetJson("scenes/" + id.Item1); - return GetInstanceConcrete(name, scenedata, id, http, polling, lights); - } - - private static AScene GetInstanceConcrete(String name, JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { - Type t = null; - try { - t = Type.GetType(name, true); - } catch (TypeLoadException) { - Helper.WriteError("Konnte Type " + name + " nicht laden!"); - return null; - } - return (AScene)t.GetConstructor(new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean), typeof(ReadOnlyDictionary) }).Invoke(new Object[] { json, id, http, polling, lights }); + public static AScene CreateInstanceConcrete(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + return new Scene(http.GetJson("scenes/" + id.Item1), id, http, polling, lights); } #endregion - private void ComplexInit(JsonData json, ReadOnlyDictionary lights) { - if (json.ContainsKey("lights") && json["lights"].IsArray) { - this.Lights = new Dictionary(); - foreach (JsonData item in json["lights"]) { - if (Int32.TryParse(item.ToString(), out Int32 lampid)) { - if (lights.ContainsKey(lampid)) { - this.Lights.Add(lampid, lights[lampid]); - } - } - } - } - if (json.ContainsKey("name")) { - this._name = json["name"].ToString(); - } - if (json.ContainsKey("lightstates")) { - this.LightStates = new Dictionary(); - foreach (String item in json["lightstates"].Keys) { - this.LightStates.Add(Int32.Parse(item), new LightState(json["lightstates"][item], new Tuple(this.SceneId, Int32.Parse(item)), this.http)); - } - } + protected Boolean SetSceneAttribute(Dictionary dictionary) { + return this.PutDictionary("scenes/" + this.SceneId, dictionary); } - - protected void SetSceneAttribute(Dictionary dictionary) { - this.PutDictionary("scenes/" + this.SceneId, dictionary); + protected Boolean SetLightSceneAttribute(Dictionary value) { + return this.PutDictionary("scenes/" + this.SceneId + "/lights/" + this.LightStateId + "/state", value); } #region IMqtt public String ToJson() { - Dictionary json = this.ToDictionary(); - json.Add("LastUpdate", this.LastUpdate.ToString()); - json.Add("Name", this.Name); - Dictionary lights = new Dictionary(); - foreach (KeyValuePair item in this.Lights) { - lights.Add(item.Key.ToString(), item.Value.ToDictionary()); - } - json.Add("lights", lights); - Dictionary states = new Dictionary(); - foreach (KeyValuePair item in this.LightStates) { - states.Add(item.Key.ToString(), item.Value.ToDictionary()); - } - json.Add("lightstates", states); - return JsonMapper.ToJson(json); + return JsonMapper.ToJson(this.ToDictionary()); } public String MqttTopic() { diff --git a/Hue/Interfaces/ASensor.cs b/Hue/Interfaces/ASensor.cs new file mode 100644 index 0000000..664fd10 --- /dev/null +++ b/Hue/Interfaces/ASensor.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Interfaces { + public abstract class ASensor : AConnector, IMqtt { + + + public enum Types { + Daylight + } + public enum TypesIgnore { + Zgpswitch, + Zllswitch, + Zllpresence, + Zlltemperature, + Clipswitch, + Clipopenclose, + Clippresence, + Cliptemperature, + Cliphumidity, + Cliplightlevel, + Zlllightlevel, + Clipgenericflag, + Clipgenericstatus + } + + #region Properties + public Int32 SensorId { get; } + public Types Sensorclass { get; } + public DateTime Lastupdated { get; protected set; } + private String _name; + public String Name { + get { + return this._name; + } + set { + this._name = value; + this.SetSensorAttribute(new Dictionary() { { "name", value } }); + } + } + #endregion + + #region Constructor + protected ASensor(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(http, polling) { + this.SensorId = id.Item1; + this.Sensorclass = id.Item2; + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("name")) { + this._name = json["name"].ToString(); + } + if (json.ContainsKey("state")) { + JsonData state = json["state"]; + if (state.ContainsKey("lastupdated") && state["lastupdated"].IsString) { + this.Lastupdated = DateTime.Parse(state["lastupdated"].ToString()); + } + } + } + + public static ASensor CreateInstanceConcrete(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + String type = ""; + if (json.ContainsKey("type")) { + type = json["type"].ToString().ToEnumString(); + } + if (type != "" && !Enum.IsDefined(typeof(TypesIgnore), type) && Enum.IsDefined(typeof(Types), type)) { + String name = "BlubbFish.IoT.Hue.Devices.Sensors." + type; + return GetInstanceConcrete(name, json, new Tuple(id.Item1, (Types)Enum.Parse(typeof(Types), type)), http, polling); + } + if (!Enum.IsDefined(typeof(TypesIgnore), type) && !Enum.IsDefined(typeof(Types), type)) { + Helper.WriteError("Sensorclass " + type + " not exist."); + } + return null; + } + + private static ASensor GetInstanceConcrete(String name, JsonData json, Tuple id, HttpConnection http, Boolean polling) { + Type t = null; + try { + t = Type.GetType(name, true); + } catch (TypeLoadException) { + Helper.WriteError("Konnte Type " + name + " nicht laden!"); + return null; + } + return (ASensor)t.GetConstructor(new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean) }).Invoke(new Object[] { json, id, http, polling }); + } + #endregion + + protected void SetSensorAttribute(Dictionary dictionary) { + this.PutDictionary("sensors/" + this.SensorId, dictionary); + } + + protected Boolean SetSensorConfig(Dictionary dictionary) { + return this.PutDictionary("sensors/" + this.SensorId + "/config", dictionary); + } + + #region AConnector + public override void SetUpdate(JsonData json) { + if (json.ContainsKey("name") && json["name"].IsString) { + if (this.Name != json["name"].ToString()) { + this._name = json["name"].ToString(); + this.NotifyClient(this.Name); + } + } + if (json.ContainsKey("state") && json["state"].ContainsKey("lastupdated") && json["state"]["lastupdated"].IsString) { + if (this.Lastupdated != DateTime.Parse(json["state"]["lastupdated"].ToString())) { + this.Lastupdated = DateTime.Parse(json["state"]["lastupdated"].ToString()); + this.NotifyClient(this.Lastupdated); + } + } + } + #endregion + + #region IMqtt + public String MqttTopic() { + return "groups/" + this.SensorId + "/" + this.Sensorclass.ToString(); + } + + public String ToJson() { + return JsonMapper.ToJson(this.ToDictionary()); + } + #endregion + } +} diff --git a/Hue/Interfaces/IMqtt.cs b/Hue/Interfaces/IMqtt.cs index fc96a62..36001bc 100644 --- a/Hue/Interfaces/IMqtt.cs +++ b/Hue/Interfaces/IMqtt.cs @@ -5,8 +5,16 @@ using System.Text; using System.Threading.Tasks; namespace BlubbFish.IoT.Hue.Interfaces { - interface IMqtt { + public interface IMqtt { + /// + /// Gibt einen JSON-String der Eigenschaften eines Objekts zurück + /// + /// JSON-String String ToJson(); + /// + /// Gibt das MQTT-Topic als String zurück unter dem die Daten des Objekts Publiziert werden sollen + /// + /// MQTT-Topic String MqttTopic(); } } diff --git a/Hue/bin/Release/Hue.dll b/Hue/bin/Release/Hue.dll new file mode 100644 index 0000000000000000000000000000000000000000..f73a4a4829b981dee6791dc110727a4570d7567b GIT binary patch literal 52736 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 literal 0 HcmV?d00001 diff --git a/Hue/bin/Release/litjson.dll b/Hue/bin/Release/litjson.dll new file mode 100644 index 0000000000000000000000000000000000000000..892f38a25e68ff9893c46b787a53a132304c04c8 GIT binary patch literal 50688 zcmdpf33wdEv479(YWC1!?dr6yUCRfHjzy;}V}mVAG8Xv4mca%aS(euJVp$`0B_9Ym zayWv)91f5J2v=f)31>nQNPqwj2n0g#dnDlsfk3!I4iX@QaQuH&-7`CqWt+Ub_kHjC zFKkU$b#--hb#--3&uG2qluLz62;s)}v(JRM8&~?wWq4>Xg6gc$kFvyFzMoIJTU+?^ zNgW&G$#8$d+>q$n9PaMw>oW(!>tf-=U|%@i7j9j$JiOWLiA6Ir{Zo|arENki)Li2I zm)<g{Dw9!}S4gt#9RLsAnj;u^;HWqgIm=CUsJCW2xEk0u1@^l^!`=MpRZpSmiP zNce;ycL^0<7UD@V!~q{oWPtYO^FovyPd!Lii`C~ z9FnK$LlYp=tfvmvA4qeV4OCO6NAkg4Vd)bfqJ=;yMo6BS<3w@^DX!7Q@fbL)7*HJ~ z8&TochrsMMr=y%+nd>oUppan%^+*#6=1c&Oc{E`4YcKgdRffl$1>{9z0R3qd_`)c< zm?*+Cg`ezd^@C}*` zxSD9p=3)dA{1wxsSzIRM_$zX1T;fQK2g@hm8iaaUb$SBE>rc)>^_jXm0;e+kx@&;y zj0ntTz#Jk6cBVB2m$t?)E$lMqqM<)1A~ax@zYu(B4n9jJ&xqttm!1*ScsdNT8Eu2Q zg+}1|%CIi4*+OzL3L28j#Ay9ch{l28@i=7Fw3O~f1oLZDEF0xo|(PBs}+|9$)bBE#RZXSy+>Y$IbEiFkb9A1p|nfj2Q^=U`*3Mp%h#icDM?s#}S zquN75Ztpgahk(l2qa|pVHChhV&}qfEJUkdRKdOF|!f?$KNSl-7RWBVTU3J%RIxaS~ z#G)d~rKm*~HPcBgwy0S|WncRS#zP<3hieetsa56LeeES&cIX zG?R!Be8tk>E7lZWp`lUHCnvcynx43fL}J-s0S>VhRb9!LIv$j&D?;5{j&?k#UrCw8 z(Og*=%S1C(9QEo$#C2*t-lf z433~?g^tuB_DJtn4u7UxmgXdItHrX2imKX;+hC6}S+VOBD^oQkB6>WSMzDyzB5lsk0t$<$bb>*5zYN*gnY(_>^ zrb{>B_=ZK)Eh55)VNQ{pNJS(k*0y0zlSK3)iBJcwCW%NYBoPbEFwre_gwBnsc`maG zsLo5*Ul}mW4vd9$bwzr?VaR0fjh6z(AOeq;p33=LW~G#be88yca+y(wf zX)l72mgx$*A}dHP>A(7I>E>U`;fQ702Ut;zI zjmuV^1)~J8@-{&wYmvzX0}G+UiXD0cQj98(&T+Itrj3NsvaH~E`8Z_5QUOe0euazu zN_tlmxGedNHcSO&Ym_}8-xUJuQDDE~JFPBUllV4`02H7NIiE`=^(Rw5D!t$(U@8hl zEY}c0b0zbfMX}GtTTTQq9Ieixi09(XCxTl^qCinl2u4no6u1;91S6}7l8?=R2LZQZ zKferD`bes)UaN5NXwV)u6#@yffHg!7zBBP{!53~JDp%>_E~Uz5-m&EpsBA!Nqd`iHZ;E6er#(>Zij7V7X0`QgZ0=>ZREiY$nhugqb{iWqu8AHvkSF*kgzNUp(X{6?}6 z&XDfOH-qGE9`;h*Fi%4zCx(g}GRP-NafQ(WE;9gjze_cf&W){?gjjaj0$!STYl;h2 zwbA4vlCfFfydsQm6~4HK;#=R!mPCPD^kW{_-kov=csAC`GReJR6R`XuRyAD z0IaI*{^%~xMtTUr-WH?lX?o&psO;KZ2+Ks)gB007^mPZ&-IN$`#S_$E|8B%s z&Cz1Lo)}x9n$Z5qLZ|7t#b7C4)Q8ZaJD1WXe^vU^7%#7FT253!d+sn7;P6%0 zS>TWw8|E;|FHM;CQbqs~J&+dJ0sKYYCxrN}$a+Ay5W_TG>=Q-*A&-q{++7-jxRsQ zAMgeJaw6D-hKU(d6XC~1_|!zeFtlf52hp-l)YM!Ytrc}>8APSIcnViU`M?hHZQVQ< zaxfQ3_#BRiY|lD+Jo{FM542N056ggAz61%DS?V58Hc5j`Dg$Y-i4pWDn|K0-w24RB z#K8FN#x!$vPwk);o8!&T@ddm=pBy;rYnWI8YT!N$+-nb9j{6Rgb-(3<-xfs=T0Urq zA7k9u@d7(2Kw$BBa6-bKP3vAacR_4*_6|mpa0U;>1n%A?r-IrQc=3A^cUr zQ6%^%;$q?o_;SHTm?dp$zUv0_5>WjI>61kn=A~eHDKzZ>VVN&wqkj(}n#Q<8_&S#II>C^Aq@RaXm6*J+gLF8U61fssg~u29HY8Kv%#B<{^@S)ET!bJ%lgT>YU|vnE36Fu@ zl_@?w@(qxreR4D8;EU!yhq3FCZ$X}l_Sb+ZCoPy3!ICqoi%@{m8`Xit28eRn*kUQv zBiEwM09LDp<&zdc+%R%Hl@pTJktpl~*VEM#@VFv3QI$8~t@g?Oi=&9p2q^Gp|Fp{A zD3MijaKDjFjeG$L8aJSrf#_0M;vK-&tD%XX&n_6EK!$;k+&b6^c*n2_MF`a%l7 zk;G&MGKVN;R!u^IqVhm3bo2tDs4T~9V#55DU$GdXWU8z{mI-_KtBM0zqq%d zAZCw}GOg)EkkCEWyitv{@(;S^B=*wG1TrEwqm}Z1DjQAZVD}C{CoMpuK=3e>K4I#> zMb#CQ54%MvOoS|N%sPz1m1#j@-O-9PF{8+pCCOKxgX3dbHS+citJp9qWM!FMw|6dj zH<3-)btS2~W~#$P+jXU>x_MNGNNCrE{He-0RGH9Zq@x~CUIXU=MNAd)(TtR*364k1)yA{q0`CiCqk2!+lj z$W*K*RO4u}#BPEp>1?tt)r4vsO^&vkAo@9*bf=n7jibp@y9olNvq?{?3Dr27oM<;e z9CbE{rJ7KUqscP62?D0G$@)|is&O<~ZZ|<_b~f3NYC<)RCLMMYL|K*%;Q~5-yIn}7<6#qC}JUw>o*z%msld0xZqnaOUH@Aa`>K%KZv-v=(In^A{+zu$R zdBvA?7iU7%8M-TZ3qq9%U&a}MiIwPgrWh7a<6ipat@+}UUh`K3Taw46w?QYVCG`An75N)NpP81emJY-=dE-oDt91xQaVV6 zC!*YXM4AD0C^|@KN7a0FD9TBQc?a}Kugv!F zd8W&qRyc6A28+6OYj{OfgS=O(&|UemNyTkDkOw`CNm~ zTI--wA#~D{8aVwsFfq>NTT;!b#^5AMHot8L5@#px))X(*=nTa4<)g9T zJOup;B09;!`ER}~(RuzkN?rAS175*=SpuSq8fN$@g$Lkf#c;iy10k2*}5_39&w3_pO*xZk3yi| z2xmZ8Bn?tzF?4F-MHH*1=7&zbW@9rjFosS& zWbK*)tD7}W@u8tZwF+zK_K*zv;TT+pV{jjW!HYU&04j!tY+DVHt$d(agQ2owrDDof z7mTqL*ACm(7`6oXQv#@%60mEGfbY6fu1Icf&D*7kho3{)J84COJbAS9}f;Z}TJn@i9?x#dj7Ib5k^Q51yt^q0 zl00~Yh5OHU$mVhB0=Lcl(T8w%;0~QyKLPJ?F>?V&Mm#R#iZ!Sx9>OeG|X^)P*=Om3<|$&N~vO)yr#ZH5BX)= z{N0Gg(g0A9qP!)C9H>5952ah3I4j9->nA!pi5Tc64GphHsJdrRH%y+XM?7dPT|gfqJJr|fQsNzB<=~aDt0@E6iA)HunQ>=&0bY6# zJ1CowLO2#kN#-xni~pr8yh%%fJ;@!^(8#}>=st5MTUz5dFgM?n`l6;m3BvFqP!Gs+5h!&xjCMqsS zWleugWlb(;1Ikqk;4}2mhfac|A>5w1tKoTsOdVPZSg>-KE>h=e`0CI~pm_HR4};W9 zxZ2Vnfds*;1zbYJ@x2&daI@GG(d1~{?2E)&yOpY1M$)n!-OofyBodd8%V#?Hs@Elc zHZBw12e!vjbxh)~W0^>q>Z#7|oh^`MPV-qKlka9=lK6vFL}S;BF2VxUhF4KJU(R_E z&p9xfX*fI0D=?Z(J^36>)X7{-$J7jycKQ$#SX1+PFknsR;C!VKJ(Xb%(=k`FiY#9jb0I&( zF@iL$s&v?@x(qLhA}FmzMJQ~(0HxGxpybwduge;jJhUm-3nC%%GsvS{9Y*Dkt199Y ziBQ0E!JbqLJFg|-RV-X7Tk7P8BM?XAMRYDZf>iR;Q{Y}<(tF2mP!++W4a5ux6q!iF zij)y_ReKWKFtp0F#5uSqHy{toAEwY%aoZUF9TY<&uR+# zKkn?w*$g{xEHwViH&{%gJcxu93{wn0AcW#|Pe_+Bd#xq2;D1B^8!goi zrT@(q(>VImu6IcN@xV}&lV47B*u+H}CG9CyUYGd}3|y(X$U)_Vb3k8fa3EAM6}J%X zT7+~mnyeTmlA~5qSEY{${GNhUc6=n@_Q zOY)E8Y^JR3leN?lGOx{aN#dnFWB7C`4lx|=Mw#ahsTzOuP_x`BCIg*@?~(Z8P*og- zZymmRs7o^FKq%kw;+u|d7QO*|D}y0_MJT-@lu;4NtO#XQgeFwzp_*0XkB0aqb|w1g zp^}SAahaN*`>AmZ)JmQOaKqFBpvR`rLZEX}=tQ7%Q)m&;<`h~CbY2QA0osy6OM$kg z&@!NHDKz8*KHq`+fgk6<(}6E=;2FT%9e5`2;~jVw@Dm*P1mFuDcsB4w4m<#Su>%hR z$Bm3-mrxGyr4Bq7_=yfY5BM?%PIg`Hz{#*34xB8z!hw@%PjcX7+mjtQ8F!_PpXkE; zH6#0*`tE;^hB?@ z+ftFDo?6plV7x&C<26y5^;a0835Uga&7^7--yR@P2&{5}S6am@k#EUr+7UCOpgKC2{qqWC0lhsj2v@?n}BJv6>r6w{bFWSN%kqp6{} z3T}6F2@*A&98f&mh0)5~-LAG~i>Lp8W-e>$Eun|81#Y{g-$yZb?2mLa>2QotOVYQ{eLGkQyrCZZ?$Jn~2 zF6x}RnKjnBAAgxu4m!F2&55d_@UVu)GmNuf8HmRV52Kg#vC`slT$DN%7uD3 z*~qgfa{nBE;VAc}AY6TE0q2&7*S-9py5D$MKSW+kc_Kn#d2}&K(RSLAkBZKtu^m%+ zI`TfMTu0mWdc?_9NC~4;v0hfSxgk6Q-&yLa<61dW59J?nppn@an@U~vAu^3)vgo*mv91WE{r>tZZ=m6Fe%04ySD8geA+1@di)Rb=&#vh_=AoIwE)ShrK zA3NfZK4vG%NM!##ADa!c9y&fAQIAt$GM1G*9}v7Kb{&0KITbXbqi{ck+bTO_3t7p0 ze#MRWD^FT{>@TGtYb1Mqh#nf!`8UoCq|NjQj>3(pe!i(N@}hYDg_s_P^n5(?8KOgxsw{a` zXC%0tpi>a@FLXm`Jqst@k9im&VsyGh)$SdU^=F*Xl#Brd@k!SKICp~Qd0~3db-t$Mqd$4o(8=~komYPijwa^?TX{wuDy$K*B*J7 zG&us*Oqx%mzY>XGGd&GlOF-HI=X6K@X49vG4qw9yomRu8)DV&#vQ8w@`=dz9i1fiI z5)R6=$lr;?Z#ROVyNZc?|xqWps>#As7KA_}QtQ~pU5(!!>EOcYYW zrr^fgzngThDW4JrhvX^BXGEE+D0GcrV*I!rdm`o}xc79>ODfMKf^{;NLwRwH5Q0ACF@# zB=q=X;Jc+ES`)3WsjsC+IXHuZqln%HAtvt-;#pjCr=YxiAQA7|kR*Q_7)HQL`HF$?uLh7D;vvyA79s8o5{)PyXFgnHh|97F zPRu;NXo7gHi0I9kyYme37{h%G&&(o93ByK)E16!MxfgBI8SZ9EGV`)*LtL7h$Th?< zllGPv;+i~ym-+UV_(kXBUaujRPbSLmng8iB;tc6e;jhaA3=J2Rhs%jRxrjK2CldUV zMwC3Rbus6)KB7Fs(mpAihgN^dBg*ml1b3wqEGr<*%QC2ZJHtST%B~>6GYScMm}fJ? zBN@2)z5^Ok;blA(1moiV7^?aFI z%?MDd39R9F{Z#&vPH>8w;2iETjbT2IF~GV#%bYW~m$Qn_gl#Y2QTJq!Zm}Y2+rxJL zG50u!NBuo6f5wtOV7P<(n#|=-S>|y(uIE`Fy_9_55vB*Zudf%=xb9|Kl}}2L+lfDu z6Tvg_A6)3J^c(Y=8M_|T*9&ii*V5}P8vK2o=f?asz(4{vvxu-}YrrNBt0 zek=^d^R9my^uCNdzTj?_?^URXpga6@LiS z0B$JT0ciNo1}tXyyzgR^|IF}q&vhtw1iu4#cgbylmvZ@9&x0t>;c{2;lPC{ydCJ5W zP~M1g${N4LNN)`jEKDc(p_kzOO!4@rd|8O#lL3O?^%I=I?+wambqQSVGj2W$*Z z0L=DLFJCVxM0ryl>A7kG!AlBDK>0_AYH!RSct$b7%88TE%R|uF5D#WhUvoYG04%~7 zYsC+J)4a9fF<%AR-c(BbH9QJA>Nh7H1^S(ZBtOJZj`s=3(Zn~o$GH>8b5^n@72vT( z4PV7bXBHA%KY^f`LGZpH!94{816=#SBr3zJ0T*zqZ4;^7z;yUI${kEUn&nh59i9(* zhL2!Re!aI|`?4_uaDCb=f-Wk@nQ{!53p_;m5asFO$Hp9QzPMDh0Nx^w1KgkyyhtZ_ ziHl%`o8V;(Gt;PiEkmzCu058=D{y-;qw~OF%H^F-uPEVuqV+?BzD*uF`#%-&( z?QGUzCBp$gLv&C4lG_mN6R&ks%n{At$yw;KY7b=EUC0 zc3^4mIAx|?>R*Ccm!C(|20}%t-qZG`FL%`45?JZL(gR+ka8|pFygWy{+x@G(l)KR9 z(X>nPf{jZe!0t@%@~XiCdpxkgOV38>^JrQ{Ae-|YiCvrT)e&0x^Jv<&`Mq9`SQ;ku zJn!-0B0NuBqcF0WS8P-m*~}}F3L~3&`LA!O9oft)cBwkDnO9t{FtV9fT&pm$nOEGb zu#ZX;Uaz=cV&dBJH#Dz!ma$iSmC(f}URKyW-ZEf+RM=VJa$p}RtO7E9!ZS(W^NMfk zWWsV4_Co12U}q`p$zsA@Q`kuupAT_Cwz~`C^NIHqwhH6(iH{XF7vu8@ce&);it+hG zhQhv&@u5G3J;S<`C`@DQNQHgE{Y_Wc1v!;y*Q&4yxi!cS&QjQSbEYFZ{g%RJ_n=Xqbt8NzSGEKpcHcc(W;JZsmT@68j1$jxcS;<*=N zEqzI1g(k9{LUDR|o;kT;x+t>zgv3S$Qmf_9AJyW>{$jrvw^0yiL ziubLuD=>q%+t^j!kocjEUGEKxpV-*9y_3XGZR}QWx%h>`-Y@%}ce40{joss&B0STm zzpr@TFZ+pis+eqJk9envRW|m7_Xsh_*elwvk<~=RISQ-t)`$x6Q-wWHVBlWjb%hO= zECBYN!hSGuk*F3QE9}Av%YbzqLA}4C?V5NpGNnF+{TjX$6&EOM2(ch4Zd6z&VnI|q zq_D4I^+&}^3i}}2t4GC03j2F*9ykLL*~67s{YQz2!iKQ=Ys4Id-G%jDD^5~a9@cxE z*sQP%u-@y%ppEr<>%|U*P4KS9FAIK2Vb^2qjp8bWor^!&e$H2$bZEFb&aBMtH@9o#m`o83uEUAiji&NLmTV$wuvcGYWJwP6>~IS z+{oDLzK28Q_zlCm6!x>pfA-E7FDvY;)IWY z%EWPEw!&U5Gk~?*oP?do*x6|3_8lkUHs<#&5MQ)0WS`G$hP#ajw{BfZ$SRQy$8vMCcANUtyO|&I7hYVSg(s_pJ~oD@-r00!AMf+iZsKB!xYy z_4_Vy%Qur;;!$mr?-<`Hc3qe6c;9IXqp{EMb=r0PKGW#3u}!`cecd)j?KX^Rx6!Wa z_f_&~0_j5ib^371g%4p}zSDhu3X}brc3r>kWbF(a+vGdLm$0!e->`3RRJ$#9UBB;K z-!>cDQ^P~DjY-}dbgBaITt5d%MJ>*Xi1VC+M||gq)e2i&dbRId z(Ll$?sGGuA;~a@KO?(1%s}=S_)*HTEg8u#(5gM!NFTRUJRf~na@4HycP}s#~ANwv9 zJvQd@?-4iH*sb0#i>GWX-G7;Q)5dcAmy6t1Df8m868{xqu8mFhe?^?Cu)h`F>-(DM zv9U`3m10C;%O=+QuNLnO*(&c-N4UT zG0KVdis`D3a+|%X-77xIZEjT7eMO_a>L#mBqrGaMT}OM>J~2~D(P*!_+2*9Z>N_?@ zd)2KrMtjw5mK4r7Z?`eZIB&N#qm1)*yB%c^b{+5iciJPOY&3;YHhQNmg|g8UM%icz zqkQmAs|O$DgWt6aZ0A6R;7l%xE>uA{8vhgN?YWh{4Fn2)l`yDd&1WtI0> zbsFu{Kep;L+NV<(?bG*<$`yYiX0xr&^PW>$hSQ>Ug?*(o57@~H`=Gc?JRo`$mQ#`k zETOO`O3K88;!K6Tgt`aC*$O)k`^rP&LWP}`PuOJ&o04~e{~>Xe!e~!@NPLqq83!K{ zx63--@g5TQ+t^b7e~8Bz+lji9{SS+k*qkYXZS{5f9~FOVm)JwWK47lnCHBFjv;994 zxs2@;b%ojDN%4-to(dY`=i;=5#JN*M^U8o-$JlvdFoWtoRG1&W^z%^`{JH2^OzqC| z7ECBYU9ZB9n4kf>Kw-}zPCO;PtgyF2dBCnxSl+}d{J#+2RM_r}GVu%X9ff^8BM;bJ z3Y(01cv{@6uvx`xsIn{QoJc7~3gM%e%q< zUt;M}s@o|%A)Gsldl}m!{N*?KpAny^x|>kG43cDvvn41%m z^Tgk9`tY*oSJ+Kl_prip(z8+L+&f+o^pZY#0j;A~#Mug?b@YnZtuR_gzY~`$jMmZb z#5WX1Yvon3S7B4~ih$j!u-CJ^`m5smjLDgPRXir^Fq=#LuZk*8p!SFv6MpP}OC_(a`+_&p-ViS<>Km|1Hr^4`zV90_<&Z3nyNC z#98hq{C^NvbK(Ulz}^ubE37l^3I88O2B%?r#Jj-$ByLjJDaI52KZ}udQs$e${vz&H zSe1Cf|E~Cp!cNwn@c&g*bxY1(X!bYJqp)h|@}9U_VQ&I^Up%9*v!TleBD+Vndk5Ix z#R7$00$n~7BMSQv*gwR*3hRO{ABov9*{%V){8MaGSSNJ(SX8Z-bpz1l6A|7Zu}1X% zsklyIe*yNHcvE2)qIaQ1InCT7($Jr#tyI`of$7>g3KQtvr9H#gPVvuzGU3)fQCMbS z5wLKa`a4f7MkI1;?=!YjxC#wmuG3i;&K2F7(+Az!1g<+sMF?bO~!;RM-sY zGD-Wo!sf$f)Di>r}fu*t2}pJWSE*_=}?nL@}67(4yopg`h>5nogVq zEy{sB2v*>kTR0Ye79h$wMFj8h{P*D=)<^bBW3e=Gj_W{cI4RN&a@0=f9I2$sUZhP( zKK>mrhpps@2M?Uf3@KZsHvdTvkf)>T8;lau; zmJ$DSZtK)1Rd$I0Jj5-CQxjz&g8zQ2nyWY!{#6|I@b9oc>%4;gz<#*Agd|9M8b=iwF`Uwz<)3oI zkgMqk_VZt|%_s6&lTqa)=8@%tMIqUX{|$O-b<1e*--S-yq)cawO4)pDv~l6+#wFVEU4$>rEAcLZ8_!eRpku}3 zy9nPC@g=%}H<-@AcM#7}GVpD~w;kU__@0RG3OuDT@xu-QK)lle=*QdIbe>wva3;fM zhV2YbWVni97hoQKhk^LdV7Q&(oecK~dg8W6Jd69oJ>n{yG3*oVI6v4YT5)c$Pke;) zf_>r-I49V}^j(Vn8qNy#i8#&(t{1Q2uKs$IXMu;xySThZe1J2GeK-%V0i<*Aed209 zCCMvVg!0W3mf=Fr_pzIRGlSn3U?h+^C4B&e3F9Ix{)CEYC zuWMwJeGH!i%)?tPAC+#!Q=qF$&c|Q&Q*9Mm-HWFT4~O=P+2X6^`%!y>_a*U`pi^(3 zHiSFnR_2^7ewyXc7IN)QNXy2#@M}3oX}K8D0&O;U7HAzPF9qeboRa~sEk9qptGzb4 zUt7(VT+MBJxK$5x-ouh7>LhJ7kFkR_pTv|7jD@7#JmGx2#YWl=FnkNzmWzN!C-?!4 zP8seI3t_!?@eJV&&8IIcc}EKXzN=|~A87vo{h!)Iw!=OAgy0_4B<6otTU(Z`Q#qR{`Fb-(R0jCAKcb(-`kcdXH^a*rHtVMr#PzGW{4IvJur-=B zI)!Z3j`8h-guk$y<;8dCGc|v@SAQ3je(ek`Q2IlCyLKPW=+DrC8Tae$dfmi_^;=-Q z1zNkF5qd(ui>2Mmk{@PzvvyUH&$UY%LaV!&@+9*-!#ppt<)38wYfOKK>8~jY&DVQO ze~;&`S*!AmxZdO16ZOl|Z*?_m+i(wjkN5@TH*4E-f8hF<`9EfUw?=dA)<{mXb^*>o zcZ!S4o^{1BqL*E(^nTylF0$miu3MPDOF!QC4_6$sjQ97nzeDD8;&?p5eNJRe@VW0| zo`+ff!(85_Wlg9NXK1hD9OVp@C%8#k0q$hT_i}lrK4xv*OnF>;J0lzC477qWShoz; zZKt5IJjpUkH1hl@8hQRMEp0+2B)hQE&p`QYR|Csz(CA%-2953qW;12BM(-`m*61!^ zp++mWgW;X*vG+(%X3o{x3xStJ50@X*CzZeN?qwYY@K(eE?JTDEa`{~?kv%zWzrG{` zKk}uYoe@p*0nSPb04_+o9&kn4YVKvVc1>`i@xjv0*Zv;P$;z6cAsNFo_mbAC97VcvB z5W}A_{7;6@gP(doN2Azy4(s!-_SKMACml5Lmi``2Bp%elzIW30Xnt=IDCg>bh^-D42UU>E?LSvueieSVjsB@Fk4&;m@B>oSSa=ZmWbN` z!{U2@Q^ehX_cH$j%nyGA}IH+y|bQ#Y2FviJt<#DV_wZ(x~4W zjrwiSNKTVR{m#~?->0?xfX`^Z0(@3`0q{lbWx$uU-vhp;y$$%L_GiF%wD$o2qWuH# zJ?&G#4>gy^D?Zk|fI`m%bn8JtuU-I{p_c$6egWp{(*O(gN>3Djkv}Irihq4#0bsG% z061Cn16GP*zS& zv4vqb!?PJqnRp@SbEjUWeK}qJ%gXd_PCxFULwJYqHIa+x+^C(VU7%g1eOvpk_Dk(| zn$R=!-TKpdwrip5LRXo)#y!`)*`0I`xu12Hq^(Qan08*;g=t?)yFcxBY4oFLZp>~P zew4tA_XT`-_K){2@b8JV;BC$0@w>2eFgjHJHtsNfo=Y&{C%7s^@X`Rmr^*QaF`MAx z@SgxbKmVtte+9q6tuFMEgpTk(0e>}t%Kydv{;>2jly591IFl(?uqLxulc}u9sjSIY zS(6{ICU>%g+nGNz$Bq2AHpd6}=ODq&xmgTz0Urxdk1tIwLiyX}R6cqF!H?3zpvbYT znMC?X%GV}S`K01$cJ1e}j8h+}!{;BhQ~xVVsrP~sg1=*U0qZuBPn2bOHPA}xUpay1 z;*V^fi4z+^|3(J!%qb@5lp`f9%On1`vu35!J%I~%1oSUl>nM2u-S7><(*X^NI0M`9r%%e zQ?QbBP^$r_;S~cNb+v#ItS$O$!bZRfyrY4$2)vu2BMX}aSdD)z5BI-#&q7C=X22Tw zuZET_DAytP#ovAa>Y@Sf9N=w9K%68XAJ!nN9pxshR1FeOKzR^3vM#p3^YN?6$dz@m z4J%m3N!tqWoCBzfbH&Lhp9iSp&E8cgpASfXLw_pD7Xa$wLcGh1_iO=magpdm`C>pF zPm;O-9}wMu4~iJzLt+Eqe;}XM#lwgVx_Crv21E`5_)BEiI-V}jPXYfLP#3?2jyj^{ zR=`)pcEHyVMRf6dF%0;+_yXV?$Ru>}CbY)h0;r2WLu*a^1yIM^&R;_L13+E;9a?MR zLqJ{p16ph1BS2mJ6I$!yW4wv2<0lpN0DdYi1I*B_08b{MF0wSdi!CMq>LOeFI$%J< z``97~sEZu!8z|=j>UcAn-psB8)J45^J<1J$x@gqC2{>Kb3phjjHvDWZ{KAd5T_yvz z!WZ0lzvKwO1(<&~Vp1jGLd>~aEJhnQ-p_X9zh&@BH!^F)ad8sfIdkLpUFhEE6#RmU z8#(e(fTzJD++rxA+;n%Z=xC zYXF~uM`eqfw4(rT)@lK7(df;&+q7c<@6hOO@4MO(z`HcM>-&MW9Pn;!1>ldglL7D5 zRsr6xO#*yCn+*7nHWlz;Z4Th0+6jPL6qQ8xD9dcgnG8Udft zngM^MwE#Y=(cR|r8r^Tcs2vCRk~RbIWo;(l@3d0^U(?nAzOHq`?k0ToCOmn51YZ6b z{A;qdMLS#jiT0RQtIyV#=%?tWex`n{{wMuIU2{cUvt6yOlU;GwpzA)@Q?6<5M)zs% z_3mx%5%*Wz``ov8{xF-2Gsq1ES9aPt?nv}CulXArDV`p}D z)-`t4)`;25VjJSgfmmW*S2EVVx!+8noIG}&Rlj_oYaj+bzSe=Cn%38JqSRq7S$BG@ zdw`fFh3je$sACG()g4gB6jrC6>Xya2dX{gB_p<^_;kpJ{m(sLtn^RM+X2!;`jAMu7 z7_n7FeL2XMXhl|HYikc~{3rOtl>}-uE`+K{# zxAb-;ll3z@Yu45ZOXU2)zHXrHZGD5AV~MVHy)nkl80;EQ2z2Y-M8p=ew-<94H~Sn7 z2FwIu3*ivB%BdIlmkK&NsT}WaP9(avxA(;dI=1)6mdDSG9aCFdcc|JrB}858>O_aR zqP=gRo(l46S*dRH;H@E9RzkI!i7|}UELuKGjAN|S0C!zmCoE33Qk^&t@#1&TpKD-fL^of8+G&^XAny z&70m^S2i?OkD4CC0ImE`Go2XtQT^!mou+WGV9X4cGT zuANakZ$|Bm#@e>p=KA@~GiEl_)wh5uHLgTmKwbZmV%_NnT+Krtk zIn^Gk%}hB?a4j->276=2irGsO@h#ZSz}Xwy9P6W)X7;tl2D;+C$zw&!^qKSKH_WfE zscCMlYpH8%nAzM^1O4mfw@t^))nU%++nSr^Bez_>9b4w+X!{c6lUpc=baslao}SKb zbMxjd_}cMF$VJc<%{@JGlU#&edK87js7+X!F#BVPf$cWIUl}C-{9ZT*oTg^dIcwu~id^_4s@Z>}nES1149jaE&%Aq)5(nD1* zx2xkYbZ3v$WZ7WfKzwtIr*{DxK`0U>CyD~egw0Qwn-!(QT#i4_$4C+g3A_vzcWsVI z|FBjfQ~I$iJNu}PO0+;F22)@TJJ7X|{)C`{GCPwj(tsDowl0kK#l+IV0Tg?wxYz`s zI#!v~5}bc<35u@h!z$|C4(%)o*_g|)fL$gD@E|A#C$u>EN;PdVurZz-gQE{;b&J`z z1;etNTBB!z3~B?U81E z5vX5Acq_i~vKKAp=KeSq35P}S_cRaSM;g`*qUCXeaR=5KTQ|6211-E12O6IgPsT^l znv==c=5@W>JK_UQR-2qaK-}Dw*p!me(UpLi=F_xoHJue!oB2@iB;0vnLrS%>Dlt{- z;~NGOyk8wy-5N`FC*u9InWZXQW9z$MxJ6JjmPnDMEm+X9SZ~)hE+td-R+sRGp26;c z11nn0{_TnQhK-J@mW9hnY%k4!ssTGGFN+k-l1}VhecMwNYUwh4Aigf%8y|4gV$sgq zj@jxkf8wKa3u`feFyE)jac-@Ujs$d6iGp);mV4{6*k*If`1OwM^$=WilZMDUKfW#2 zL$_k%cI6OtI98U&OZoH5{52mJTL2a`0Oi>)SA}QEcwo*4Y&9cOA|H zM7LGopb7w5*C3LHt^ve;#8~1P*rrNj^3%>vg&yityBGx=ne|)f;Jaf;v9@=oVz3>m68!z8sNHC6T1XuF4&F{8)!d~ zW#xy*;Rm0-`Rf~?`w9oOAA55*_m?74#s)Q)AhjJ+L&Hq*St3&u)v^T{S&A6%!!2iL%+{o> zKN&|v5*;K>rq^5|yW;j}$$G}d4T6^i9e(YPx) zWx6OOE*+-e?7O{hy{S@EE~>0mktt*1BBwBvXEWA{)RFT3`DOxvkx7dOiPu43kF;`O zDMQ^oFamF+w0lWEdlemY3)~fomW^GB<*_rw#u#EOt_O`+Em#YQajPA%M>46f*p}=j z=?JKNAq5Cs(FaYrMDEK4B$1unR2T2-9~_Xy!M-??lrTA5f#r%(<9LO(KUy9@XgXD~ z0}@p+AuHRZg&<{1oS0?Hd4us@!W{c&=Q>4`+bPiX>zRWzQV8!+@@`IcyNK^XS%XE` ziqu`HaNeR~_!e3A&3U41u8K5sq`5*z?Oj-ha01Mkd;npcGzsfY+pe+BY=w0LEf&VM zp=B>aI%&2K=W*6P?nI`}?s1GWps<56J7)c`%!i59quV=nA%_YF>~oH~UgV=VJ>drr zq;4~EZ5+0t6z!IfFN<)LLntQL8h&fMYeS!j9Hl!+rz^ZblUAjb5%QUf*}puN*b>Kf zKek%l*2J)iRK2{FgMZ@i59}K-yKq0ck4M_UH<}ILRD^YARgNNs$ zaVSnIHupjmc>r?apou$Sxf-NF4qA!q(A?UYgfV+#oo8@6mHQ?|8y+t8qCKZu_Oy&r zn(qJ*_HfMFH$I2MjSs;`2XdSz%Da1nDmkwdMsc_z?{wu*#*)C%*@skiGv5s-t&^e! zv0iutdmY{AZL{M&IL3P{im;g&69YzbTI)aQ;7rkI2HKPS;EspD28ZX;j_sHNny8Ngy68&B@GVB$MYobIGYEo1?HT|!$c6fsf_jlDNu}#=sMT)hN-p<3v2I_ z$7cx5sxWRc(4IV0t+c4(;C;m8!*fg@R;s`W9-Z~ayW?EOOvrLid;=)-AQPwJRBEQ% zA+*7P368J%au970hpfzG^dYEJ<)GEa@v-svO_76AyAG2hi+z*^1u1!iE6FSpyU><6 zj>5@Z-6Ak)vG14*q zGSmEA0&QO2isj^D(@zY@G?I^$BtG5{)OkY^k$ybw597n_ecX_eG!np}6Miv(gNk^9 z3w-mW5_9T{XQDmHMY6XlvX)H5bCF)+lb$KdI8ESooC&v6zGbDkoP61Lu@YN^lOjsf ztnpBqqK;*$g#Ck}CKYA!Vqx^a05Owmw%GT~%gn7Jfp5HL8$1lRE;!fX`;={zf6{$E zuMGKwhKB)MUJtS+4pAvrg=CP{k!2XAi%txr@5&qfkVn8X#H3%HK!x=>1jf}~ya;hjWr>Iz)DP}|G(k{A93T0ggyGI2Rj;D@+wMCo`)Ho?(~wgdQz745*Nw=P_}S-y!k@ebq+<1IZC^-1s@ z!BXjsv0hNaXe(<6m5$np|1(l)BxHjDq5Y5P>9nkN;#hmg{zlv5(BobQE07&xY(Z_w zzb}#cvn_eTzbnsS!+%>=nEs9l^RYqvPmFWaOmtf-wMHmuI_H^mrab8V0+6BD}GO?uFKEIzID3P>ey-E)pGs&m*jXpvL z);d?WsHW&}u+figD~=UU9-XU*+RydLdM$)9slc2Zby%1$JU%TQK$9&$lt!SH) zr{=K0L=^3WQ$#P}}ncF!V5onZ>Qj0k{((c_^Q->Pum<|Uo&1V>6a*jpr3UcR>x~Ke2TV<_8 zr(7xJz)0A|I}X`F?m_*Scd{qVKed-WMfx5gw2=!JL5*!7(h{XmvK5TvgieR1tpx7_ zu*fjPP+%qJlAK#nilZ%=leS1CH!%JN#%K$TL4H5vQTMd?NuAAHn=&5_25-J+O4-y~ zLT!YWtB)OMOf8u~=t3@PO}ulsv}G>Gi{p#Amh`~3l}Xz)xfA!%*^Ihw)`(m!revks zB{81O*hIzu$FX^-zP-9)3LhI%7oLBwqqxmWRpfQp*0@5&FGo2 zTOHBHc9zRi&I|=fr#)!;M{CrDnYGe|v78Q#s+}4++c+18tx4Ky8aW{55D*I&5WkcI zxQ^i<{=uL1>~UlZ;Ull11t!v>2zJ6}EoR|bgI^uMKNahOcesX7e>6rUqW6?HJpKF& z&6iiV)a`kz8h;uQ)-JBT#{B7JAon#6*uPFW9AJ zOySqYFQb$yl+)`Nk|Mb(jELzHAi?j1IP2V#TS)Jh{{ zCm|jHpU3J3(z~oO1m*~D(BrZdQ$2ZLh8)NsbKr-B4e$XEpl3M@uceXD^(-F7PD>_+ z)`bqUJ-XsimvoPA_u}{HmV^wCD`ggMUT|j~nTZVZt|c+U;|>l6N6rnN5g6Hp(FAyU zXj*kH`KgpiK=+`Nk&A*InB^>&N7K^;)GMLUWCcgA@Il+)$o2T&D>T5s&Qd@3Nab+Q zD$=Af+xJtoQ!t584P+W#pd?L*;Lb^QeJ~P8)yvj3L60X8E)9;{PZhwv19~FND97>;Qv60L);W)xIl^Y$N>D5 z1V2*hfr_N>C>d7!Or?%wkSz{Su7%`xqP#DrFgXku(Qt(S>cWFtKN$ zx9cZ)CII69625#N25uwF>kUlF3V?WzoCMHd46-a7dQ>kpmbvg#`$8u%C2|J8k3Qg; zB_*XL9^u9WV?hdy7`#FcZl)_QY_A8amALSaoDi1>j-CM_X0fK!<3sC| zw*jG57ARqb$wKb~fr}F@&EqQ3vq;1O9e=!pYXmjDTAA?RT7?Be%cmsRf!XN?N15gW zy#awCN7KtFEWpmRI;hJ?o)fh!T7w27`!z3q9Dzs>1W@Y5#KHO&hCw4{1$M@;YEnGx z3E-8kfr8<2d;RIOPP{I5-#GsEOV0}Igu(92&U7MPC*pS^>1i0|$m3p*s}yeioRMLZQ{aSFT8_U~^@57;J*4mwdc}pZHw_@m#e#MoLrkE#2^a)C0O>w}G-{Ym zz)ip);3426kU=05KjNoBn@pnm0bE%S8`yaa;>VplyEy_AsODFONj1SVtuVHlUzC4% z=IWCsHoUxx{~*e(;e^4h6M#Vkv;%3~%vVMM91D+6KOC<%_O zM=Y^O2ohFNh9{SSuxjGXf9A?g<2&O*Xb7z)Xf%H7NTTW&vVj1k7Y8`TbmmAoLzwL7k)AGr85QfAbBWD0FBhm@X_ktr^wKFWFOpKgTI2~`gDZp zk?)g7aLA^}9b7=MJ0sW>TmV}jq!z>NmEYLzBU=P(@CU3ESF!zL-6Ohll!feIc5ne! z11=+vd$Kh|Vu+R3k;e@#P=G1HV!j?%>MqL$B>7=ON^Xx1nmA$1dq9NG0aY@Heyk0-(*>KG4 zQt}Z!xDxDu(#pY=YGTN;t_X73i12zNGWE;?QsRzKVjH297QmYpfhn>WqD-y=h@tza z4Np881X`K+d$iBOzeYka2>%`@MVt;jpAKE|V9|cb1+OidTaz02D3alCKQ-Y|N8cUv#k zh6l{>z{YqooHPd$-7!@3#Du`V5IALKZC7nWcTZh)-OPrX>W0Ren(C(7nf29;(|hV? z#Cm#q)^&B^mIA+GSQD+Si{chyktRx_i`zQvH;=2-tCh##AG4bg#lJ??k(Fy#sy{8H z_uq4=RoJcxGq(?clV6&nQ3>(>OKt=!=*Iu+i5J z2cHml$d+jB?OoIr?-Ts@Kd~6U3`uQ1n+h4(w8zmFhZ{xy(B4QNa%K9@Dm$-KrhV1< zp)J<>U{MMzMEu=+iZ&Lvcxh)n8VKn@AKHg$8^#SQf2tfbB9!Axvgk@5s<+0#g#Rx9 z{Tp}!f9)?uMh{2M2-fETbmCip@5+~j=%ZXoefnGtD4m33oKCJKdnGCNTrju`RWLZ&QRCmkcK|=dj$C171YvU zO%jJUbkrnIKAAz>;eHB2HRzOjHguq*o0+EuM;r3L1{{OH2Ka3a@*X-@-HiX}xCoD~ zL8tT70mso5oq(=mE%<~RzC(7D?fO|aJlDdNWLTdj#B|LbRV!+eIOd{*FFM9cjlt?m zK7+#9iojNCw>t^#=%oJtwR6TO2m?VBjn}b|s9mt|0D@k@Dpf#2a!-%sdpoWs3kqq) z6h?9X?w|QhcpLUTzGlR>+VN=x*5LD|T??Qlhv#MGmm9saY0Dfk`VFv2%;?Didvqt} zZ3(l{Dkr`q~O@)}z zYmJksmq^@yJ67mn@um{jG1Ys1`V@3pZ_eCbk;13C>y*m{z3Y#-87AE-v#;p1dCbR` psg#*CoJh0cgbw+*HMmPDr2MAKdFDD7-|ulI#Bf-w{0n}d0}tYM&(ReadOnlyDictionary list, JsonData json, String key) { + if (json.ContainsKey(key)) { + foreach (String item in json[key].Keys) { + if (list.ContainsKey(Int32.Parse(item))) { + list[Int32.Parse(item)].GetType().GetMethod("SetUpdate").Invoke(list[Int32.Parse(item)], new Object[] { json[key][item] }); + } + } + } + } + + public static void SetUpdate(ReadOnlyDictionary list, JsonData json, String key) { + if (json.ContainsKey(key)) { + foreach (String item in json[key].Keys) { + if (list.ContainsKey(item)) { + list[item].GetType().GetMethod("SetUpdate").Invoke(list[item], new Object[] { json[key][item] }); + } + } + } + } } public static class StringHelper { public static String ToEnumString(this String text) {