commit 9fb1782abe0ab6a5b7429eafc35861b34a25fbae Author: BlubbFish Date: Sat Nov 25 11:00:48 2017 +0000 [NF] Zway-Bot hinzugefügt [NF] Viele kleine Änderungen an Zwaylib zur Verbesserung der Kompatibilität diff --git a/Zway-Bot.sln b/Zway-Bot.sln new file mode 100644 index 0000000..42984ac --- /dev/null +++ b/Zway-Bot.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zway-Bot", "Zway-Bot\Zway-Bot.csproj", "{AEF8059F-8AEF-45C7-85C4-318C8F797900}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zway", "..\Zway\Zway\Zway.csproj", "{166258ED-CB3D-43F5-8E8D-3A993B64D022}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "..\Utils\Utils\Utils.csproj", "{FAC8CE64-BF13-4ECE-8097-AEB5DD060098}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AEF8059F-8AEF-45C7-85C4-318C8F797900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEF8059F-8AEF-45C7-85C4-318C8F797900}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEF8059F-8AEF-45C7-85C4-318C8F797900}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEF8059F-8AEF-45C7-85C4-318C8F797900}.Release|Any CPU.Build.0 = Release|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Debug|Any CPU.Build.0 = Debug|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.ActiveCfg = Release|Any CPU + {166258ED-CB3D-43F5-8E8D-3A993B64D022}.Release|Any CPU.Build.0 = Release|Any CPU + {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAC8CE64-BF13-4ECE-8097-AEB5DD060098}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC298685-EC11-4957-9D91-F044DDDD0B6E} + EndGlobalSection +EndGlobal diff --git a/Zway-Bot/CronJob.cs b/Zway-Bot/CronJob.cs new file mode 100644 index 0000000..50569d8 --- /dev/null +++ b/Zway-Bot/CronJob.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Threading; +using BlubbFish.IoT.Zway; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.Utils; + +namespace ZwayBot { + internal class CronJob : IDisposable { + private ZwayController zw; + private InIReader ini; + private DateTime crontime; + private Thread thread; + + public delegate void CronRunned(Object sender, CronEvent e); + public event CronRunned Update; + + private Dictionary cron_named = new Dictionary { + { "@yearly", "0 0 1 1 *" }, + { "@annually", "0 0 1 1 *" }, + { "@monthly", "0 0 1 * *" }, + { "@weekly", "0 0 * * 0" }, + { "@daily", "0 0 * * *" }, + { "@hourly", "0 * * * *" } + }; + + public CronJob(ZwayController zw) { + this.zw = zw; + this.ini = InIReader.GetInstance("cron.ini"); + this.crontime = DateTime.Now; + this.thread = new Thread(this.Runner); + this.thread.Start(); + } + + private void Runner() { + Thread.Sleep(DateTime.Now.AddMinutes(1).AddSeconds(DateTime.Now.Second * (-1)).AddMilliseconds(DateTime.Now.Millisecond * (-1)) - DateTime.Now); + while (true) { + if(this.crontime.Minute < DateTime.Now.Minute) { + this.crontime = DateTime.Now; + foreach (String item in this.ini.GetSections()) { + if(ParseCronString(this.ini.GetValue(item, "cron"))) { + this.SetValues(this.ini.GetValue(item, "set")); + } + } + } + Thread.Sleep(100); + } + } + + private void SetValues(String value) { + foreach (String item in value.Split(';')) { + String[] items = item.Split(':'); + if(items.Length == 2) { + String[] addr = items[0].Split('-'); + String[] values = items[1].Split('-'); + if(addr.Length == 3 || addr.Length == 4) { + Int32 deviceid = Int32.Parse(addr[0]); + Int32 instanceid = Int32.Parse(addr[1]); + Int32 classid = Int32.Parse(addr[2]); + ICommandClass c; + if(addr.Length == 4) { + Int32 subcid = Int32.Parse(addr[3]); + c = this.zw.GetCommandClassSub(deviceid, instanceid, classid, subcid); + } else { + c = this.zw.GetCommandClass(deviceid, instanceid, classid); + } + if (c != null && values.Length == 2) { + if (Helper.HasProperty(c, values[0])) { + Helper.SetProperty(c, values[0], values[1]); + this.Update?.Invoke(this, new CronEvent(items[0], values[0], values[1])); + } + } + } + } + } + } + + private Boolean ParseCronString(String str) { + str = str.Trim(); + if(this.cron_named.ContainsKey(str)) { + str = this.cron_named[str]; + } + String[] value = str.Split(' '); + if(value.Length != 5) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("mm"), value[0], "0-59")) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("HH"), value[1], "0-23")) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("MM"), value[3], "1-12")) { + return false; + } + if (value[2] != "*" && value[4] != "*") { + if (!this.CheckDateStr(this.crontime.ToString("dd"), value[2], "1-31") && !this.CheckDateStr(((Int32)this.crontime.DayOfWeek).ToString(), value[4], "0-7")) { + return false; + } + } else { + if (!this.CheckDateStr(this.crontime.ToString("dd"), value[2], "1-31")) { + return false; + } + if (!this.CheckDateStr(((Int32)this.crontime.DayOfWeek).ToString(), value[4], "0-7")) { + return false; + } + } + return true; + } + + private Boolean CheckDateStr(String date, String cron, String limit) { + cron = cron.ToLower(); + for (Int32 i = 0; i <= 6; i++) { + cron = cron.Replace(DateTime.Parse("2015-01-" + (4 + i) + "T00:00:00").ToString("ddd", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + cron = cron.Replace(DateTime.Parse("2015-01-" + (4 + i) + "T00:00:00").ToString("dddd", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + } + for (Int32 i = 1; i <= 12; i++) { + cron = cron.Replace(DateTime.Parse("2015-"+i+"-01T00:00:00").ToString("MMM", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + cron = cron.Replace(DateTime.Parse("2015-" + i + "-01T00:00:00").ToString("MMMM", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + } + if (cron.Contains("*")) { + cron = cron.Replace("*", limit); + } + if(cron.Contains("-")) { + MatchCollection m = new Regex("(\\d+)-(\\d+)").Matches(cron); + foreach (Match p in m) { + List s = new List(); + for(Int32 i=Math.Min(Int32.Parse(p.Groups[1].Value), Int32.Parse(p.Groups[2].Value));i<= Math.Max(Int32.Parse(p.Groups[1].Value), Int32.Parse(p.Groups[2].Value)); i++) { + s.Add(i.ToString()); + } + cron = cron.Replace(p.Groups[0].Value, String.Join(",", s)); + } + } + Int32 match = 0; + if(cron.Contains("/")) { + Match m = new Regex("/(\\d+)").Match(cron); + cron = cron.Replace(m.Groups[0].Value, ""); + match = Int32.Parse(m.Groups[1].Value); + } + Dictionary ret = new Dictionary(); + if(!cron.Contains(",")) { + ret.Add(Int32.Parse(cron), ""); + } else { + foreach (String item in cron.Split(',')) { + if(!ret.ContainsKey(Int32.Parse(item))) { + ret.Add(Int32.Parse(item), ""); + } + } + } + if(match != 0) { + Dictionary r = new Dictionary(); + foreach (KeyValuePair item in ret) { + if(item.Key%match == 0) { + r.Add(item.Key, ""); + } + } + ret = r; + } + return ret.ContainsKey(Int32.Parse(date)); + } + + #region IDisposable Support + private Boolean disposedValue = false; // Dient zur Erkennung redundanter Aufrufe. + + protected virtual void Dispose(Boolean disposing) { + if (!this.disposedValue) { + if (disposing) { + this.thread.Abort(); + while(this.thread.ThreadState != ThreadState.Aborted) { Thread.Sleep(100); } + } + + this.thread = null; + + this.disposedValue = true; + } + } + + ~CronJob() { + Dispose(false); + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} \ No newline at end of file diff --git a/Zway-Bot/Events.cs b/Zway-Bot/Events.cs new file mode 100644 index 0000000..ac7f33e --- /dev/null +++ b/Zway-Bot/Events.cs @@ -0,0 +1,34 @@ +using System; + +namespace ZwayBot { + public class CronEvent : EventArgs { + + public CronEvent() { + } + + public CronEvent(String addr, String prop, String value) { + this.Address = addr; + this.Property = prop; + this.Value = value; + } + + public String Address { get; } + public String Property { get; } + public String Value { get; } + } + public class OvertakerEvent : EventArgs { + + public OvertakerEvent() { + } + + public OvertakerEvent(String addr, String prop, String value) { + this.Address = addr; + this.Property = prop; + this.Value = value; + } + + public String Address { get; } + public String Property { get; } + public String Value { get; } + } +} diff --git a/Zway-Bot/Helper.cs b/Zway-Bot/Helper.cs new file mode 100644 index 0000000..283215f --- /dev/null +++ b/Zway-Bot/Helper.cs @@ -0,0 +1,48 @@ +using System; +using System.Reflection; + +namespace ZwayBot { + class Helper { + public static void SetProperty(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(Int64) && Int64.TryParse(value, out Int64 v64)) { + prop.SetValue(o, v64); + } + } + } + public static Boolean HasProperty(Object o, String type) { + Type t = o.GetType(); + foreach (PropertyInfo item in t.GetProperties()) { + if (item.Name == type) { + return true; + } + } + return false; + } + + internal static Object GetProperty(Object o, String name) { + PropertyInfo prop = o.GetType().GetProperty(name); + if(prop.CanRead) { + return prop.GetValue(o); + } + return null; + } + + internal static Boolean HasInterface(Object o, String type) { + Type t = o.GetType(); + foreach (Type item in t.GetInterfaces()) { + if(item.Name == type) { + return true; + } + } + return false; + } + } +} diff --git a/Zway-Bot/Overtaker.cs b/Zway-Bot/Overtaker.cs new file mode 100644 index 0000000..6a9ac68 --- /dev/null +++ b/Zway-Bot/Overtaker.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using BlubbFish.IoT.Zway; +using BlubbFish.IoT.Zway.Events; +using BlubbFish.IoT.Zway.Interfaces; +using BlubbFish.Utils; + +namespace ZwayBot { + internal class Overtaker { + private ZwayController zw; + private InIReader ini; + private Dictionary> events = new Dictionary>(); + + public delegate void OvertakeValue(Object sender, OvertakerEvent e); + public event OvertakeValue Update; + + public Overtaker(ZwayController zw) { + this.zw = zw; + this.ini = InIReader.GetInstance("overtaker.ini"); + this.ParseIni(); + } + + private void ParseIni() { + foreach (String item in this.ini.GetSections()) { + String from = this.ini.GetValue(item, "from"); + String[] source = from.Split(':'); + this.events.Add(source[0], this.ini.GetSection(item)); + String[] addr = source[0].Split('-'); + ICommandClass c; + if (addr.Length == 3 || addr.Length == 4) { + Int32 deviceid = Int32.Parse(addr[0]); + Int32 instanceid = Int32.Parse(addr[1]); + Int32 classid = Int32.Parse(addr[2]); + if (addr.Length == 4) { + Int32 subcid = Int32.Parse(addr[3]); + c = this.zw.GetCommandClassSub(deviceid, instanceid, classid, subcid); + } else { + c = this.zw.GetCommandClass(deviceid, instanceid, classid); + } + if (c != null) { + c.Polling = true; + c.Update += this.ChangedEvent; + } + } + } + } + + private void ChangedEvent(Object sender, DeviceUpdateEvent e) { + if(Helper.HasInterface(sender, "ICommandClass")) { + if (this.events.ContainsKey(((ICommandClass)sender).Id)) { + this.SetValues(sender, ((ICommandClass)sender).Id, this.events[((ICommandClass)sender).Id]); + } + } + } + + private void SetValues(Object sender, String name, Dictionary dictionary) { + String from = dictionary["from"]; + String[] source = from.Split(':'); + if (source.Length != 2) { + return; + } + String source_value; + if (Helper.HasProperty(sender, source[1])) { + source_value = Helper.GetProperty(sender, source[1]).ToString(); + } else { + return; + } + if(dictionary.ContainsKey("convert")) { + foreach (String tuple in dictionary["convert"].Split(';')) { + String[] item = tuple.Split('-'); + if(source_value == item[0]) { + source_value = item[1]; + } + } + } + foreach (String to in dictionary["to"].Split(';')) { + String[] target = to.Split(':'); + if(target.Length == 2) { + String[] addr = target[0].Split('-'); + if (addr.Length == 3 || addr.Length == 4) { + Int32 deviceid = Int32.Parse(addr[0]); + Int32 instanceid = Int32.Parse(addr[1]); + Int32 classid = Int32.Parse(addr[2]); + ICommandClass c; + if (addr.Length == 4) { + Int32 subcid = Int32.Parse(addr[3]); + c = this.zw.GetCommandClassSub(deviceid, instanceid, classid, subcid); + } else { + c = this.zw.GetCommandClass(deviceid, instanceid, classid); + } + if (c != null) { + String target_value; + if (Helper.HasProperty(c, target[1])) { + target_value = Helper.GetProperty(c, target[1]).ToString(); + if (target_value != source_value) { + Helper.SetProperty(c, target[1], source_value); + this.Update?.Invoke(this, new OvertakerEvent(target[0], target[1], source_value)); + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Zway-Bot/Program.cs b/Zway-Bot/Program.cs new file mode 100644 index 0000000..8456c6d --- /dev/null +++ b/Zway-Bot/Program.cs @@ -0,0 +1,39 @@ +using System; +using BlubbFish.IoT.Zway; +using BlubbFish.Utils; + +namespace ZwayBot { + class Program { + static void Main(String[] args) { + ZwayController zw = new ZwayController(InIReader.GetInstance("settings.ini").GetSection("zway"), InIReader.GetInstance("names.ini").GetSection("names"), false); + zw.Update += ZwayDataUpate; + CronJob cron = new CronJob(zw); + cron.Update += Cron_Update; + Overtaker over = new Overtaker(zw); + over.Update += Over_Update; + while (true) { + System.Threading.Thread.Sleep(100); + if (Console.KeyAvailable) { + ConsoleKeyInfo key = Console.ReadKey(false); + if (key.Key == ConsoleKey.Escape) { + break; + } + } + } + cron.Dispose(); + zw.Dispose(); + } + + private static void Over_Update(Object sender, OvertakerEvent e) { + Console.WriteLine("OVERTAKE: " + e.Address + " set " + e.Property + " to " + e.Value); + } + + private static void Cron_Update(Object sender, CronEvent e) { + Console.WriteLine("CRON: " + e.Address + " set " + e.Property + " to " + e.Value); + } + + private static void ZwayDataUpate(Object sender, BlubbFish.IoT.Zway.Events.DeviceUpdateEvent e) { + Console.WriteLine("-> ZW [" + e.UpdateTime + "]: " + sender.ToString()); + } + } +} diff --git a/Zway-Bot/Properties/AssemblyInfo.cs b/Zway-Bot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c223518 --- /dev/null +++ b/Zway-Bot/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("Zway-Bot")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Zway-Bot")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[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("aef8059f-8aef-45c7-85c4-318c8f797900")] + +// 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, +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Zway-Bot/Zway-Bot.csproj b/Zway-Bot/Zway-Bot.csproj new file mode 100644 index 0000000..50ec038 --- /dev/null +++ b/Zway-Bot/Zway-Bot.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {AEF8059F-8AEF-45C7-85C4-318C8F797900} + Exe + ZwayBot + Zway-Bot + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + {fac8ce64-bf13-4ece-8097-aeb5dd060098} + Utils + + + {166258ed-cb3d-43f5-8e8d-3a993b64d022} + Zway + + + + \ No newline at end of file