commit 62c3c5815c795d4e54778e1f98d199cf49dc41b4 Author: BlubbFish Date: Fri Apr 20 19:50:28 2018 +0000 [WP] Hue library added diff --git a/Hue.sln b/Hue.sln new file mode 100644 index 0000000..a182e5d --- /dev/null +++ b/Hue.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hue", "Hue\Hue.csproj", "{738A6137-8FCA-4CE1-BD1C-042B21B6B5F2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {738A6137-8FCA-4CE1-BD1C-042B21B6B5F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {738A6137-8FCA-4CE1-BD1C-042B21B6B5F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {738A6137-8FCA-4CE1-BD1C-042B21B6B5F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {738A6137-8FCA-4CE1-BD1C-042B21B6B5F2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8F059288-842C-4EA3-AFD4-9AE2E17BD1B3} + EndGlobalSection +EndGlobal diff --git a/Hue/Devices/Groups/Room.cs b/Hue/Devices/Groups/Room.cs new file mode 100644 index 0000000..40249e6 --- /dev/null +++ b/Hue/Devices/Groups/Room.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +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 + } + private Boolean _state; + public Boolean State { + get { + return this._state; + } + set { + this.SetGroupAction(new Dictionary() { { "on", value } }); + } + } + private Byte _brightness; + public Byte Brightness { + get { + return this._brightness; + } + set { + this.SetGroupAction(new Dictionary() { { "bri", value } }); + } + } + public AlertEffect Alert { + get { + return this._alert; + } + set { + this.SetGroupAction(new Dictionary() { { "alert", value } }); + } + } + #endregion + + #region Constructor + public Room(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(json, id, http, polling, lights) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("action")) { + JsonData state = json["action"]; + if (state.ContainsKey("on") && state["on"].IsBoolean) { + this._state = (Boolean)state["on"]; + } + if (state.ContainsKey("bri") && state["bri"].IsInt) { + this._brightness = (Byte)state["bri"]; + } + if (state.ContainsKey("alert") && state["alert"].IsString) { + this._alert = (AlertEffect)Enum.Parse(typeof(AlertEffect), state["alert"].ToString()); + } + } + } + #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) { + lid.Add(item.Key.ToString()); + } + 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(); + } + #endregion + } +} diff --git a/Hue/Devices/Lights/Dimmablelight.cs b/Hue/Devices/Lights/Dimmablelight.cs new file mode 100644 index 0000000..5d823a0 --- /dev/null +++ b/Hue/Devices/Lights/Dimmablelight.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +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, + lselect + } + + #region Properties + private Boolean _state; + public Boolean State { + get { + return this._state; + } + set { + this.SetLightState(new Dictionary() { { "on", value } }); + } + } + private Byte _brightness; + public Byte Brightness { + get { + return this._brightness; + } + set { + this.SetLightState(new Dictionary() { { "bri", value } }); + } + } + private AlertEffect _alert; + public AlertEffect Alert { + get { + return this._alert; + } + set { + this.SetLightState(new Dictionary() { { "alert", value } }); + } + } + public Boolean Reachable { get; private set; } + #endregion + + #region Constructor + public Dimmablelight(JsonData json, Tuple id, HttpConnection http, Boolean polling) : base(json, id, http, polling) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("state")) { + JsonData state = json["state"]; + if (state.ContainsKey("on") && state["on"].IsBoolean) { + this._state = (Boolean)state["on"]; + } + if (state.ContainsKey("bri") && state["bri"].IsInt) { + this._brightness = (Byte)state["bri"]; + } + if (state.ContainsKey("alert") && state["alert"].IsString) { + this._alert = (AlertEffect)Enum.Parse(typeof(AlertEffect), state["alert"].ToString()); + } + if (state.ContainsKey("reachable") && state["reachable"].IsBoolean) { + this.Reachable = (Boolean)state["reachable"]; + } + } + } + #endregion + + #region AConnector + 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 } + }; + } + #endregion + } +} diff --git a/Hue/Devices/Scenes/LightState.cs b/Hue/Devices/Scenes/LightState.cs new file mode 100644 index 0000000..c168d74 --- /dev/null +++ b/Hue/Devices/Scenes/LightState.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +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; + + #region Properties + private Boolean _state; + public Boolean State { + get { + return this._state; + } + set { + this.SetLightScene(new Dictionary() { { "on", value } }); + } + } + private Byte _brightness; + public Byte Brightness { + get { + return this._brightness; + } + set { + this.SetLightScene(new Dictionary() { { "bri", 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; + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("on") && json["on"].IsBoolean) { + this._state = (Boolean)json["on"]; + } + if (json.ContainsKey("bri") && json["bri"].IsInt) { + this._brightness = (Byte)json["bri"]; + } + } + #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; + } + #endregion + } +} diff --git a/Hue/Devices/Scenes/Scene.cs b/Hue/Devices/Scenes/Scene.cs new file mode 100644 index 0000000..8006242 --- /dev/null +++ b/Hue/Devices/Scenes/Scene.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +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 + 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; } + #endregion + + #region Constructor + public Scene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) : base(json, id, http, polling, lights) { + this.ComplexInit(json); + } + + private void ComplexInit(JsonData json) { + if(json.ContainsKey("owner") && json["owner"].IsString) { + this.Owner = json["owner"].ToString(); + } + if (json.ContainsKey("recycle") && json["recycle"].IsBoolean) { + this.Recycle = (Boolean)json["recycle"]; + } + if (json.ContainsKey("locked") && json["locked"].IsBoolean) { + this.Locked = (Boolean)json["locked"]; + } + if (json.ContainsKey("picture") && json["picture"].IsString) { + this.Picture = json["picture"].ToString(); + } + if (json.ContainsKey("lastupdated") && json["lastupdated"].IsString) { + this.Lastupdated = DateTime.Parse(json["lastupdated"].ToString()); + } + if (json.ContainsKey("version") && json["version"].IsInt) { + this.Version = (Int32)json["version"]; + } + } + #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) { + lid.Add(item.Key.ToString()); + } + List lst = new List(); + foreach (KeyValuePair item in this.LightStates) { + lst.Add(item.Value.ToString()); + } + return "Scene " + this.Name + " [" + this.SceneId + "]: (["+ String.Join(",", lid.ToArray()) +"]) Light-States ["+ String.Join(",", lst.ToArray())+"]"; + } + #endregion + } +} diff --git a/Hue/Events/UpdateEvent.cs b/Hue/Events/UpdateEvent.cs new file mode 100644 index 0000000..00432d4 --- /dev/null +++ b/Hue/Events/UpdateEvent.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlubbFish.IoT.Hue.Events { + public class AllUpdateEvent : EventArgs { + + public AllUpdateEvent() { + } + + public AllUpdateEvent(Object value, DateTime time, Object parent) { + this.GetValue = value; + this.UpdateTime = time; + this.Parent = parent; + } + + public Object GetValue { get; } + public DateTime UpdateTime { get; } + public Object Parent { get; private set; } + } + public class LightUpdateEvent : AllUpdateEvent { + public LightUpdateEvent() { + } + + public LightUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { + } + } + public class GroupUpdateEvent : AllUpdateEvent { + public GroupUpdateEvent() { + } + + public GroupUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { + } + } + + public class SceneUpdateEvent : AllUpdateEvent { + public SceneUpdateEvent() { + } + + public SceneUpdateEvent(Object value, DateTime time, Object parent) : base(value, time, parent) { + } + } +} diff --git a/Hue/Exceptions/HueExceptions.cs b/Hue/Exceptions/HueExceptions.cs new file mode 100644 index 0000000..cee353b --- /dev/null +++ b/Hue/Exceptions/HueExceptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.Serialization; + +namespace BlubbFish.IoT.Hue.Exceptions { + [Serializable] + public class ConnectionException : Exception { + public ConnectionException() { } + public ConnectionException(String message) : base(message) { } + public ConnectionException(String message, Exception innerException) : base(message, innerException) { } + protected ConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Hue/Hue.csproj b/Hue/Hue.csproj new file mode 100644 index 0000000..4fecf08 --- /dev/null +++ b/Hue/Hue.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {738A6137-8FCA-4CE1-BD1C-042B21B6B5F2} + Library + Properties + BlubbFish.IoT.Hue + Hue + v4.7.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {91a14cd2-2940-4500-8193-56d37edddbaa} + litjson_4.7.1 + + + + + \ No newline at end of file diff --git a/Hue/HueController.cs b/Hue/HueController.cs new file mode 100644 index 0000000..995d448 --- /dev/null +++ b/Hue/HueController.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using BlubbFish.IoT.Hue.Events; +using BlubbFish.IoT.Hue.Exceptions; +using BlubbFish.IoT.Hue.Interfaces; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue { + public class HueController : IDisposable { + private Boolean polling; + private HttpConnection http; + private Thread updatethread; + private Thread pollthread; + + public delegate void DataUpdate(Object sender, AllUpdateEvent e); + public event DataUpdate Update; + + public ReadOnlyDictionary Lights { get; private set; } + public ReadOnlyDictionary Groups { get; private set; } + public ReadOnlyDictionary Scenes { get; private set; } + + public HueController(Dictionary settings, Boolean enablePoll = true) { + this.polling = enablePoll; + if(!settings.ContainsKey("server")) { + throw new ArgumentException("Missing \"server\" in Configurationfile"); + } + if(!settings.ContainsKey("key")) { + this.CreateKey(settings["server"]); + return; + } + this.Connect(settings["server"], settings["key"]); + } + + private void Connect(String server, String key) { + this.http = new HttpConnection(server, key); + this.CreateAll(this.http.GetJson("")); + this.updatethread = new Thread(this.Updater); + this.updatethread.Start(); + this.pollthread = new Thread(this.Poll); + this.pollthread.Start(); + foreach (KeyValuePair item in this.Lights) { + item.Value.Update += this.AllUpdate; + } + foreach (KeyValuePair item in this.Groups) { + item.Value.Update += this.AllUpdate; + } + foreach (KeyValuePair item in this.Scenes) { + item.Value.Update += this.AllUpdate; + } + /*foreach (KeyValuePair item in this.Sensors) { + item.Value.Update += this.AllUpdate; + } + this.Config.Update += this.AllUpdate;*/ + } + + private void Poll() { + //throw new NotImplementedException(); + } + + private void AllUpdate(Object sender, AllUpdateEvent e) { + this.Update?.Invoke(sender, e); + } + + private void Updater() { + //throw new NotImplementedException(); + } + + private void CreateAll(JsonData json) { + if(json.ContainsKey("lights")) { + this.CreateLights(json["lights"]); + } + if(json.ContainsKey("groups")) { + this.CreateGroups(json["groups"]); + } + if(json.ContainsKey("scenes")) { + this.CreateScenes(json["scenes"]); + } + } + + 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); + if (s != null) { + scenes.Add(sceneid, s); + } + } + 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\"}"); + if(json.IsArray && json[0].ContainsKey("error") && json[0]["error"].ContainsKey("type") && json[0]["error"]["type"].IsInt) { + if((Int32)json[0]["error"]["type"] == 101) { + Helper.WriteError("Please press the linkbutton on the Hue-Bridge! Then press return!"); + } + } + Console.ReadLine(); + json = this.http.PostJson("", "{\"devicetype\": \"Huelib#BlubbFish\"}"); + String key = ""; + if(json.IsArray && json[0].ContainsKey("success") && json[0]["success"].ContainsKey("username") && json[0]["success"]["username"].IsString) { + key = json[0]["success"]["username"].ToString(); + } + Helper.WriteError("Create \"key\" in config file! key=" + key); + throw new ConnectionException("Create \"key\" in config file! key=" + key); + } + + + + + #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.Lights = null; + this.Groups = null; + this.Scenes = null; + /*this.Sensors = null; + this.Config = null;*/ + this.disposedValue = true; + } + } + + public void Dispose() { + Dispose(true); + } + #endregion + } +} diff --git a/Hue/Interfaces/AConnector.cs b/Hue/Interfaces/AConnector.cs new file mode 100644 index 0000000..28bcef5 --- /dev/null +++ b/Hue/Interfaces/AConnector.cs @@ -0,0 +1,20 @@ +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 AConnector { + protected HttpConnection http; + + protected void PutDictionary(String address, Dictionary value) { + this.http.PutJson(address, JsonMapper.ToJson(value)); + } + + public delegate void UpdatedValue(Object sender, AllUpdateEvent e); + public abstract override String ToString(); + public abstract Dictionary ToDictionary(); + public abstract event UpdatedValue Update; + } +} diff --git a/Hue/Interfaces/AGroup.cs b/Hue/Interfaces/AGroup.cs new file mode 100644 index 0000000..f3731e2 --- /dev/null +++ b/Hue/Interfaces/AGroup.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using BlubbFish.IoT.Hue.lib; +using LitJson; + +namespace BlubbFish.IoT.Hue.Interfaces { + public abstract class AGroup : AConnector, IMqtt { + protected String _name; + protected GroupClass _class; + + public enum GroupClass { + [Description("Living room")] + Livingroom, + Kitchen, + Dining, + Bedroom, + [Description("Kids bedroom")] + Kidsbedroom, + Bathroom, + Nursery, + Recreation, + Office, + Gym, + Hallway, + Toilet, + [Description("Front door")] + Frontdoor, + Garage, + Terrace, + Garden, + Driveway, + Carport, + Other + } + public enum Types { + Room + } + public enum TypesIgnore { + Luminaire, + Lightsource, + LightGroup, + Entertainment + } + + #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 } }); + } + } + 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 GroupClass Class { + get { + return this._class; + } + set { + this._class = value; + this.SetGroupAttribute(new Dictionary() { { "class", Helper.GetEnumDescription(value) } }); + } + } + #endregion + + #region Constructor + protected AGroup(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + 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) { + 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.Groups." + type; + return GetInstanceConcrete(name, json, new Tuple(id.Item1, (Types)Enum.Parse(typeof(Types), type)), http, polling, lights); + } + if (!Enum.IsDefined(typeof(TypesIgnore), type) && !Enum.IsDefined(typeof(Types), type)) { + Helper.WriteError("Lightclass " + type + " not exist."); + } + return null; + } + + private static AGroup 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 (AGroup)t.GetConstructor(new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean), typeof(ReadOnlyDictionary) }).Invoke(new Object[] { json, 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("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")) { + this._class = (GroupClass)Enum.Parse(typeof(GroupClass), json["class"].ToString().ToEnumString()); + } + } + + protected void SetGroupAttribute(Dictionary value) { + this.PutDictionary("groups/" + this.GroupId, value); + } + + protected void SetGroupAction(Dictionary value) { + this.PutDictionary("groups/" + this.GroupId + "/action", value); + } + + #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); + } + + public String MqttTopic() { + return "groups/" + this.GroupId + "/" + this.Groupclass.ToString(); + } + #endregion + } +} diff --git a/Hue/Interfaces/ALight.cs b/Hue/Interfaces/ALight.cs new file mode 100644 index 0000000..6c042bf --- /dev/null +++ b/Hue/Interfaces/ALight.cs @@ -0,0 +1,87 @@ +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 ALight : AConnector, IMqtt { + public enum Types { + Dimmablelight + } + public enum TypesIgnore { + Colorlight, + Extendedcolorlight + } + + #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; } + #endregion + + #region Constructor + protected ALight(JsonData json, Tuple id, HttpConnection http, Boolean polling) { + this.LightId = id.Item1; + this.Lightclass = id.Item2; + this.http = http; + this.LastUpdate = DateTime.Now; + this.Polling = polling; + this.ComplexInit(json); + } + + internal static ALight CreateLight(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.Lights." + 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("Lightclass " + type + " not exist."); + } + return null; + } + + private static ALight 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 (ALight)t.GetConstructor(new Type[] { typeof(JsonData), typeof(Tuple), typeof(HttpConnection), typeof(Boolean) }).Invoke(new Object[] { json, id, http, polling }); + } + #endregion + + private void ComplexInit(JsonData json) { + if (json.ContainsKey("name")) { + this.Name = json["name"].ToString(); + } + } + + protected void SetLightState(Dictionary value) { + this.PutDictionary("lights/" + this.LightId + "/state", value); + } + + #region IMqtt + public String MqttTopic() { + return "lights/" + this.LightId + "/" + this.Lightclass.ToString(); + } + + 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); + } + #endregion + } +} diff --git a/Hue/Interfaces/AScene.cs b/Hue/Interfaces/AScene.cs new file mode 100644 index 0000000..3fbe906 --- /dev/null +++ b/Hue/Interfaces/AScene.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using BlubbFish.IoT.Hue.Devices.Scenes; +using BlubbFish.IoT.Hue.lib; +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 } }); + } + } + #endregion + + #region Constructor + protected AScene(JsonData json, Tuple id, HttpConnection http, Boolean polling, ReadOnlyDictionary lights) { + this.SceneId = id.Item1; + this.http = http; + this.LastUpdate = DateTime.Now; + this.Polling = polling; + this.ComplexInit(json, lights); + } + + 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 }); + } + #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 void SetSceneAttribute(Dictionary dictionary) { + this.PutDictionary("scenes/" + this.SceneId, dictionary); + } + + #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); + } + + public String MqttTopic() { + return "groups/" + this.SceneId; + } + #endregion + } +} diff --git a/Hue/Interfaces/IMqtt.cs b/Hue/Interfaces/IMqtt.cs new file mode 100644 index 0000000..fc96a62 --- /dev/null +++ b/Hue/Interfaces/IMqtt.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlubbFish.IoT.Hue.Interfaces { + interface IMqtt { + String ToJson(); + String MqttTopic(); + } +} diff --git a/Hue/Properties/AssemblyInfo.cs b/Hue/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..80c9cd1 --- /dev/null +++ b/Hue/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Hue")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Hue")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("738a6137-8fca-4ce1-bd1c-042b21b6b5f2")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Hue/lib/Helper.cs b/Hue/lib/Helper.cs new file mode 100644 index 0000000..e0724f9 --- /dev/null +++ b/Hue/lib/Helper.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace BlubbFish.IoT.Hue.lib { + class Helper { + internal static void WriteError(String text) { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("ERROR: " + text); + Console.ResetColor(); + } + public static String GetEnumDescription(Enum value) { + FieldInfo fi = value.GetType().GetField(value.ToString()); + + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + + if (attributes != null && attributes.Length > 0) { + return attributes[0].Description; + } else { + return value.ToString(); + } + } + } + public static class StringHelper { + public static String ToEnumString(this String text) { + if(text.Length == 0) { + return text; + } + if(text.Length == 1) { + return text.ToUpper(); + } + text = text.Replace(" ", ""); + text = text.ToLower(); + return text[0].ToString().ToUpper() + text.Substring(1); + } + } +} diff --git a/Hue/lib/HttpClient.cs b/Hue/lib/HttpClient.cs new file mode 100644 index 0000000..7c0c54d --- /dev/null +++ b/Hue/lib/HttpClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using LitJson; + +namespace BlubbFish.IoT.Hue.lib { + public class HttpConnection { + private String server; + private Object getLock = new Object(); + private enum RequestMethod { + GET, + POST, + PUT + } + + public HttpConnection(String server) { + this.server = "http://" + server + "/api/"; + this.Init(false); + } + + public HttpConnection(String server, String key) { + this.server = "http://" + server + "/api/" + key + "/"; + this.Init(true); + } + + private void Init(Boolean withuser = true) { + this.RequestString("config", "", false); + if(withuser) { + String i = this.RequestString("", "", false); + } + } + + + + internal JsonData PostJson(String address, String json) { + return this.ToJson(this.RequestString(address, json, true, RequestMethod.POST)); + } + + internal JsonData PutJson(String address, String json) { + return this.ToJson(this.RequestString(address, json, true, RequestMethod.PUT)); + } + + internal JsonData GetJson(String address) { + return this.ToJson(this.RequestString(address)); + } + + private JsonData ToJson(String text) { + if (text == null) { + return new JsonData(); + } + try { + return JsonMapper.ToObject(text); + } catch (Exception) { + return new JsonData(); + } + } + + private String RequestString(String address, String json = "", Boolean withoutput = true, RequestMethod method = RequestMethod.GET) { + String ret = null; + lock (this.getLock) { + HttpWebRequest request = WebRequest.CreateHttp(this.server + address); + request.Timeout = 5000; + if(method == RequestMethod.POST || method == RequestMethod.PUT) { + Byte[] requestdata = Encoding.ASCII.GetBytes(json); + request.ContentLength = requestdata.Length; + request.Method = method.ToString(); + request.ContentType = "application/x-www-form-urlencoded"; + using (Stream stream = request.GetRequestStream()) { + stream.Write(requestdata, 0, requestdata.Length); + } + } + try { + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { + if (response.StatusCode == HttpStatusCode.Unauthorized) { + Console.Error.WriteLine("Benutzer oder Passwort falsch!"); + throw new Exceptions.ConnectionException("Benutzer oder Passwort falsch!"); + } + if (withoutput) { + StreamReader reader = new StreamReader(response.GetResponseStream()); + ret = reader.ReadToEnd(); + } + } + } catch (Exception e) { + Helper.WriteError("Konnte keine Verbindung zum Razzbery Server herstellen. Resource: \"" + this.server + address + "\" Fehler: " + e.Message); + return null; + //throw new Exceptions.ConnectionException("Konnte keine Verbindung zum Razzbery Server herstellen: " + e.Message); + } + } + return ret; + } + } +}