From 477e20c474600ee08125db3cb5b193708283bb7d Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Tue, 26 Sep 2017 21:44:41 +0200 Subject: [PATCH] =?UTF-8?q?[NF]=20Aufger=C3=A4umt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../Connector/ADataBackend.cs | 38 ++++++ .../Mqtt-SWB-Dashboard/Connector/Mosquitto.cs | 121 ++++++++++++++++++ .../Mqtt-SWB-Dashboard/Connector/Mqtt.cs | 66 ++++++++++ .../Mqtt-SWB-Dashboard/Connector/Telegram.cs | 54 ++++++++ .../Mqtt-SWB-Dashboard/Helper/Household.cs | 6 +- .../Mqtt-SWB-Dashboard/MainWindow.xaml.cs | 16 ++- .../Models/PowerChartModel.cs | 29 ++--- .../Mqtt-SWB-Dashboard/Mosquitto.cs | 88 ------------- .../Mqtt-SWB-Dashboard.csproj | 4 +- .../Mqtt-SWB-Dashboard/Stats.cs | 45 +++++-- .../bin/Release/Mqtt-SWB-Dashboard.exe | Bin 33280 -> 34816 bytes .../Mqtt-SWB-Dashboard/bin/Release/Utils.dll | Bin 24576 -> 24576 bytes .../Mqtt-SWB-Dashboard/settings.ini.example | 8 ++ .../MqttToTelegram/Condition/ACondition.cs | 32 +++++ .../Condition/ConditionWorker.cs | 35 +++++ .../MqttToTelegram/Condition/Edge.cs | 26 ++++ .../MqttToTelegram/Connector/ADataBackend.cs | 38 ++++++ .../MqttToTelegram/Connector/Mosquitto.cs | 121 ++++++++++++++++++ .../MqttToTelegram/Connector/Mqtt.cs | 66 ++++++++++ .../MqttToTelegram/Connector/Telegram.cs | 54 ++++++++ MqttToTelegram/MqttToTelegram/Mqtt.cs | 37 ------ .../MqttToTelegram/MqttToTelegram.csproj | 21 ++- MqttToTelegram/MqttToTelegram/Program.cs | 32 ++++- .../MqttToTelegram/Sensor/ASensor.cs | 121 ++++++++++++++++++ .../MqttToTelegram/Sensor/Flex4GridSwitch.cs | 52 ++++++++ .../MqttToTelegram/Sensor/Flex4gridPower.cs | 34 +++++ .../MqttToTelegram/Sensor/Luminanz.cs | 20 +++ MqttToTelegram/MqttToTelegram/Sensor/Pir.cs | 16 +++ MqttToTelegram/MqttToTelegram/Sensor/Power.cs | 20 +++ .../MqttToTelegram/Sensor/Switch.cs | 16 +++ .../MqttToTelegram/Sensor/Temperatur.cs | 20 +++ MqttToTelegram/MqttToTelegram/Telegram.cs | 35 ----- .../MqttToTelegram/settings.ini.example | 4 + Utils/Utils/bin/Release/Utils.dll | Bin 24576 -> 24576 bytes 35 files changed, 1070 insertions(+), 208 deletions(-) create mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/ADataBackend.cs create mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mosquitto.cs create mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mqtt.cs create mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Telegram.cs delete mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mosquitto.cs create mode 100644 Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/settings.ini.example create mode 100644 MqttToTelegram/MqttToTelegram/Condition/ACondition.cs create mode 100644 MqttToTelegram/MqttToTelegram/Condition/ConditionWorker.cs create mode 100644 MqttToTelegram/MqttToTelegram/Condition/Edge.cs create mode 100644 MqttToTelegram/MqttToTelegram/Connector/ADataBackend.cs create mode 100644 MqttToTelegram/MqttToTelegram/Connector/Mosquitto.cs create mode 100644 MqttToTelegram/MqttToTelegram/Connector/Mqtt.cs create mode 100644 MqttToTelegram/MqttToTelegram/Connector/Telegram.cs delete mode 100644 MqttToTelegram/MqttToTelegram/Mqtt.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/ASensor.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Flex4GridSwitch.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Flex4gridPower.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Luminanz.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Pir.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Power.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Switch.cs create mode 100644 MqttToTelegram/MqttToTelegram/Sensor/Temperatur.cs delete mode 100644 MqttToTelegram/MqttToTelegram/Telegram.cs create mode 100644 MqttToTelegram/MqttToTelegram/settings.ini.example diff --git a/.gitignore b/.gitignore index e2c0f04..2070ce8 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,6 @@ /TimeKeeper/Arduino/Zeit/Zeit-V2/Debug /TimeKeeper/Arduino/Zeit/Zeit-V2/__vm /TimeKeeper/lib/System.Data.SQLite.xml +/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/.svn +/MqttToTelegram/MqttToTelegram/Connector/.svn +/MqttToTelegram/MqttToTelegram/Sensor/.svn diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/ADataBackend.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/ADataBackend.cs new file mode 100644 index 0000000..33a0301 --- /dev/null +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/ADataBackend.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace Dashboard.Connector { + public abstract class ADataBackend { + + public abstract event MqttMessage MessageIncomming; + public abstract event MqttMessage MessageSending; + public delegate void MqttMessage(Object sender, MqttEventArgs e); + + public static ADataBackend GetInstance(Dictionary dictionary) { + String object_sensor = "Dashboard.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/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mosquitto.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mosquitto.cs new file mode 100644 index 0000000..cf88022 --- /dev/null +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mosquitto.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; + +namespace Dashboard.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/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mqtt.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mqtt.cs new file mode 100644 index 0000000..36d21a4 --- /dev/null +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Mqtt.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BlubbFish.Utils; +using uPLibrary.Networking.M2Mqtt; +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace Dashboard.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/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Telegram.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Connector/Telegram.cs new file mode 100644 index 0000000..4ec1ded --- /dev/null +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/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 MqttToTelegram { + 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() { + bot = new TelegramBotClient(InIReader.GetInstance("settings.ini").GetValue("general", "telegram-key")); + bot.OnMessage += 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() { + 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/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Helper/Household.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Helper/Household.cs index 47e301d..a328130 100644 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Helper/Household.cs +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Helper/Household.cs @@ -48,7 +48,7 @@ namespace Mqtt_SWB_Dashboard.Helper { internal Int32 GetActive() { Int32 ret = 0; foreach (KeyValuePair item in this.Devices) { - if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-10)) { + if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-15)) { ret++; } } @@ -58,7 +58,7 @@ namespace Mqtt_SWB_Dashboard.Helper { internal Double GetPower() { Double ret = 0; foreach (KeyValuePair item in this.Devices) { - if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-10) && item.Value.Type != Device.DevType.Production) { + if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-15) && item.Value.Type != Device.DevType.Production) { ret += item.Value.Power; } } @@ -78,7 +78,7 @@ namespace Mqtt_SWB_Dashboard.Helper { internal Double GetColum() { Double ret = 0; foreach (KeyValuePair item in this.Devices) { - if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-10) && item.Value.Type != Device.DevType.Production) { + if (item.Value.TimeStamp > DateTime.Now.AddMinutes(-15) && item.Value.Type != Device.DevType.Production) { ret += item.Value.Comul; } } diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/MainWindow.xaml.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/MainWindow.xaml.cs index 19c04f9..0eaa4be 100644 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/MainWindow.xaml.cs +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Windows; +using Dashboard.Connector; namespace Mqtt_SWB_Dashboard { /// @@ -21,11 +22,11 @@ namespace Mqtt_SWB_Dashboard { LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); try { - String broker = InIReader.GetInstance("settings.ini").GetValue("general", "broker"); + String broker = InIReader.GetInstance("settings.ini").GetValue("mqtt", "server"); this.Dispatcher.BeginInvoke((Action)(() => { this.connectedTo.Text = "Connected to: " + broker; })); - this.s = new Stats(new Mosquitto(broker)); + this.s = new Stats(ADataBackend.GetInstance(InIReader.GetInstance("settings.ini").GetSection("mqtt"))); this.s.UpdatedConsumption += this.S_UpdatedConsumption; this.S_UpdatedConsumption(this.s, null); } catch(Exception e) { @@ -36,16 +37,17 @@ namespace Mqtt_SWB_Dashboard { private void S_UpdatedConsumption(Stats sender, EventArgs e) { this.Dispatcher.BeginInvoke((Action)(() => { - this.countHouses.Text = sender.GetNumberHouseholds(); - this.countDevices.Text = sender.GetNumberDevices(); this.countRaspis.Text = sender.GetNumberRaspis(); this.maxRaspi.Text = sender.GetMostRaspiUptime(); this.avgUptime.Text = sender.GetAvgRaspiUptime(); + this.countColum.Text = sender.GetCurrentColum(); + Tuple devices = sender.GetNumberDevices(); + this.countDevices.Text = devices.Item1 + " / " + devices.Item2; + Tuple houses = sender.GetNumberHouseholds(); + this.countHouses.Text = houses.Item1 + " / " + houses.Item2 + " / " + houses.Item3; Tuple power = sender.GetCurrentPower(); this.countPower.Text = power.Item1 + "W / " + power.Item2 + "W"; - Tuple colum = sender.GetCurrentColum(); - this.countColum.Text = colum.Item1.ToString("F1") + "kW / " + colum.Item2.ToString("F1") + "kW"; - ((Models.PowerChartModel)this.DataContext).AddPower(power, colum); + ((Models.PowerChartModel)this.DataContext).AddPower(power, houses, devices); })); } diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Models/PowerChartModel.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Models/PowerChartModel.cs index 0621698..c00e095 100644 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Models/PowerChartModel.cs +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Models/PowerChartModel.cs @@ -12,7 +12,7 @@ using OxyPlot.Series; namespace Mqtt_SWB_Dashboard.Models { public class PowerChartModel : INotifyPropertyChanged { private Double MaximumDrawnL = 5; - private Double MaximumDrawnR = 5; + private Int32 MaximumDrawnR = 5; public event PropertyChangedEventHandler PropertyChanged; @@ -23,33 +23,28 @@ namespace Mqtt_SWB_Dashboard.Models { this.Model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, AbsoluteMinimum = 0, Minimum = 0, AbsoluteMaximum = 1, IsZoomEnabled = false }); this.Model.Axes.Add(new LinearAxis { Position = AxisPosition.Right, AbsoluteMinimum = 0, Minimum = 0, AbsoluteMaximum = 1, IsZoomEnabled = false, Key = "Right" }); - this.Model.Series.Add(new LineSeries { Title = "Active [W]", Color = OxyColor.FromRgb(0, 150, 0) }); - this.Model.Series.Add(new LineSeries { Title = "Seen [W]", Color = OxyColor.FromRgb(0, 255, 0) }); - this.Model.Series.Add(new LineSeries { Title = "Active [kW]", Color = OxyColor.FromRgb(0, 0, 150), YAxisKey= "Right" }); - this.Model.Series.Add(new LineSeries { Title = "Seen [kW]", Color = OxyColor.FromRgb(0, 0, 255), YAxisKey = "Right" }); + this.Model.Series.Add(new LineSeries { Title = "Power [W]", Color = OxyColor.FromRgb(0, 150, 0) }); + this.Model.Series.Add(new LineSeries { Title = "Houses", Color = OxyColor.FromRgb(0, 0, 255), YAxisKey= "Right" }); + this.Model.Series.Add(new LineSeries { Title = "Devices", Color = OxyColor.FromRgb(255, 0, 0), YAxisKey = "Right" }); this.RaisePropertyChanged("Model"); } - internal void AddPower(Tuple power, Tuple colum) { - if((power.Item1 == 0 && power.Item2 == 0) || (colum.Item1 == 0 && colum.Item2 == 0)) { + internal void AddPower(Tuple power, Tuple houses, Tuple devices) { + if((power.Item1 == 0 && power.Item2 == 0) || (houses.Item1 == 0 && houses.Item2 == 0 && houses.Item3 == 0) || (devices.Item1 == 0 && devices.Item2 == 0)) { return; } ((LineSeries)this.Model.Series[0]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), power.Item1)); - ((LineSeries)this.Model.Series[1]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), power.Item2)); - ((LineSeries)this.Model.Series[2]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), colum.Item1)); - ((LineSeries)this.Model.Series[3]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), colum.Item2)); + ((LineSeries)this.Model.Series[1]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), houses.Item1)); + ((LineSeries)this.Model.Series[2]).Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), devices.Item1)); if (this.MaximumDrawnL < power.Item1) { this.MaximumDrawnL = power.Item1; } - if (this.MaximumDrawnL < power.Item2) { - this.MaximumDrawnL = power.Item2; + if (this.MaximumDrawnR < houses.Item1) { + this.MaximumDrawnR = houses.Item1; } - if (this.MaximumDrawnR < colum.Item1) { - this.MaximumDrawnR = colum.Item1; - } - if (this.MaximumDrawnR < colum.Item2) { - this.MaximumDrawnR = colum.Item2; + if (this.MaximumDrawnR < devices.Item1) { + this.MaximumDrawnR = devices.Item1; } //this.Model.Axes[1].Minimum = 0; //this.Model.Axes[2].Minimum = 0; diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mosquitto.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mosquitto.cs deleted file mode 100644 index 597b5f5..0000000 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mosquitto.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text.RegularExpressions; - -namespace Mqtt_SWB_Dashboard { - class Mosquitto : IDisposable { - private Process p; - private String message; - - public delegate void MqttMessage(Object sender, MqttEventArgs e); - public event MqttMessage MessageIncomming; - - public Mosquitto(String broker) { - //mosquitto_sub --cafile ca.pem --cert cert.pem --key cert.key -h swb.broker.flex4grid.eu -p 8883 -t /# -v - this.message = ""; - this.p = new Process(); - this.p.StartInfo.FileName = "mosquitto_sub.exe"; - this.p.StartInfo.Arguments = "--cafile ca.pem --cert cert.pem --key cert.key -h "+broker+" -p 8883 -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(); - } - - 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. - - protected virtual void Dispose(Boolean disposing) { - if (!this.disposedValue) { - if (disposing) { - this.p.CancelOutputRead(); - this.p.Kill(); - this.p.Close(); - } - this.p = null; - this.disposedValue = true; - } - } - - ~Mosquitto() { - Dispose(false); - } - - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - #endregion - } - 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/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard.csproj b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard.csproj index 7037cb8..837ea42 100644 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard.csproj +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard.csproj @@ -63,6 +63,8 @@ MSBuild:Compile Designer + + @@ -82,7 +84,6 @@ - Code @@ -105,6 +106,7 @@ SettingsSingleFileGenerator Settings.Designer.cs + diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Stats.cs b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Stats.cs index 5d707d3..2d8a587 100644 --- a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Stats.cs +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/Stats.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Dashboard.Connector; using LitJson; using Mqtt_SWB_Dashboard.Helper; @@ -10,12 +11,12 @@ namespace Mqtt_SWB_Dashboard { internal class Stats : IDisposable { private SortedDictionary households = new SortedDictionary(); private SortedDictionary raspis = new SortedDictionary(); - private Mosquitto mosquitto; + private ADataBackend mosquitto; public delegate void UpdateMessage(Stats sender, EventArgs e); public event UpdateMessage UpdatedConsumption; - public Stats(Mosquitto mosquitto) { + public Stats(ADataBackend mosquitto) { LoadSavedData(); this.mosquitto = mosquitto; this.mosquitto.MessageIncomming += this.Mosquitto_MessageIncomming; @@ -44,6 +45,10 @@ namespace Mqtt_SWB_Dashboard { private void Mosquitto_MessageIncomming(Object sender, MqttEventArgs e) { if (Regex.Match(e.Topic, "/flex4grid/VersionControl/LocalGateway/groups/.*").Success) { this.LvcMessages(e); + } else if(Regex.Match(e.Topic, "/flex4grid/v1/load_balancing/events/.*").Success) { + this.PowerEvents(e); + } else if (Regex.Match(e.Topic, "/flex4grid/v1/households/[^/]*/device/error").Success) { + this.ErrorDeviceMessage(e); } else if (Regex.Match(e.Topic, "/flex4grid/VersionControl/LocalGateway/hosts/.*/status").Success || e.Topic == "/flex4grid/VersionControl/LocalGateway/status") { this.RaspiStatus(e); @@ -67,6 +72,22 @@ namespace Mqtt_SWB_Dashboard { #region Mqtt-Message Parser + /// + /// Fehlermeldung eines Plugs + /// + /// + private void ErrorDeviceMessage(MqttEventArgs e) { + // /flex4grid/v1/households/[^/]*/device/error {"errorCode": "200", "timestamp": "2017-09-26T19:21:58.561839Z", "errorMessage": "Unable_to_start_ZWave_network", "id": "1"} + } + + /// + /// Raspberry Powerevents + /// + /// + private void PowerEvents(MqttEventArgs e) { + // /flex4grid/v1/load_balancing/events/10000000 {"status": 1, "areaId": "1000000", "end": "2017-09-20T18:00:00.0000000000Z", "timestamp": "2017-09-19T08:55:52.237Z", "group_name": "ALL_2017_09_20", "start" :"2017-09-20T17:00:00.0000000000Z", "magnitude": "49", "id":"84"} + } + /// /// LogMessages eines Raspis /// @@ -173,7 +194,7 @@ namespace Mqtt_SWB_Dashboard { #region Statistics Output - internal String GetNumberDevices() { + internal Tuple GetNumberDevices() { Int32 active = 0; Int32 all = 0; try { @@ -182,18 +203,18 @@ namespace Mqtt_SWB_Dashboard { all += item.Value.Devices.Count; } } catch(Exception) { - return "0 / 0"; + return new Tuple(0, 0); } - return active.ToString() + " / " + all.ToString(); + return new Tuple(active, all); } - internal String GetNumberHouseholds() { + internal Tuple GetNumberHouseholds() { Int32 active = 0; Int32 ping = 0; Int32 all = this.households.Count; try { foreach (KeyValuePair item in this.households) { - if (item.Value.Active > DateTime.Now.AddMinutes(-10)) { + if (item.Value.Active > DateTime.Now.AddMinutes(-15)) { ping++; } if (item.Value.Connected) { @@ -201,9 +222,9 @@ namespace Mqtt_SWB_Dashboard { } } } catch (Exception) { - return "0 / 0 / 0"; + return new Tuple(0, 0, 0); } - return ping + " / " + active + " / " + all; + return new Tuple(ping, active, all); } internal Tuple GetCurrentPower() { @@ -220,7 +241,7 @@ namespace Mqtt_SWB_Dashboard { return new Tuple(active, all); } - internal Tuple GetCurrentColum() { + internal String GetCurrentColum() { Double active = 0; Double all = 0; try { @@ -229,9 +250,9 @@ namespace Mqtt_SWB_Dashboard { all += item.Value.GetAllColum(); } } catch(Exception) { - return new Tuple(0d, 0d); + return "0kWh / 0kWh"; } - return new Tuple(active, all); + return active.ToString("F1") + "kWh / " + all.ToString("F1") + "kWh"; } internal String GetNumberRaspis() { diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/bin/Release/Mqtt-SWB-Dashboard.exe b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/bin/Release/Mqtt-SWB-Dashboard.exe index b775cc49b938954413460bbb23354d15858ebb04..a9d9abc97c5af6c34fd6b1e7143340eb3ca57739 100644 GIT binary patch literal 34816 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 AaR2}S literal 33280 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 S|C|%Ope;7zFRFEr@4o>P-?`xc diff --git a/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/settings.ini.example b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/settings.ini.example new file mode 100644 index 0000000..0ed4f13 --- /dev/null +++ b/Mqtt-SWB-Dashboard/Mqtt-SWB-Dashboard/settings.ini.example @@ -0,0 +1,8 @@ +[mqtt] +type=mosquitto +; server=localhost +server=swb.broker.flex4grid.eu +port=8883 +cafile=ca.pem +cert=cert.pem +key=cert.key \ No newline at end of file diff --git a/MqttToTelegram/MqttToTelegram/Condition/ACondition.cs b/MqttToTelegram/MqttToTelegram/Condition/ACondition.cs new file mode 100644 index 0000000..c0357d1 --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Condition/ACondition.cs @@ -0,0 +1,32 @@ +using Dashboard.Sensor; +using System; +using System.Collections.Generic; + +namespace MqttToTelegram.Condition { + abstract class ACondition { + protected ASensor sensor; + protected Dictionary settings; + + public ACondition(Dictionary settings) { + this.settings = settings; + Dictionary l = new Dictionary(); + l.Add("topic", this.settings["sensor_topic"]); + l.Add("polling", this.settings["sensor_polling"]); + this.sensor = ASensor.GetInstance(this.settings["sensor"],l); + this.sensor.Update += Sensor_Update; + } + + protected abstract void Sensor_Update(Object sender, EventArgs e); + + public static ACondition GetInstance(String v, Dictionary dictionary) { + string object_condition = "MqttToTelegram.Condition." + char.ToUpper(v[0]) + v.Substring(1).ToLower(); + Type t = null; + try { + t = Type.GetType(object_condition, true); + } catch(TypeLoadException) { + throw new ArgumentException("condition.ini: " + v + " is not a Sensor"); + } + return (ACondition)t.GetConstructor(new Type[] { typeof(Dictionary) }).Invoke(new object[] { dictionary }); + } + } +} \ No newline at end of file diff --git a/MqttToTelegram/MqttToTelegram/Condition/ConditionWorker.cs b/MqttToTelegram/MqttToTelegram/Condition/ConditionWorker.cs new file mode 100644 index 0000000..b4d2f5a --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Condition/ConditionWorker.cs @@ -0,0 +1,35 @@ +using BlubbFish.Utils; +using System; +using System.Collections.Generic; +using Dashboard.Connector; + +namespace MqttToTelegram.Condition { + class ConditionWorker { + private InIReader ini; + private static ConditionWorker instance; + private List conditions; + private Telegram telegram; + + public static ConditionWorker Instance { + get { + if(instance == null) { + instance = new ConditionWorker(); + } + return instance; + } + } + + internal void Run(Telegram telegram) { + this.telegram = telegram; + } + + private ConditionWorker() { + this.ini = InIReader.GetInstance("condition.ini"); + this.conditions = new List(); + List sections = this.ini.GetSections(); + foreach(String section in sections) { + this.conditions.Add(ACondition.GetInstance(this.ini.GetValue(section,"type"), this.ini.GetSection(section))); + } + } + } +} diff --git a/MqttToTelegram/MqttToTelegram/Condition/Edge.cs b/MqttToTelegram/MqttToTelegram/Condition/Edge.cs new file mode 100644 index 0000000..ee52876 --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Condition/Edge.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MqttToTelegram.Condition { + class Edge : ACondition { + private Boolean histBool; + + public Edge(Dictionary settings) : base(settings) { + + } + + protected override void Sensor_Update(Object sender, EventArgs e) { + if(this.sensor.Datatypes == Dashboard.Sensor.ASensor.Types.Bool) { + if(this.sensor.GetBool == Boolean.Parse(this.settings["sensor_value"]) && this.histBool != this.sensor.GetBool) { + this.histBool = this.sensor.GetBool; + Telegram.Instance.Send("Jemand ist DA!"); + } else { + this.histBool = this.sensor.GetBool; + } + } + } + } +} diff --git a/MqttToTelegram/MqttToTelegram/Connector/ADataBackend.cs b/MqttToTelegram/MqttToTelegram/Connector/ADataBackend.cs new file mode 100644 index 0000000..33a0301 --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Connector/ADataBackend.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace Dashboard.Connector { + public abstract class ADataBackend { + + public abstract event MqttMessage MessageIncomming; + public abstract event MqttMessage MessageSending; + public delegate void MqttMessage(Object sender, MqttEventArgs e); + + public static ADataBackend GetInstance(Dictionary dictionary) { + String object_sensor = "Dashboard.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/MqttToTelegram/MqttToTelegram/Connector/Mosquitto.cs b/MqttToTelegram/MqttToTelegram/Connector/Mosquitto.cs new file mode 100644 index 0000000..cf88022 --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Connector/Mosquitto.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; + +namespace Dashboard.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/MqttToTelegram/MqttToTelegram/Connector/Mqtt.cs b/MqttToTelegram/MqttToTelegram/Connector/Mqtt.cs new file mode 100644 index 0000000..36d21a4 --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/Connector/Mqtt.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BlubbFish.Utils; +using uPLibrary.Networking.M2Mqtt; +using uPLibrary.Networking.M2Mqtt.Messages; + +namespace Dashboard.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/MqttToTelegram/MqttToTelegram/Connector/Telegram.cs b/MqttToTelegram/MqttToTelegram/Connector/Telegram.cs new file mode 100644 index 0000000..4ec1ded --- /dev/null +++ b/MqttToTelegram/MqttToTelegram/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 MqttToTelegram { + 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() { + bot = new TelegramBotClient(InIReader.GetInstance("settings.ini").GetValue("general", "telegram-key")); + bot.OnMessage += 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() { + 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/MqttToTelegram/MqttToTelegram/Mqtt.cs b/MqttToTelegram/MqttToTelegram/Mqtt.cs deleted file mode 100644 index e0ccfa0..0000000 --- a/MqttToTelegram/MqttToTelegram/Mqtt.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Text; -using uPLibrary.Networking.M2Mqtt; -using uPLibrary.Networking.M2Mqtt.Messages; - -namespace MqttToTelegram { - class Mqtt { - private static Mqtt instance; - private MqttClient client; - - private Mqtt() { - client = new MqttClient("129.26.160.65"); - byte code = client.Connect(System.Guid.NewGuid().ToString()); - client.MqttMsgPublishReceived += MessageResieved; - } - - private void MessageResieved(System.Object sender, MqttMsgPublishEventArgs e) { - Console.WriteLine("Received = " + Encoding.UTF8.GetString(e.Message) + " on Topic " + e.Topic); - } - - public static Mqtt Instance { - get { - if(instance == null) { - instance = new Mqtt(); - } - return instance; - } - } - public void Subscripe(string[] topics) { - byte[] qos = new byte[topics.Length]; - for(int i=0;i - + + + + - + + + + + + + + + + + {fac8ce64-bf13-4ece-8097-aeb5dd060098} + Utils + + +