From d5ca3672e95519b8871ee2c5ce4aa2c886c1345f Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Sun, 1 Oct 2017 22:18:03 +0000 Subject: [PATCH] [NF] Umbenennung zu IoTBot Namespace --- IoT-Bot/Connector/ADataBackend.cs | 38 ++++++++++ IoT-Bot/Connector/Mosquitto.cs | 121 ++++++++++++++++++++++++++++++ IoT-Bot/Connector/Mqtt.cs | 65 ++++++++++++++++ IoT-Bot/Connector/Telegram.cs | 54 +++++++++++++ IoT-Bot/Sensor/ASensor.cs | 121 ++++++++++++++++++++++++++++++ IoT-Bot/Sensor/Flex4GridSwitch.cs | 52 +++++++++++++ IoT-Bot/Sensor/Flex4gridPower.cs | 34 +++++++++ IoT-Bot/Sensor/Luminanz.cs | 20 +++++ IoT-Bot/Sensor/Pir.cs | 16 ++++ IoT-Bot/Sensor/Power.cs | 20 +++++ IoT-Bot/Sensor/Switch.cs | 16 ++++ IoT-Bot/Sensor/Temperatur.cs | 20 +++++ 12 files changed, 577 insertions(+) create mode 100644 IoT-Bot/Connector/ADataBackend.cs create mode 100644 IoT-Bot/Connector/Mosquitto.cs create mode 100644 IoT-Bot/Connector/Mqtt.cs create mode 100644 IoT-Bot/Connector/Telegram.cs create mode 100644 IoT-Bot/Sensor/ASensor.cs create mode 100644 IoT-Bot/Sensor/Flex4GridSwitch.cs create mode 100644 IoT-Bot/Sensor/Flex4gridPower.cs create mode 100644 IoT-Bot/Sensor/Luminanz.cs create mode 100644 IoT-Bot/Sensor/Pir.cs create mode 100644 IoT-Bot/Sensor/Power.cs create mode 100644 IoT-Bot/Sensor/Switch.cs create mode 100644 IoT-Bot/Sensor/Temperatur.cs diff --git a/IoT-Bot/Connector/ADataBackend.cs b/IoT-Bot/Connector/ADataBackend.cs new file mode 100644 index 0000000..c3ff494 --- /dev/null +++ b/IoT-Bot/Connector/ADataBackend.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace IoTBot.Connector { + public abstract class ADataBackend { + + public abstract event MqttMessage MessageIncomming; + public abstract event MqttMessage MessageSending; + public delegate void MqttMessage(ADataBackend sender, MqttEventArgs e); + + public static ADataBackend GetInstance(Dictionary dictionary) { + String object_sensor = "IoTBot.Connector." + Char.ToUpper(dictionary["type"][0]) + dictionary["type"].Substring(1).ToLower(); + Type t = null; + try { + t = Type.GetType(object_sensor, true); + } catch (TypeLoadException) { + throw new ArgumentException("settings.ini: " + dictionary["type"] + " is not a Connector"); + } + return (ADataBackend)t.GetConstructor(new Type[] { typeof(Dictionary) }).Invoke(new Object[] { dictionary }); + } + + public abstract void Send(String topic, String data); + + public abstract void Dispose(); + } + public class MqttEventArgs : EventArgs { + public MqttEventArgs() : base() { } + public MqttEventArgs(String message, String topic) { + this.Topic = topic; + this.Message = message; + this.Date = DateTime.Now; + } + + public String Topic { get; private set; } + public String Message { get; private set; } + public DateTime Date { get; private set; } + } +} diff --git a/IoT-Bot/Connector/Mosquitto.cs b/IoT-Bot/Connector/Mosquitto.cs new file mode 100644 index 0000000..e6e79d4 --- /dev/null +++ b/IoT-Bot/Connector/Mosquitto.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; + +namespace IoTBot.Connector { + class Mosquitto : ADataBackend, IDisposable { + private Process p; + private String message; + + public override event MqttMessage MessageIncomming; + public override event MqttMessage MessageSending; + + public Mosquitto(Dictionary mqtt_settings) { + this.settings = mqtt_settings; + //mosquitto_sub --cafile ca.pem --cert cert.pem --key cert.key -h swb.broker.flex4grid.eu -p 8883 -t "#" -v -d + this.message = ""; + this.p = new Process(); + this.p.StartInfo.FileName = "mosquitto_sub"; + String args = "-h " + this.settings["server"]+" "; + if(this.settings.ContainsKey("port")) { + args += "-p "+ this.settings["port"]+" "; + } + if (this.settings.ContainsKey("cafile")) { + args += "--cafile " + this.settings["cafile"] + " "; + } + if (this.settings.ContainsKey("cert")) { + args += "--cert " + this.settings["cert"] + " "; + } + if (this.settings.ContainsKey("key")) { + args += "--key " + this.settings["key"] + " "; + } + this.p.StartInfo.Arguments = args+"-t \"#\" -v -d"; + this.p.StartInfo.CreateNoWindow = true; + this.p.StartInfo.UseShellExecute = false; + this.p.StartInfo.RedirectStandardOutput = true; + this.p.StartInfo.RedirectStandardError = true; + this.p.OutputDataReceived += this.P_OutputDataReceived; + this.p.ErrorDataReceived += this.P_ErrorDataReceived; + this.p.Start(); + this.p.BeginOutputReadLine(); + + } + + public override void Send(String topic, String data) { + Process send = new Process(); + send.StartInfo.FileName = "mosquitto_pub"; + String args = "-h " + this.settings["server"] + " "; + if (this.settings.ContainsKey("port")) { + args += "-p " + this.settings["port"] + " "; + } + if (this.settings.ContainsKey("cafile")) { + args += "--cafile " + this.settings["cafile"] + " "; + } + if (this.settings.ContainsKey("cert")) { + args += "--cert " + this.settings["cert"] + " "; + } + if (this.settings.ContainsKey("key")) { + args += "--key " + this.settings["key"] + " "; + } + send.StartInfo.Arguments = args + "-m \""+data.Replace("\"","\\\"")+"\" -t \""+topic+"\" -d"; + send.StartInfo.CreateNoWindow = true; + send.StartInfo.UseShellExecute = false; + send.StartInfo.RedirectStandardOutput = true; + send.StartInfo.RedirectStandardError = true; + send.Start(); + send.WaitForExit(); + MessageSending?.Invoke(this, new MqttEventArgs(data, topic)); + } + + private void P_ErrorDataReceived(Object sender, DataReceivedEventArgs e) { + if (e.Data != null) { + throw new NotImplementedException(e.Data); + } + } + + private void P_OutputDataReceived(Object sender, DataReceivedEventArgs e) { + if (e.Data != null) { + if (e.Data.StartsWith("Client mosqsub")) { + if (this.message != "" && this.message.IndexOf(" received PUBLISH ") > 0) { + MatchCollection matches = (new Regex("^Client mosqsub[\\|/].*received PUBLISH \\(.*,.*,.*,.*, '(.*)'.*\\)\\)\n[^ ]* (.*)$", RegexOptions.IgnoreCase | RegexOptions.Singleline)).Matches(this.message); + String topic = matches[0].Groups[1].Value; + String message = matches[0].Groups[2].Value.Trim(); + this.MessageIncomming?.Invoke(this, new MqttEventArgs(message, topic)); + } + this.message = e.Data + "\n"; + } else { + this.message += e.Data + "\n"; + } + } + } + + #region IDisposable Support + private Boolean disposedValue = false; // Dient zur Erkennung redundanter Aufrufe. + private readonly Dictionary settings; + + protected virtual void Dispose(Boolean disposing) { + if (!this.disposedValue) { + if (disposing) { + this.p.CancelOutputRead(); + if (!this.p.HasExited) { + this.p.Kill(); + } + this.p.Close(); + } + this.p = null; + this.disposedValue = true; + } + } + + ~Mosquitto() { + Dispose(false); + } + + public override void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/IoT-Bot/Connector/Mqtt.cs b/IoT-Bot/Connector/Mqtt.cs new file mode 100644 index 0000000..46a42c4 --- /dev/null +++ b/IoT-Bot/Connector/Mqtt.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; +using uPLibrary.Networking.M2Mqtt; +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace IoTBot.Connector { + class Mqtt : ADataBackend, IDisposable { + private MqttClient client; + + public override event MqttMessage MessageIncomming; + public override event MqttMessage MessageSending; + + public Mqtt(Dictionary settings) { + if(settings.ContainsKey("port")) { + this.client = new MqttClient(settings["server"], Int32.Parse(settings["port"]), false, null, null, MqttSslProtocols.None); + } else { + this.client = new MqttClient(settings["server"]); + } + Connect(); + } + + private void Connect() { + this.client.MqttMsgPublishReceived += this.Client_MqttMsgPublishReceived; + this.client.Connect(Guid.NewGuid().ToString()); + this.client.Subscribe(new String[] { "#" }, new Byte[] { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE }); + } + + private void Client_MqttMsgPublishReceived(Object sender, MqttMsgPublishEventArgs e) { + this.MessageIncomming?.Invoke(this, new MqttEventArgs(Encoding.UTF8.GetString(e.Message), e.Topic)); + } + + public override void Send(String topic, String data) { + this.client.Publish(topic, Encoding.UTF8.GetBytes(data)); + this.MessageSending?.Invoke(this, new MqttEventArgs(data, topic)); + } + + #region IDisposable Support + private Boolean disposedValue = false; + + + + protected virtual void Dispose(Boolean disposing) { + if(!this.disposedValue) { + if(disposing) { + this.client.MqttMsgPublishReceived -= this.Client_MqttMsgPublishReceived; + this.client.Unsubscribe(new String[] { "#" }); + this.client.Disconnect(); + } + + this.client = null; + + this.disposedValue = true; + } + } + ~Mqtt() { + Dispose(false); + } + public override void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/IoT-Bot/Connector/Telegram.cs b/IoT-Bot/Connector/Telegram.cs new file mode 100644 index 0000000..fa4d3f8 --- /dev/null +++ b/IoT-Bot/Connector/Telegram.cs @@ -0,0 +1,54 @@ +using BlubbFish.Utils; +using System; +using Telegram.Bot; +using Telegram.Bot.Args; +using Telegram.Bot.Exceptions; +using Telegram.Bot.Types; + +namespace IoTBot.Connector { + class Telegram { + private static Telegram instance; + private TelegramBotClient bot; + private ChatId chat; + + public delegate void TelegramMessage(Object sender, Message e); + + public event TelegramMessage MessageIncomming; + public event TelegramMessage MessageSending; + + private Telegram() { + this.bot = new TelegramBotClient(InIReader.GetInstance("settings.ini").GetValue("general", "telegram-key")); + this.bot.OnMessage += this.Bot_OnMessage; + this.Connect(); + } + + private void Bot_OnMessage(Object sender, MessageEventArgs e) { + this.MessageIncomming?.Invoke(this, e.Message); + } + + public static Telegram Instance { + get { + if(instance == null) { + instance = new Telegram(); + } + return instance; + } + } + + private void Connect() { + this.bot.StartReceiving(); + this.chat = new ChatId(InIReader.GetInstance("settings.ini").GetValue("general", "chatid")); + } + + public async void Send(String text) { + try { + Message x = await this.bot.SendTextMessageAsync(this.chat, text); + this.MessageSending?.Invoke(this, x); + } catch(ApiRequestException e) { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e.Message+" "+e.ErrorCode+" "+e.Parameters); + Console.ForegroundColor = ConsoleColor.White; + } + } + } +} diff --git a/IoT-Bot/Sensor/ASensor.cs b/IoT-Bot/Sensor/ASensor.cs new file mode 100644 index 0000000..a85fd47 --- /dev/null +++ b/IoT-Bot/Sensor/ASensor.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + public abstract class ASensor : IDisposable { + protected String topic; + protected Int32 pollcount; + private Dictionary settings; + protected ADataBackend backend; + private Thread updateThread; + private Boolean pollEnabled = false; + + public ASensor(Dictionary settings, String name, ADataBackend backend) { + this.GetBool = true; + this.GetFloat = 0.0f; + this.GetInt = 0; + this.topic = (settings.Keys.Contains("topic")) ? settings["topic"] : ""; + this.settings = settings; + this.Title = (settings.Keys.Contains("title")) ? settings["title"] : ""; + this.Name = name; + this.backend = backend; + this.backend.MessageIncomming += this.IncommingMqttMessage; + if (settings.Keys.Contains("polling")) { + this.pollEnabled = true; + this.Polling = Int32.Parse(settings["polling"]); + this.pollcount = this.Polling; + this.updateThread = new Thread(this.SensorPolling); + this.updateThread.Start(); + } + } + + private void SensorPolling() { + while(this.pollEnabled) { + Thread.Sleep(1000); + this.Poll(); + } + } + + private void IncommingMqttMessage(Object sender, MqttEventArgs e) { + if(Regex.Match(e.Topic, this.topic).Success) { + if (this.UpdateValue(e)) { + this.Timestamp = DateTime.Now; + this.Update?.Invoke(this, e); + } + } + } + + internal static ASensor GetInstance(ADataBackend backend, Dictionary dictionary, String name) { + String object_sensor = "IoTBot.Sensor." + Char.ToUpper(dictionary["type"][0]) + dictionary["type"].Substring(1).ToLower(); + Type t = null; + try { + t = Type.GetType(object_sensor, true); + } catch(TypeLoadException) { + throw new ArgumentException("sensor.ini: " + dictionary["type"] + " is not a Sensor"); + } + return (ASensor)t.GetConstructor(new Type[] { typeof(Dictionary), typeof(String), typeof(ADataBackend) }).Invoke(new Object[] { dictionary, name, backend }); + } + + protected virtual void Poll() { + if(this.pollcount++ >= this.Polling) { + this.pollcount = 1; + this.backend.Send(this.topic + "/status",""); + } + } + + internal virtual void SetBool(Boolean v) { + this.backend.Send(this.topic + "/set", v ? "on" : "off"); + } + + protected abstract Boolean UpdateValue(MqttEventArgs e); + + public Single GetFloat { get; protected set; } + public Boolean GetBool { get; protected set; } + public Int32 GetInt { get; protected set; } + public Types Datatypes { get; protected set; } + public DateTime Timestamp { get; protected set; } + public Int32 Polling { get; private set; } + public String Title { get; protected set; } + public String Name { get; internal set; } + + public enum Types { + Bool, + Int, + Float + } + public delegate void UpdatedValue(ASensor sender, EventArgs e); + public event UpdatedValue Update; + + #region IDisposable Support + private Boolean disposedValue = false; + + + protected virtual void Dispose(Boolean disposing) { + if(!this.disposedValue) { + if(disposing) { + this.pollEnabled = false; + if (this.updateThread != null && this.updateThread.ThreadState == ThreadState.Running) { + this.updateThread.Abort(); + while (this.updateThread.ThreadState != ThreadState.Aborted) { } + } + this.backend.MessageIncomming -= this.IncommingMqttMessage; + } + this.settings = null; + this.updateThread = null; + this.disposedValue = true; + } + } + ~ASensor() { + Dispose(false); + } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} \ No newline at end of file diff --git a/IoT-Bot/Sensor/Flex4GridSwitch.cs b/IoT-Bot/Sensor/Flex4GridSwitch.cs new file mode 100644 index 0000000..df0af82 --- /dev/null +++ b/IoT-Bot/Sensor/Flex4GridSwitch.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using IoTBot.Connector; +using LitJson; + +namespace IoTBot.Sensor { + class Flex4gridswitch : ASensor { + private String id; + + public Flex4gridswitch(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.Datatypes = Types.Bool; + this.id = settings["id"]; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + CultureInfo info = new CultureInfo("de-DE"); + info.NumberFormat.NumberDecimalSeparator = "."; + CultureInfo.DefaultThreadCurrentCulture = info; + CultureInfo.DefaultThreadCurrentUICulture = info; + System.Threading.Thread.CurrentThread.CurrentCulture = info; + System.Threading.Thread.CurrentThread.CurrentUICulture = info; + try { + JsonData data = JsonMapper.ToObject(e.Message); + if (data.Keys.Contains("id") && data["id"].ToString() == this.id) { + this.GetBool = data["status"].ToString() == "active" ? true : false; + return true; + } + if(data.Keys.Contains(this.id)) { + this.GetBool = data[this.id].ToString() == "active" ? true : false; + return true; + } + } catch (Exception) { + } + return false; + } + + protected override void Poll() { + if (this.pollcount++ >= this.Polling) { + this.pollcount = 1; + String hid = Regex.Match(this.topic, "/flex4grid/v1/households/([^/]*)/device/state").Groups[1].Value; + this.backend.Send("/flex4grid/v1/households/" + hid + "/devices/state/request", "{\"timestamp\": \"" + DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff'Z'") + "\"}"); + } + } + + internal override void SetBool(Boolean v) { + String hid = Regex.Match(this.topic, "/flex4grid/v1/households/([^/]*)/device/state").Groups[1].Value; + this.backend.Send("/flex4grid/v1/households/" + hid + "/device/actuate", "{\"command\":\""+(v ? "ON" : "OFF") + "\",\"deviceId\":\""+ this.id + "\",\"timestamp\":\"" + DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff'Z'") + "\"}"); + } + } +} \ No newline at end of file diff --git a/IoT-Bot/Sensor/Flex4gridPower.cs b/IoT-Bot/Sensor/Flex4gridPower.cs new file mode 100644 index 0000000..60f84bf --- /dev/null +++ b/IoT-Bot/Sensor/Flex4gridPower.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using IoTBot.Connector; +using LitJson; + +namespace IoTBot.Sensor { + class Flex4gridpower : ASensor { + private String id; + + public Flex4gridpower(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.Datatypes = Types.Float; + this.id = settings["id"]; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + CultureInfo info = new CultureInfo("de-DE"); + info.NumberFormat.NumberDecimalSeparator = "."; + CultureInfo.DefaultThreadCurrentCulture = info; + CultureInfo.DefaultThreadCurrentUICulture = info; + System.Threading.Thread.CurrentThread.CurrentCulture = info; + System.Threading.Thread.CurrentThread.CurrentUICulture = info; + try { + JsonData data = JsonMapper.ToObject(e.Message); + if(data["id"].ToString() == this.id) { + this.GetFloat = Single.Parse(data["power"].ToString()); + return true; + } + } catch (Exception) { + } + return false; + } + } +} diff --git a/IoT-Bot/Sensor/Luminanz.cs b/IoT-Bot/Sensor/Luminanz.cs new file mode 100644 index 0000000..19c1c51 --- /dev/null +++ b/IoT-Bot/Sensor/Luminanz.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + class Luminanz : ASensor { + public Luminanz(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.GetBool = true; + this.GetFloat = 0.0f; + this.GetInt = 0; + this.Datatypes = Types.Int; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + this.GetInt = Int32.Parse(e.Message, new CultureInfo("en-US")); + return true; + } + } +} diff --git a/IoT-Bot/Sensor/Pir.cs b/IoT-Bot/Sensor/Pir.cs new file mode 100644 index 0000000..37306b5 --- /dev/null +++ b/IoT-Bot/Sensor/Pir.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + class Pir : ASensor { + public Pir(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.Datatypes = Types.Bool; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + this.GetBool = (e.Message.ToLower() == "on") ? true : false; + return true; + } + } +} \ No newline at end of file diff --git a/IoT-Bot/Sensor/Power.cs b/IoT-Bot/Sensor/Power.cs new file mode 100644 index 0000000..e09ebc6 --- /dev/null +++ b/IoT-Bot/Sensor/Power.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + class Power : ASensor { + public Power(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.GetBool = true; + this.GetFloat = 0.0f; + this.GetInt = 0; + this.Datatypes = Types.Float; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + this.GetFloat = Single.Parse(e.Message, new CultureInfo("en-US")); + return true; + } + } +} diff --git a/IoT-Bot/Sensor/Switch.cs b/IoT-Bot/Sensor/Switch.cs new file mode 100644 index 0000000..3e1c3c8 --- /dev/null +++ b/IoT-Bot/Sensor/Switch.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + class Switch : ASensor { + public Switch(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.Datatypes = Types.Bool; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + this.GetBool = (e.Message.ToLower() == "on") ? true : false; + return true; + } + } +} \ No newline at end of file diff --git a/IoT-Bot/Sensor/Temperatur.cs b/IoT-Bot/Sensor/Temperatur.cs new file mode 100644 index 0000000..c469ec4 --- /dev/null +++ b/IoT-Bot/Sensor/Temperatur.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using IoTBot.Connector; + +namespace IoTBot.Sensor { + class Temperatur : ASensor { + public Temperatur(Dictionary settings, String name, ADataBackend backend) : base(settings, name, backend) { + this.GetBool = true; + this.GetFloat = 0.0f; + this.GetInt = 0; + this.Datatypes = Types.Float; + } + + protected override Boolean UpdateValue(MqttEventArgs e) { + this.GetFloat = Single.Parse(e.Message, new CultureInfo("en-US")); + return true; + } + } +} \ No newline at end of file