diff --git a/Bot-Utils.csproj b/Bot-Utils.csproj index ec2d46c..7c26c2d 100644 --- a/Bot-Utils.csproj +++ b/Bot-Utils.csproj @@ -7,10 +7,12 @@ {BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9} Library Properties - Bot_Utils + BlubbFish.Utils.IoT.Bots Bot-Utils v4.7.1 512 + + true @@ -29,6 +31,9 @@ prompt 4 + + + @@ -40,8 +45,37 @@ - + + + + + + + + + + + + + + 5.4.0.201 + + + + + {91a14cd2-2940-4500-8193-56d37edddbaa} + litjson_4.7.1 + + + {b870e4d5-6806-4a0b-b233-8907eedc5afc} + Utils-IoT + + + {fac8ce64-bf13-4ece-8097-aeb5dd060098} + Utils + + \ No newline at end of file diff --git a/Bot.cs b/Bot.cs new file mode 100644 index 0000000..ddde4fe --- /dev/null +++ b/Bot.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using BlubbFish.Utils.IoT.Bots.Moduls; +using BlubbFish.Utils.IoT.Bots.Events; +using BlubbFish.Utils.IoT.Bots.Interfaces; + +namespace BlubbFish.Utils.IoT.Bots { + public abstract class Bot { + private Thread sig_thread; + private Boolean RunningProcess = true; + protected ProgramLogger logger = new ProgramLogger(); + protected readonly Dictionary moduls = new Dictionary(); + + protected void WaitForShutdown() { + if (Type.GetType("Mono.Runtime") != null) { + this.sig_thread = new Thread(delegate () { + Mono.Unix.UnixSignal[] signals = new Mono.Unix.UnixSignal[] { + new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGTERM), + new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGINT) + }; + Console.WriteLine("Signalhandler Mono attached."); + while (true) { + Int32 i = Mono.Unix.UnixSignal.WaitAny(signals, -1); + Console.WriteLine("Signalhandler Mono INT recieved " + i + "."); + this.RunningProcess = false; + break; + } + }); + this.sig_thread.Start(); + } else { + Console.CancelKeyPress += new ConsoleCancelEventHandler(this.SetupShutdown); + Console.WriteLine("Signalhandler Windows attached."); + } + while (this.RunningProcess) { + Thread.Sleep(100); + } + } + + private void SetupShutdown(Object sender, ConsoleCancelEventArgs e) { + e.Cancel = true; + Console.WriteLine("Signalhandler Windows INT recieved."); + this.RunningProcess = false; + } + + protected void ModulDispose() { + foreach (KeyValuePair item in this.moduls) { + ((AModul)item.Value).Dispose(); + Console.WriteLine("Modul entladen: " + item.Key); + } + if (this.sig_thread != null && this.sig_thread.IsAlive) { + this.sig_thread.Abort(); + } + } + + protected void ModulLoader(String @namespace, Object library) { + Assembly asm = Assembly.GetEntryAssembly(); + foreach (Type item in asm.GetTypes()) { + if (item.Namespace == @namespace) { + Type t = item; + String name = t.Name; + if (InIReader.ConfigExist(name.ToLower())) { + this.moduls.Add(name, (AModul)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, InIReader.GetInstance(name.ToLower()) })); + Console.WriteLine("Load Modul " + name); + } else if (t.HasInterface(typeof(IForceLoad))) { + this.moduls.Add(name, (AModul)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, null })); + Console.WriteLine("Load Modul Forced " + name); + } + } + } + } + + protected void ModulInterconnect() { + foreach (KeyValuePair item in this.moduls) { + ((AModul)item.Value).Interconnect(this.moduls); + } + } + + protected void ModulEvents() { + foreach (KeyValuePair item in this.moduls) { + ((AModul)item.Value).Update += this.ModulUpdate; + } + } + + protected void ModulUpdate(Object sender, ModulEventArgs e) { + Console.WriteLine(e.ToString()); + } + } +} diff --git a/Events/CronEvent.cs b/Events/CronEvent.cs new file mode 100644 index 0000000..cb7237e --- /dev/null +++ b/Events/CronEvent.cs @@ -0,0 +1,16 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class CronEvent : ModulEventArgs { + + public CronEvent() { + } + + public CronEvent(String addr, String prop, String value) { + this.Address = addr; + this.Property = prop; + this.Value = value; + this.Source = "Cronjob"; + } + } +} diff --git a/Events/ModulEventArgs.cs b/Events/ModulEventArgs.cs new file mode 100644 index 0000000..ee44173 --- /dev/null +++ b/Events/ModulEventArgs.cs @@ -0,0 +1,15 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class ModulEventArgs : EventArgs { + public ModulEventArgs() { + } + public String Address { get; protected set; } + public String Property { get; protected set; } + public String Value { get; protected set; } + public String Source { get; protected set; } + public override String ToString() { + return this.Source + ": " + this.Address + " set " + this.Property + " to " + this.Value; + } + } +} diff --git a/Events/MqttEvent.cs b/Events/MqttEvent.cs new file mode 100644 index 0000000..fa30fa7 --- /dev/null +++ b/Events/MqttEvent.cs @@ -0,0 +1,16 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class MqttEvent : ModulEventArgs { + public MqttEvent() { + } + public MqttEvent(String topic, String text) { + this.Address = topic; + this.Value = text; + this.Source = "MQTT"; + } + public override String ToString() { + return this.Source + ": on " + this.Address + " set " + this.Value; + } + } +} diff --git a/Events/OvertakerEvent.cs b/Events/OvertakerEvent.cs new file mode 100644 index 0000000..0af0525 --- /dev/null +++ b/Events/OvertakerEvent.cs @@ -0,0 +1,16 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class OvertakerEvent : ModulEventArgs { + + public OvertakerEvent() { + } + + public OvertakerEvent(String addr, String prop, String value) { + this.Address = addr; + this.Property = prop; + this.Value = value; + this.Source = "Overtaker"; + } + } +} diff --git a/Events/SenmlEvent.cs b/Events/SenmlEvent.cs new file mode 100644 index 0000000..fcdfce2 --- /dev/null +++ b/Events/SenmlEvent.cs @@ -0,0 +1,16 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class SenmlEvent : ModulEventArgs { + public SenmlEvent() { + } + public SenmlEvent(String topic, String text) { + this.Address = topic; + this.Value = text; + this.Source = "Senml"; + } + public override String ToString() { + return this.Source + ": on " + this.Address + " set " + this.Value; + } + } +} diff --git a/Events/StatusPollingEvent.cs b/Events/StatusPollingEvent.cs new file mode 100644 index 0000000..70238ed --- /dev/null +++ b/Events/StatusPollingEvent.cs @@ -0,0 +1,18 @@ +using System; + +namespace BlubbFish.Utils.IoT.Bots.Events { + public class StatusPollingEvent : ModulEventArgs { + public StatusPollingEvent() { + } + + public StatusPollingEvent(String text, String node) { + this.Value = text; + this.Address = node; + this.Source = "POLLING"; + } + + public override String ToString() { + return this.Source + ": " + this.Value + " on " + this.Address; + } + } +} diff --git a/Helper.cs b/Helper.cs new file mode 100644 index 0000000..34fd3e7 --- /dev/null +++ b/Helper.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; + +namespace BlubbFish.Utils.IoT.Bots { + public static class Helper { + #region PropertyHelper + public static Boolean HasProperty(this Object o, String type) { + Type t = o.GetType(); + foreach (PropertyInfo item in t.GetProperties()) { + if (item.Name == type) { + return true; + } + } + return false; + } + + public static Object GetProperty(this Object o, String name) { + PropertyInfo prop = o.GetType().GetProperty(name); + if (prop.CanRead) { + return prop.GetValue(o); + } + return null; + } + + public static void SetProperty(this Object o, String name, String value) { + PropertyInfo prop = o.GetType().GetProperty(name); + if (prop.CanWrite) { + if (prop.PropertyType == typeof(Boolean) && Boolean.TryParse(value, out Boolean vb)) { + prop.SetValue(o, vb); + } else if (prop.PropertyType == typeof(Int32) && Int32.TryParse(value, out Int32 v32)) { + prop.SetValue(o, v32); + } else if (prop.PropertyType == typeof(Single) && Single.TryParse(value, out Single vs)) { + prop.SetValue(o, vs); + } else if (prop.PropertyType == typeof(Double) && Double.TryParse(value, out Double vd)) { + prop.SetValue(o, vd); + } else if (prop.PropertyType == typeof(Int64) && Int64.TryParse(value, out Int64 v64)) { + prop.SetValue(o, v64); + } + } + } + #endregion + + #region InterfaceHelper + public static Boolean HasInterface(this Type o, Type interf) { + foreach (Type item in o.GetInterfaces()) { + if (item == interf) { + return true; + } + } + return false; + } + + public static Boolean HasAbstract(this Type o, Type type) { + if (o.BaseType == type) { + return true; + } + return false; + } + #endregion + + #region StringHelper + public static String ToUpperLower(this String s) { + if (s.Length == 0) { + return ""; + } + if (s.Length == 1) { + return s.ToUpper(); + } + return s[0].ToString().ToUpper() + s.Substring(1).ToLower(); + } + + public static void WriteError(String text) { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("ERROR: " + text); + Console.ResetColor(); + } + #endregion + } +} diff --git a/Interfaces/IForceLoad.cs b/Interfaces/IForceLoad.cs new file mode 100644 index 0000000..eb89e6a --- /dev/null +++ b/Interfaces/IForceLoad.cs @@ -0,0 +1,4 @@ +namespace BlubbFish.Utils.IoT.Bots.Interfaces { + public interface IForceLoad { + } +} diff --git a/Moduls/AModul.cs b/Moduls/AModul.cs new file mode 100644 index 0000000..2745e60 --- /dev/null +++ b/Moduls/AModul.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using BlubbFish.Utils.IoT.Bots.Events; + +namespace BlubbFish.Utils.IoT.Bots.Moduls { + public abstract class AModul { + protected T library; + private readonly InIReader settings; + protected Dictionary> config = new Dictionary>(); + + public Boolean HasConfig { get; private set; } + public Boolean ConfigPublic { get; private set; } + + public delegate void ModulEvent(Object sender, ModulEventArgs e); + public abstract event ModulEvent Update; + + public AModul(T lib, InIReader settings) { + this.HasConfig = false; + this.ConfigPublic = false; + this.library = lib; + this.settings = settings; + this.ParseConfig(); + } + + private void ParseConfig() { + if (this.settings != null) { + this.HasConfig = true; + foreach (String item in this.settings.GetSections(false)) { + this.config.Add(item, this.settings.GetSection(item)); + } + if (this.config.ContainsKey("modul")) { + this.ConfigPublic = this.config["modul"].ContainsKey("config") && this.config["modul"]["config"].ToLower() == "public"; + } + } + } + + public Dictionary> GetConfig() { + if (this.HasConfig && this.ConfigPublic) { + Dictionary> ret = new Dictionary>(this.config); + if (ret.ContainsKey("modul")) { + ret.Remove("modul"); + } + return ret; + } + return new Dictionary>(); + } + + public virtual void Interconnect(Dictionary moduls) { } + + public virtual void SetInterconnection(String param, Action hook, Object data) { } + + public abstract void Dispose(); + + public void SetConfig(Dictionary> newconf) { + if (this.HasConfig && this.ConfigPublic) { + if (newconf.ContainsKey("modul")) { + newconf.Remove("modul"); + } + if (this.config.ContainsKey("modul")) { + newconf.Add("modul", this.config["modul"]); + } + this.config = newconf; + this.settings.SetSections(this.config); + this.UpdateConfig(); + } + } + + protected abstract void UpdateConfig(); + } +} diff --git a/Moduls/Mqtt.cs b/Moduls/Mqtt.cs new file mode 100644 index 0000000..62424ab --- /dev/null +++ b/Moduls/Mqtt.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading; +using BlubbFish.Utils.IoT.Bots.Events; +using BlubbFish.Utils.IoT.Connector; +using BlubbFish.Utils.IoT.Events; +using LitJson; + +namespace BlubbFish.Utils.IoT.Bots.Moduls { + public abstract class Mqtt : AModul, IDisposable { + protected readonly Thread connectionWatcher; + protected ABackend mqtt; + protected Dictionary modules; + + #region Constructor + public Mqtt(T lib, InIReader settings) : base(lib, settings) { + if (this.config.ContainsKey("settings")) { + this.connectionWatcher = new Thread(this.ConnectionWatcherRunner); + this.connectionWatcher.Start(); + } + } + #endregion + + #region Watcher + protected void ConnectionWatcherRunner() { + while (true) { + try { + if (this.mqtt == null || !this.mqtt.IsConnected) { + this.Reconnect(); + } + Thread.Sleep(10000); + } catch (Exception) { } + } + } + + protected void Reconnect() { + this.Disconnect(); + this.Connect(); + } + + protected abstract void Connect(); + + protected abstract void Disconnect(); + #endregion + + #region AModul + public override void Interconnect(Dictionary moduls) { + this.modules = moduls; + } + + protected override void UpdateConfig() { + this.Reconnect(); + } + #endregion + + protected Tuple ChangeConfig(BackendEvent e, String topic) { + if (e.From.ToString().StartsWith(topic) && (e.From.ToString().EndsWith("/set") || e.From.ToString().EndsWith("/get"))) { + Match m = new Regex("^"+ topic + "(\\w+)/[gs]et$|").Match(e.From.ToString()); + if (!m.Groups[1].Success) { + return new Tuple(false, null); + } + AModul modul = null; + foreach (KeyValuePair item in this.modules) { + if (item.Key.ToLower() == m.Groups[1].Value) { + modul = ((AModul)item.Value); + } + } + if (modul == null) { + return new Tuple(false, null); + } + if (e.From.ToString().EndsWith("/get") && modul.HasConfig && modul.ConfigPublic) { + String t = topic + m.Groups[1].Value; + String d = JsonMapper.ToJson(modul.GetConfig()).ToString(); + ((ADataBackend)this.mqtt).Send(t, d); + return new Tuple(true, new MqttEvent(t, d)); + } else if (e.From.ToString().EndsWith("/set") && modul.HasConfig && modul.ConfigPublic) { + try { + JsonData a = JsonMapper.ToObject(e.Message); + Dictionary> newconf = new Dictionary>(); + foreach (String section in a.Keys) { + Dictionary sectiondata = new Dictionary(); + foreach (String item in a[section].Keys) { + sectiondata.Add(item, a[section][item].ToString()); + } + newconf.Add(section, sectiondata); + } + modul.SetConfig(newconf); + return new Tuple(true, new MqttEvent("New Config", "Write")); + } catch (Exception) { } + } + } + return new Tuple(false, null); + } + + #region IDisposable Support + private Boolean disposedValue = false; + + protected void Dispose(Boolean disposing) { + if (!this.disposedValue) { + if (disposing) { + this.connectionWatcher.Abort(); + while (this.connectionWatcher.ThreadState == ThreadState.Running) { Thread.Sleep(10); } + this.Disconnect(); + } + this.disposedValue = true; + } + } + + public override void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/bin/Release/Bot-Utils.dll b/bin/Release/Bot-Utils.dll index b1fa95c..42947bf 100644 Binary files a/bin/Release/Bot-Utils.dll and b/bin/Release/Bot-Utils.dll differ