Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70bcf69b1b | ||
|
|
fc108773d9 | ||
|
|
92a6ba2b64 | ||
|
|
57daf12ed4 | ||
|
|
cabcce03b3 | ||
|
|
a9c77c6614 | ||
|
|
1c84703ac6 | ||
|
|
25e1e5165d | ||
|
|
a76234f43d | ||
|
|
a3afadd5f5 | ||
|
|
002b3cf338 | ||
|
|
a7895ef59a | ||
|
|
852a096f1d | ||
|
|
69030796f5 | ||
|
|
19d92c8cac | ||
|
|
c792ea20ad | ||
|
|
611205e78f | ||
|
|
4be8bb72f4 | ||
|
|
76cb025606 | ||
|
|
aa84aa0539 | ||
|
|
04f43b95ff | ||
|
|
a1009b5293 | ||
|
|
91a05f882e | ||
|
|
74d270b6d2 | ||
|
|
200420c9b0 | ||
|
|
acf5cea593 | ||
|
|
307f03c7c0 | ||
|
|
32b4daf3fb | ||
|
|
55e88f7b45 | ||
|
|
8baef3b2b5 | ||
|
|
80baca45e5 | ||
|
|
75eac65510 | ||
|
|
44b8daa325 | ||
|
|
2cd2c7c02e | ||
|
|
33b1b6527c | ||
|
|
9e60264d7d |
+29
-5
@@ -11,7 +11,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "..\Utils\Utils\Uti
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils-IoT", "..\Utils\IoT\Utils-IoT.csproj", "{B870E4D5-6806-4A0B-B233-8907EEDC5AFC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConnectorDataMosquitto", "..\Utils\IoT\Connector\Data\Mosquitto\ConnectorDataMosquitto.csproj", "{39235FAD-BA9D-4B51-82FC-6969967BEAE9}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConnectorDataMqtt", "..\Utils\IoT\Connector\Data\Mqtt\ConnectorDataMqtt.csproj", "{EE6C8F68-ED46-4C1C-ABDD-CFCDF75104F2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iot-Interfaces", "..\Utils\IoT\Interfaces\Iot-Interfaces.csproj", "{4DAADA29-C600-4CF3-8AD3-9C97C8D7F632}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "litjson_4.7.1", "..\Librarys\litjson\litjson\litjson_4.7.1.csproj", "{91A14CD2-2940-4500-8193-56D37EDDDBAA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M2Mqtt_4.7.1", "..\Librarys\mqtt\M2Mqtt\M2Mqtt_4.7.1.csproj", "{A11AEF5A-B246-4FE8-8330-06DB73CC8074}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot-Utils", "..\Utils\Bot-Utils\Bot-Utils.csproj", "{BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -35,10 +43,26 @@ Global
|
||||
{B870E4D5-6806-4A0B-B233-8907EEDC5AFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B870E4D5-6806-4A0B-B233-8907EEDC5AFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B870E4D5-6806-4A0B-B233-8907EEDC5AFC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{39235FAD-BA9D-4B51-82FC-6969967BEAE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{39235FAD-BA9D-4B51-82FC-6969967BEAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{39235FAD-BA9D-4B51-82FC-6969967BEAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{39235FAD-BA9D-4B51-82FC-6969967BEAE9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EE6C8F68-ED46-4C1C-ABDD-CFCDF75104F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE6C8F68-ED46-4C1C-ABDD-CFCDF75104F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE6C8F68-ED46-4C1C-ABDD-CFCDF75104F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EE6C8F68-ED46-4C1C-ABDD-CFCDF75104F2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4DAADA29-C600-4CF3-8AD3-9C97C8D7F632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DAADA29-C600-4CF3-8AD3-9C97C8D7F632}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DAADA29-C600-4CF3-8AD3-9C97C8D7F632}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DAADA29-C600-4CF3-8AD3-9C97C8D7F632}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{91A14CD2-2940-4500-8193-56D37EDDDBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{91A14CD2-2940-4500-8193-56D37EDDDBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{91A14CD2-2940-4500-8193-56D37EDDDBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{91A14CD2-2940-4500-8193-56D37EDDDBAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A11AEF5A-B246-4FE8-8330-06DB73CC8074}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB7BFCB5-3DB0-49E1-802A-3CE3EECC59F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ZwayBot {
|
||||
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";
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
public class Flex4gridEvent : ModulEventArgs {
|
||||
public Flex4gridEvent() {
|
||||
}
|
||||
public Flex4gridEvent(String topic, String text) {
|
||||
this.Address = topic;
|
||||
this.Value = text;
|
||||
this.Source = "Flex4Grid";
|
||||
}
|
||||
public override String ToString() {
|
||||
return this.Source + ": on " + this.Address + " set " + this.Value;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
internal static void WriteError(String text) {
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine("ERROR: " + text);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.Utils;
|
||||
|
||||
namespace ZwayBot {
|
||||
abstract class AModul {
|
||||
protected ZwayController zw;
|
||||
protected InIReader ini;
|
||||
|
||||
public AModul(ZwayController zway, InIReader settings) {
|
||||
this.zw = zway;
|
||||
this.ini = settings;
|
||||
}
|
||||
|
||||
public delegate void ModulEvent(Object sender, ModulEventArgs e);
|
||||
public abstract event ModulEvent Update;
|
||||
|
||||
public abstract void Dispose();
|
||||
public virtual void Interconnect(Dictionary<String, AModul> moduls) {
|
||||
|
||||
}
|
||||
|
||||
public virtual void SetInterconnection(String param, Action hook) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-35
@@ -6,16 +6,20 @@ using System.Threading;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Interfaces;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Bots.Moduls;
|
||||
using BlubbFish.Utils.IoT.Bots.Interfaces;
|
||||
using BlubbFish.Utils.IoT.Bots.Events;
|
||||
using BlubbFish.Utils.IoT.Bots;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
internal class CronJob : AModul, IDisposable {
|
||||
internal class CronJob : AModul<ZwayController>, IDisposable, IForceLoad {
|
||||
private DateTime crontime;
|
||||
private Thread thread;
|
||||
private List<Tuple<String, Action>> internalCron = new List<Tuple<String, Action>>();
|
||||
private readonly List<Tuple<String, Action<Object>, Object>> internalCron = new List<Tuple<String, Action<Object>, Object>>();
|
||||
|
||||
public override event ModulEvent Update;
|
||||
|
||||
private Dictionary<String, String> cron_named = new Dictionary<String, String> {
|
||||
private readonly Dictionary<String, String> cron_named = new Dictionary<String, String> {
|
||||
{ "@yearly", "0 0 1 1 *" },
|
||||
{ "@annually", "0 0 1 1 *" },
|
||||
{ "@monthly", "0 0 1 * *" },
|
||||
@@ -35,14 +39,16 @@ namespace ZwayBot.Moduls {
|
||||
while (true) {
|
||||
if(this.crontime.Minute != DateTime.Now.Minute) {
|
||||
this.crontime = DateTime.Now;
|
||||
foreach (String item in this.ini.GetSections()) {
|
||||
if(this.ParseCronString(this.ini.GetValue(item, "cron"))) {
|
||||
this.SetValues(this.ini.GetValue(item, "set"));
|
||||
if (this.config.Count != 0) {
|
||||
foreach (KeyValuePair<String, Dictionary<String, String>> item in this.config) {
|
||||
if (item.Value.ContainsKey("cron") && item.Value.ContainsKey("set") && this.ParseCronString(item.Value["cron"])) {
|
||||
this.SetValues(item.Value["set"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Tuple<String, Action> item in this.internalCron) {
|
||||
foreach (Tuple<String, Action<Object>, Object> item in this.internalCron) {
|
||||
if(this.ParseCronString(item.Item1)) {
|
||||
item.Item2?.Invoke();
|
||||
item.Item2?.Invoke(item.Item3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,24 +60,12 @@ namespace ZwayBot.Moduls {
|
||||
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]));
|
||||
}
|
||||
ACommandClass c = this.library.GetCommandClass(items[0]);
|
||||
if (c != null && values.Length == 2) {
|
||||
if (c.HasProperty(values[0])) {
|
||||
c.SetProperty(values[0], values[1]);
|
||||
this.Update?.Invoke(this, new CronEvent(items[0], values[0], values[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +112,7 @@ namespace ZwayBot.Moduls {
|
||||
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("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("*")) {
|
||||
@@ -162,10 +156,12 @@ namespace ZwayBot.Moduls {
|
||||
return ret.ContainsKey(Int32.Parse(date));
|
||||
}
|
||||
|
||||
public override void SetInterconnection(String cron, Action hook) {
|
||||
this.internalCron.Add(new Tuple<String, Action>(cron, hook));
|
||||
public override void SetInterconnection(String cron, Action<Object> hook, Object data) {
|
||||
this.internalCron.Add(new Tuple<String, Action<Object>, Object>(cron, hook, data));
|
||||
}
|
||||
|
||||
protected override void UpdateConfig() { }
|
||||
|
||||
#region IDisposable Support
|
||||
private Boolean disposedValue = false;
|
||||
|
||||
@@ -173,19 +169,13 @@ namespace ZwayBot.Moduls {
|
||||
if (!this.disposedValue) {
|
||||
if (disposing) {
|
||||
this.thread.Abort();
|
||||
while(this.thread.ThreadState != ThreadState.Aborted) { Thread.Sleep(100); }
|
||||
while(this.thread.ThreadState == ThreadState.Running) { Thread.Sleep(100); }
|
||||
}
|
||||
|
||||
this.thread = null;
|
||||
|
||||
this.disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~CronJob() {
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Devices;
|
||||
using BlubbFish.IoT.Zway.Devices.CommandClasses;
|
||||
using BlubbFish.IoT.Zway.Devices.CommandClasses.CommandClassSubs;
|
||||
using BlubbFish.IoT.Zway.Events;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Connector;
|
||||
using LitJson;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
class Flex4Grid : AModul, IDisposable {
|
||||
private ADataBackend mqtt;
|
||||
private String household = "";
|
||||
private Object requestalivelock = new Object();
|
||||
|
||||
public override event ModulEvent Update;
|
||||
|
||||
public Flex4Grid(ZwayController zway, InIReader settings) : base(zway, settings) {
|
||||
this.mqtt = ADataBackend.GetInstance(this.ini.GetSection("settings"));
|
||||
this.ParseIni();
|
||||
this.mqtt.MessageIncomming += this.Mqtt_MessageIncomming;
|
||||
}
|
||||
|
||||
private void ParseIni() {
|
||||
if (this.ini.GetValue("zway", "devices") != null) {
|
||||
foreach (String device in this.ini.GetValue("zway", "devices").Split(',')) {
|
||||
Int32 deviceid = Int32.Parse(device);
|
||||
if (this.zw.Devices.ContainsKey(deviceid) && this.zw.Devices[deviceid].Instances.ContainsKey(0)) {
|
||||
this.zw.Devices[deviceid].Instances[0].Update += this.DeviceUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this.ini.GetValue("f4g", "household") != null) {
|
||||
this.household = this.ini.GetValue("f4g", "household");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeviceUpdate(Object sender, DeviceUpdateEvent e) {
|
||||
Instance instance = (Instance)sender;
|
||||
if(e.Parent.GetType() == typeof(Switchbinary)) {
|
||||
if (instance.CommandClasses.ContainsKey(37)) { //SwitchBinary
|
||||
String topic = "/flex4grid/v1/households/" + this.household + "/device/state";
|
||||
String text = JsonMapper.ToJson(new Dictionary<String, String>() {
|
||||
{ "status", ((Switchbinary)instance.CommandClasses[37]).Level?"active":"idle" },
|
||||
{ "timestamp", DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff'Z'") },
|
||||
{ "type", ((Switchbinary)instance.CommandClasses[37]).Name },
|
||||
{ "id", instance.DeviceId.ToString() }
|
||||
}).ToString();
|
||||
this.mqtt.Send(topic, text);
|
||||
this.Update?.Invoke(this, new Flex4gridEvent(topic, text));
|
||||
}
|
||||
} else if(e.Parent.GetType() == typeof(Sensormultilevelsub) || e.Parent.GetType() == typeof(Metersub)) {
|
||||
if (instance.CommandClasses.ContainsKey(49) && ((Sensormultilevel)instance.CommandClasses[49]).Sub.ContainsKey(4) && //SensorMultilevel
|
||||
instance.CommandClasses.ContainsKey(50) && ((Meter)instance.CommandClasses[50]).Sub.ContainsKey(0)) { //Meter
|
||||
String topic = "/flex4grid/v1/households/" + this.household + "/device/consumption";
|
||||
String text = JsonMapper.ToJson(new Dictionary<String, String>() {
|
||||
{ "timestamp", DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff'Z'") },
|
||||
{ "id", instance.DeviceId.ToString() },
|
||||
{ "power", Math.Round(((Sensormultilevelsub)((Sensormultilevel)instance.CommandClasses[49]).Sub[4]).Level).ToString("F0") },
|
||||
{ "energyCumul", ((Metersub)((Meter)instance.CommandClasses[50]).Sub[0]).Level.ToString() }
|
||||
}).ToString();
|
||||
this.mqtt.Send(topic, text);
|
||||
this.Update?.Invoke(this, new Flex4gridEvent(topic, text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Mqtt_MessageIncomming(Object sender, MqttEventArgs e) {
|
||||
JsonData message;
|
||||
String topic = e.Topic;
|
||||
try {
|
||||
if (e.Message.StartsWith("{")) {
|
||||
message = JsonMapper.ToObject(e.Message);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
if(topic == "/flex4grid/v1/households/" + this.household + "/device/actuate") {
|
||||
if(message.Keys.Contains("deviceId") && message.Keys.Contains("command")) {
|
||||
String device = message["deviceId"].ToString();
|
||||
if(this.ini.GetValue("zway", "devices") != null) {
|
||||
foreach (String item in this.ini.GetValue("zway", "devices").Split(',')) {
|
||||
if (item == device) {
|
||||
Int32 deviceid = Int32.Parse(device);
|
||||
if (this.zw.Devices.ContainsKey(deviceid) && this.zw.Devices[deviceid].Instances.ContainsKey(0) && this.zw.Devices[deviceid].Instances[0].CommandClasses.ContainsKey(37)) {
|
||||
((Switchbinary)this.zw.Devices[deviceid].Instances[0].CommandClasses[37]).Level = message["command"].ToString() == "ON";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (topic == "/flex4grid/v1/households/" + this.household + "/devices/state/request") {
|
||||
if (this.ini.GetValue("zway", "devices") != null) {
|
||||
Dictionary<String, String> response = new Dictionary<String, String>();
|
||||
foreach (String device in this.ini.GetValue("zway", "devices").Split(',')) {
|
||||
Int32 deviceid = Int32.Parse(device);
|
||||
if (this.zw.Devices.ContainsKey(deviceid) && this.zw.Devices[deviceid].Instances.ContainsKey(0)) {
|
||||
if (this.zw.Devices[deviceid].Instances[0].CommandClasses.ContainsKey(37)) {
|
||||
Switchbinary sw = ((Switchbinary)this.zw.Devices[deviceid].Instances[0].CommandClasses[37]);
|
||||
response.Add(sw.DeviceId.ToString(), sw.Level ? "active" : "idle");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(response.Count != 0) {
|
||||
response.Add("timestamp", DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ffffff'Z'"));
|
||||
String rtopic = "/flex4grid/v1/households/" + this.household + "/devices/state/response";
|
||||
String rtext = JsonMapper.ToJson(response).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestAlive() {
|
||||
String raspi = this.ini.GetValue("f4g", "raspi");
|
||||
lock (this.requestalivelock) {
|
||||
String req = "https://wiki.flex4grid.eu/rupdate/rupdate_general.yml.gpg.asc?gw=" + raspi;
|
||||
HttpWebRequest request = WebRequest.CreateHttp(req);
|
||||
try {
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
|
||||
this.Update?.Invoke(this, new Flex4gridEvent(req, response.StatusCode.ToString()));
|
||||
}
|
||||
} catch (Exception) { }
|
||||
}
|
||||
lock (this.requestalivelock) {
|
||||
String req = "https://wiki.flex4grid.eu/rupdate/rupdate_general.yml?gw=" + raspi;
|
||||
HttpWebRequest request = WebRequest.CreateHttp(req);
|
||||
try {
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
|
||||
this.Update?.Invoke(this, new Flex4gridEvent(req, response.StatusCode.ToString()));
|
||||
}
|
||||
} catch (Exception) { }
|
||||
}
|
||||
lock (this.requestalivelock) {
|
||||
String req = "http://swb.pcs.flex4grid.eu/gateway/" + raspi;
|
||||
HttpWebRequest request = WebRequest.CreateHttp(req);
|
||||
try {
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
|
||||
this.Update?.Invoke(this, new Flex4gridEvent(req, response.StatusCode.ToString()));
|
||||
}
|
||||
} catch (Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
public override void Interconnect(Dictionary<String, AModul> moduls) {
|
||||
if (this.ini.GetValue("f4g", "raspi") != null) {
|
||||
foreach (KeyValuePair<String, AModul> item in moduls) {
|
||||
if (item.Value is CronJob) {
|
||||
item.Value.SetInterconnection("10,40 * * * *", this.RequestAlive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private Boolean disposedValue = false;
|
||||
|
||||
protected virtual void Dispose(Boolean disposing) {
|
||||
if (!this.disposedValue) {
|
||||
if (disposing) {
|
||||
this.mqtt.Dispose();
|
||||
}
|
||||
this.disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Flex4Grid() {
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Events;
|
||||
using BlubbFish.IoT.Zway.Interfaces;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Bots;
|
||||
using BlubbFish.Utils.IoT.Bots.Events;
|
||||
using BlubbFish.Utils.IoT.Bots.Moduls;
|
||||
using BlubbFish.Utils.IoT.Connector;
|
||||
using BlubbFish.Utils.IoT.Events;
|
||||
using BlubbFish.Utils.IoT.Interfaces;
|
||||
using LitJson;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
class Mqtt : Mqtt<ZwayController> {
|
||||
public override event ModulEvent Update;
|
||||
|
||||
public Mqtt(ZwayController zwave, InIReader settings) : base(zwave, settings) { }
|
||||
|
||||
#region Mqtt
|
||||
protected override void Connect() {
|
||||
this.mqtt = ABackend.GetInstance(this.config["settings"], ABackend.BackendType.Data);
|
||||
this.mqtt.MessageIncomming += this.EventInput;
|
||||
this.library.Update += this.EventOutput;
|
||||
}
|
||||
|
||||
protected override void Disconnect() {
|
||||
if (this.mqtt != null) {
|
||||
this.mqtt.MessageIncomming -= this.EventInput;
|
||||
}
|
||||
this.library.Update -= this.EventOutput;
|
||||
if (this.mqtt != null) {
|
||||
this.mqtt.Dispose();
|
||||
}
|
||||
this.mqtt = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
protected virtual void EventOutput(Object sender, DeviceUpdateEvent e) {
|
||||
String topic = "";
|
||||
String data = "";
|
||||
if (e.Parent.GetType().HasInterface(typeof(IMqtt))) {
|
||||
IMqtt sensor = (IMqtt)e.Parent;
|
||||
topic = "zwavebot/devices/" + sensor.MqttTopic();
|
||||
data = sensor.ToJson();
|
||||
}
|
||||
if (topic != "" && data != "") {
|
||||
((ADataBackend)this.mqtt).Send(topic, data);
|
||||
this.Update?.Invoke(this, new MqttEvent(topic, data));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void EventInput(Object sender, BackendEvent e) {
|
||||
if (e.From.ToString().StartsWith("zwavebot/devices/") && (e.From.ToString().EndsWith("/set") || e.From.ToString().EndsWith("/get"))) {
|
||||
Match m = new Regex("^zwavebot/devices/(\\d+)/(\\d+)/(\\d+)/[gs]et$|^zwavebot/devices/(\\d+)/(\\d+)/(\\d+)/(\\d+)/[gs]et$").Match(e.From.ToString());
|
||||
String id = "";
|
||||
if (m.Groups[1].Success) {
|
||||
id = m.Groups[1].Value + "-" + m.Groups[2].Value + "-" + m.Groups[3].Value;
|
||||
}
|
||||
if (m.Groups[4].Success) {
|
||||
id = m.Groups[4].Value + "-" + m.Groups[5].Value + "-" + m.Groups[6].Value + "-" + m.Groups[7].Value;
|
||||
}
|
||||
ACommandClass c = this.library.GetCommandClass(id);
|
||||
if (c == null) {
|
||||
return;
|
||||
}
|
||||
if (e.From.ToString().EndsWith("/set")) {
|
||||
try {
|
||||
JsonData a = JsonMapper.ToObject(e.Message);
|
||||
foreach (String item in a.Keys) {
|
||||
String key = item.ToUpperLower();
|
||||
if (c.HasProperty(key)) {
|
||||
c.SetProperty(key, a[item].ToString());
|
||||
this.Update?.Invoke(this, new MqttEvent(c.Id, a[item].ToString()));
|
||||
}
|
||||
}
|
||||
} catch (Exception) { }
|
||||
} else if (e.From.ToString().EndsWith("/get")) {
|
||||
c.PollOnce = true;
|
||||
((ADataBackend)this.mqtt).Send("zwavebot/devices/" + c.MqttTopic(), c.ToJson());
|
||||
this.Update?.Invoke(this, new MqttEvent(e.From.ToString(), "Dataget"));
|
||||
}
|
||||
}
|
||||
Tuple<Boolean, MqttEvent> cs = this.ChangeConfig(e, "zwavebot/config/");
|
||||
if (cs.Item1) {
|
||||
this.Update?.Invoke(this, cs.Item2);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Events;
|
||||
using BlubbFish.IoT.Zway.Interfaces;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Bots;
|
||||
using BlubbFish.Utils.IoT.Bots.Events;
|
||||
using BlubbFish.Utils.IoT.Bots.Moduls;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
internal class Overtaker : AModul, IDisposable {
|
||||
private Dictionary<String, Dictionary<String, String>> events = new Dictionary<String, Dictionary<String, String>>();
|
||||
internal class Overtaker : AModul<ZwayController>, IDisposable {
|
||||
private readonly Dictionary<String, Dictionary<String, String>> events = new Dictionary<String, Dictionary<String, String>>();
|
||||
|
||||
public override event ModulEvent Update;
|
||||
|
||||
@@ -16,22 +19,12 @@ namespace ZwayBot.Moduls {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
foreach (KeyValuePair<String, Dictionary<String, String>> item in this.config) {
|
||||
if (item.Value.ContainsKey("from")) {
|
||||
String from = item.Value["from"];
|
||||
String[] source = from.Split(':');
|
||||
this.events.Add(source[0], item.Value);
|
||||
ACommandClass c = this.library.GetCommandClass(source[0]);
|
||||
if (c != null) {
|
||||
c.Polling = true;
|
||||
c.Update += this.ChangedEvent;
|
||||
@@ -41,9 +34,9 @@ namespace ZwayBot.Moduls {
|
||||
}
|
||||
|
||||
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]);
|
||||
if(sender.GetType().HasAbstract(typeof(ACommandClass))) {
|
||||
if (this.events.ContainsKey(((ACommandClass)sender).Id)) {
|
||||
this.SetValues(sender, ((ACommandClass)sender).Id, this.events[((ACommandClass)sender).Id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,8 +48,8 @@ namespace ZwayBot.Moduls {
|
||||
return;
|
||||
}
|
||||
String source_value;
|
||||
if (Helper.HasProperty(sender, source[1])) {
|
||||
source_value = Helper.GetProperty(sender, source[1]).ToString();
|
||||
if (sender.HasProperty(source[1])) {
|
||||
source_value = sender.GetProperty(source[1]).ToString();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -68,27 +61,15 @@ namespace ZwayBot.Moduls {
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (dictionary.ContainsKey("to")) {
|
||||
foreach (String to in dictionary["to"].Split(';')) {
|
||||
String[] target = to.Split(':');
|
||||
if (target.Length == 2) {
|
||||
ACommandClass c = this.library.GetCommandClass(target[0]);
|
||||
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);
|
||||
if (c.HasProperty(target[1])) {
|
||||
if (c.GetProperty(target[1]).ToString() != source_value) {
|
||||
c.SetProperty(target[1], source_value);
|
||||
this.Update?.Invoke(this, new OvertakerEvent(target[0], target[1], source_value));
|
||||
}
|
||||
}
|
||||
@@ -98,6 +79,8 @@ namespace ZwayBot.Moduls {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateConfig() { }
|
||||
|
||||
#region IDisposable Support
|
||||
private Boolean disposedValue = false;
|
||||
|
||||
@@ -109,10 +92,6 @@ namespace ZwayBot.Moduls {
|
||||
}
|
||||
}
|
||||
|
||||
~Overtaker() {
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Events;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Bots;
|
||||
using BlubbFish.Utils.IoT.Bots.Events;
|
||||
using BlubbFish.Utils.IoT.Connector;
|
||||
using BlubbFish.Utils.IoT.Events;
|
||||
using BlubbFish.Utils.IoT.Interfaces;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
class Senml : Mqtt {
|
||||
public override event ModulEvent Update;
|
||||
private readonly String SenmlGuid;
|
||||
|
||||
public Senml(ZwayController zway, InIReader settings) : base(zway, settings) {
|
||||
if (this.config.ContainsKey("settings")) {
|
||||
this.SenmlGuid = this.config["settings"]["guid"];
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
protected override void EventOutput(Object sender, DeviceUpdateEvent e) {
|
||||
String topic = "";
|
||||
String data = "";
|
||||
if (e.Parent.GetType().HasInterface(typeof(ISenml))) {
|
||||
ISenml sensor = (ISenml)e.Parent;
|
||||
topic = sensor.SenmlTopic() + this.SenmlGuid+ "/senml";
|
||||
data = sensor.ToSenml();
|
||||
}
|
||||
if (topic != "" && data != null && data != "") {
|
||||
((ADataBackend)this.mqtt).Send(topic, data);
|
||||
this.Update?.Invoke(this, new SenmlEvent(topic, data));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void EventInput(Object sender, BackendEvent e) { }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.IoT.Zway.Devices;
|
||||
using BlubbFish.IoT.Zway.Interfaces;
|
||||
using BlubbFish.Utils;
|
||||
using BlubbFish.Utils.IoT.Bots.Events;
|
||||
using BlubbFish.Utils.IoT.Bots.Interfaces;
|
||||
using BlubbFish.Utils.IoT.Bots.Moduls;
|
||||
|
||||
namespace ZwayBot.Moduls {
|
||||
class Statuspolling : AModul<ZwayController>, IDisposable, IForceLoad {
|
||||
public override event ModulEvent Update;
|
||||
|
||||
public Statuspolling(ZwayController zway, InIReader settings) : base(zway, settings) { }
|
||||
|
||||
|
||||
public override void Interconnect(Dictionary<String, Object> moduls) {
|
||||
foreach (KeyValuePair<String, Object> item in moduls) {
|
||||
if (item.Value is CronJob) {
|
||||
((AModul<ZwayController>)item.Value).SetInterconnection("0 0 * * *", new Action<Object>(this.PollAll), null);
|
||||
if (this.config.Count != 0) {
|
||||
foreach (KeyValuePair<String, Dictionary<String, String>> section in this.config) {
|
||||
if (section.Value.ContainsKey("cron") && section.Value.ContainsKey("devices")) {
|
||||
((AModul<ZwayController>)item.Value).SetInterconnection(section.Value["cron"], new Action<Object>(this.PollSpecific), section.Value["devices"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PollSpecific(Object obj) {
|
||||
String devices = (String)obj;
|
||||
foreach (String item in devices.Split(',')) {
|
||||
ACommandClass c = this.library.GetCommandClass(item);
|
||||
c.PollOnce = true;
|
||||
this.Update?.Invoke(this, new StatusPollingEvent("Polling", item));
|
||||
}
|
||||
}
|
||||
|
||||
private void PollAll(Object o) {
|
||||
foreach (KeyValuePair<Int32, Device> device in this.library.Devices) {
|
||||
foreach (KeyValuePair<Int32, Instance> instance in device.Value.Instances) {
|
||||
foreach (KeyValuePair<ACommandClass.Classes, ACommandClass> commandclass in instance.Value.CommandClasses) {
|
||||
commandclass.Value.PollOnce = true;
|
||||
if(commandclass.Value.HasSub) {
|
||||
foreach (KeyValuePair<Int32, ACommandClass> subclass in commandclass.Value.Sub) {
|
||||
subclass.Value.PollOnce = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Update?.Invoke(this, new StatusPollingEvent("Polling", "all nodes"));
|
||||
}
|
||||
|
||||
protected override void UpdateConfig() { }
|
||||
|
||||
public override void Dispose() {}
|
||||
}
|
||||
}
|
||||
+15
-67
@@ -1,87 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BlubbFish.IoT.Zway;
|
||||
using BlubbFish.Utils;
|
||||
using ZwayBot.Moduls;
|
||||
using BlubbFish.Utils.IoT.Bots;
|
||||
|
||||
namespace ZwayBot {
|
||||
class Program {
|
||||
class Program : Bot {
|
||||
static void Main(String[] args) => new Program(args);
|
||||
|
||||
private ZwayController zw;
|
||||
private Dictionary<String, AModul> moduls = new Dictionary<String, AModul>();
|
||||
|
||||
public Program(String[] args) {
|
||||
InIReader.SetSearchPath(new List<String>() { "/etc/zwaybot", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\zwaybot" });
|
||||
Dictionary<String, String> names;
|
||||
if(File.Exists("names.ini")) {
|
||||
names = InIReader.GetInstance("names.ini").GetSection("names");
|
||||
if(InIReader.ConfigExist("names")) {
|
||||
names = InIReader.GetInstance("names").GetSection("names");
|
||||
} else {
|
||||
names = new Dictionary<String, String>();
|
||||
}
|
||||
if(!File.Exists("settings.ini")) {
|
||||
if(!InIReader.ConfigExist("settings")) {
|
||||
Helper.WriteError("No settings.ini found. Abord!");
|
||||
return;
|
||||
}
|
||||
this.zw = new ZwayController(InIReader.GetInstance("settings.ini").GetSection("zway"), names, false);
|
||||
this.zw.Update += this.ZwayDataUpate;
|
||||
this.ModulLoader();
|
||||
this.ModulInterconnect();
|
||||
this.ModulEvents();
|
||||
this.logger.SetPath(InIReader.GetInstance("settings").GetValue("logging", "path"));
|
||||
ZwayController zw = new ZwayController(InIReader.GetInstance("settings").GetSection("zway"), names, false);
|
||||
zw.Update += this.ZwayDataUpate;
|
||||
this.ModulLoader<ZwayController>("ZwayBot.Moduls", zw);
|
||||
this.ModulInterconnect<ZwayController>();
|
||||
this.ModulEvents<ZwayController>();
|
||||
this.WaitForShutdown();
|
||||
this.ModulDispose();
|
||||
}
|
||||
|
||||
private void ModulInterconnect() {
|
||||
foreach (KeyValuePair<String, AModul> item in this.moduls) {
|
||||
item.Value.Interconnect(this.moduls);
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForShutdown() {
|
||||
while (true) {
|
||||
System.Threading.Thread.Sleep(100);
|
||||
if (Console.KeyAvailable) {
|
||||
ConsoleKeyInfo key = Console.ReadKey(false);
|
||||
if (key.Key == ConsoleKey.Escape) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ModulDispose() {
|
||||
foreach (KeyValuePair<String, AModul> item in this.moduls) {
|
||||
item.Value.Dispose();
|
||||
Console.WriteLine("Modul entladen: " + item.Key);
|
||||
}
|
||||
this.zw.Dispose();
|
||||
}
|
||||
|
||||
private void ModulEvents() {
|
||||
foreach (KeyValuePair<String, AModul> item in this.moduls) {
|
||||
item.Value.Update += this.ModulUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private void ModulUpdate(Object sender, ModulEventArgs e) {
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
|
||||
private void ModulLoader() {
|
||||
Assembly asm = Assembly.GetExecutingAssembly();
|
||||
foreach (Type item in asm.GetTypes()) {
|
||||
if(item.Namespace == "ZwayBot.Moduls") {
|
||||
Type t = item;
|
||||
String name = t.Name;
|
||||
if (File.Exists(name.ToLower() + ".ini")) {
|
||||
this.moduls.Add(name, (AModul)t.GetConstructor(new Type[] { typeof(ZwayController), typeof(InIReader) }).Invoke(new Object[] { this.zw, InIReader.GetInstance(name.ToLower() + ".ini") }));
|
||||
Console.WriteLine("Load Modul " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.ModulDispose<ZwayController>();
|
||||
zw.Dispose();
|
||||
}
|
||||
|
||||
private void ZwayDataUpate(Object sender, BlubbFish.IoT.Zway.Events.DeviceUpdateEvent e) {
|
||||
Console.WriteLine("-> ZW [" + e.UpdateTime + "]: " + e.Parent.ToString());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Resources;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Allgemeine Informationen über eine Assembly werden über die folgenden
|
||||
@@ -11,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("BlubbFish")]
|
||||
[assembly: AssemblyProduct("Zway-Bot")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017 - 05.06.2018")]
|
||||
[assembly: AssemblyTrademark("BlubbFish")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -28,13 +27,12 @@ using System.Runtime.InteropServices;
|
||||
// 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.1.2.1")]
|
||||
[assembly: AssemblyFileVersion("1.1.2.1")]
|
||||
[assembly: AssemblyVersion("1.6.1")]
|
||||
[assembly: AssemblyFileVersion("1.6.1")]
|
||||
[assembly: NeutralResourcesLanguage("de-DE")]
|
||||
|
||||
// “Internet Of Things” icon by By Michael Wohlwend, US, from thenounproject.com.
|
||||
|
||||
+52
-22
@@ -12,6 +12,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -39,27 +41,13 @@
|
||||
<ApplicationIcon>Resources\Icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="LitJson, Version=0.9.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\LitJson.0.9.0\lib\LitJson.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Events.cs" />
|
||||
<Compile Include="Moduls\AModul.cs" />
|
||||
<None Include="dpkg\preinst" />
|
||||
<None Include="dpkg\postinst" />
|
||||
<Compile Include="Moduls\CronJob.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="Moduls\Flex4Grid.cs" />
|
||||
<Compile Include="Moduls\Mqtt.cs" />
|
||||
<Compile Include="Moduls\Overtaker.cs" />
|
||||
<Compile Include="Moduls\Senml.cs" />
|
||||
<Compile Include="Moduls\StatusPolling.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
@@ -69,9 +57,21 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Utils\IoT\Connector\Data\Mosquitto\ConnectorDataMosquitto.csproj">
|
||||
<Project>{39235fad-ba9d-4b51-82fc-6969967beae9}</Project>
|
||||
<Name>ConnectorDataMosquitto</Name>
|
||||
<ProjectReference Include="..\..\Librarys\litjson\litjson\litjson_4.7.1.csproj">
|
||||
<Project>{91a14cd2-2940-4500-8193-56d37edddbaa}</Project>
|
||||
<Name>litjson_4.7.1</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Utils\Bot-Utils\Bot-Utils.csproj">
|
||||
<Project>{bb7bfcb5-3db0-49e1-802a-3ce3eecc59f9}</Project>
|
||||
<Name>Bot-Utils</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Utils\IoT\Connector\Data\Mqtt\ConnectorDataMqtt.csproj">
|
||||
<Project>{ee6c8f68-ed46-4c1c-abdd-cfcdf75104f2}</Project>
|
||||
<Name>ConnectorDataMqtt</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Utils\IoT\Interfaces\Iot-Interfaces.csproj">
|
||||
<Project>{4daada29-c600-4cf3-8ad3-9c97c8d7f632}</Project>
|
||||
<Name>Iot-Interfaces</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Utils\IoT\Utils-IoT.csproj">
|
||||
<Project>{b870e4d5-6806-4a0b-b233-8907eedc5afc}</Project>
|
||||
@@ -87,9 +87,35 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="config-example\cronjob.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config-example\names.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config-example\senml.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config-example\settings.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config-example\statuspolling.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="config-example\mqtt.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config-example\overtaker.conf.example">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="dpkg\control" />
|
||||
<None Include="dpkg\create-Builds.bat" />
|
||||
<None Include="dpkg\make-deb.sh" />
|
||||
<None Include="dpkg\prerm" />
|
||||
<None Include="dpkg\zwaybot-logrotate" />
|
||||
<None Include="dpkg\zwaybot.service" />
|
||||
<None Include="Resources\Icon.ico" />
|
||||
<Content Include="Resources\icon.svg" />
|
||||
</ItemGroup>
|
||||
@@ -99,5 +125,9 @@
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
[modul]
|
||||
config=public
|
||||
|
||||
[cold]
|
||||
cron=0 23 * * 0-4
|
||||
set=7-0-67-2:Level-19;7-0-112-8:Level-2
|
||||
|
||||
[hot]
|
||||
cron=0 16 * * 1-5
|
||||
set=7-0-67-1:Level-22;7-0-112-8:Level-1
|
||||
@@ -0,0 +1,6 @@
|
||||
[modul]
|
||||
config=private
|
||||
|
||||
[settings]
|
||||
type=mqtt
|
||||
server=localhost
|
||||
@@ -0,0 +1,24 @@
|
||||
[names]
|
||||
2-0-37=Kühlschrank Schalter
|
||||
2-0-49-4=Kühlschrank Leistung
|
||||
2-0-50-0=Kühlschrank Verbrauch [kWh]
|
||||
2-0-50-2=Kühlschrank Verbrauch [Wh]
|
||||
2-0-112-1=Kühlschrank Config (Immer AN)
|
||||
2-0-112-16=Kühlschrank Config (Zurück zu altem Status nach Stromausfall)
|
||||
2-0-112-34=Kühlschrank Config (Reaktion auf Alarm)
|
||||
2-0-112-35=Kühlschrank Config (Antwort auf Alarmmeldung)
|
||||
2-0-112-39=Kühlschrank Config (Alarm Dauer)
|
||||
2-0-112-40=Kühlschrank Config (Sofortiger Bericht über Stromverbrauch)
|
||||
2-0-112-42=Kühlschrank Config (Änderung der Leistung zum Generieren eines Reports)
|
||||
2-0-112-43=Kühlschrank Config (Regelmäßiges Senden eines Reports über die Stromaufnahme)
|
||||
2-0-112-45=Kühlschrank Config (Automatisches Senden eines Reportes über Stromverbrauch bei Verbrauchsänderung)
|
||||
2-0-112-47=Kühlschrank Config (Zeitinterval zum Senden eines Reportes ohne Änderung im Verbrauch)
|
||||
2-0-112-49=Kühlschrank Config (Messen des Eigenstromverbrauches)
|
||||
2-0-112-50=Kühlschrank Config (unterer Leistungsschwellwert)
|
||||
2-0-112-51=Kühlschrank Config (oberer Leistungsschwellwert)
|
||||
2-0-112-52=Kühlschrank Config (Aktion bei Erreichen des definierten Schwellwertes (Parameter 50/51))
|
||||
2-0-112-60=Kühlschrank Config (Leistungsschwellwert für violettes Blinken)
|
||||
2-0-112-61=Kühlschrank Config (LED Ring Farbe im Einschaltzustand)
|
||||
2-0-112-62=Kühlschrank Config (LED Ring Farbe im Ausschaltzustand)
|
||||
2-0-112-63=Kühlschrank Config (LED Ring Farbe bei Z-Wave Alarmmeldungen)
|
||||
2-0-112-70=Kühlschrank Config (Überlastabschaltung)
|
||||
@@ -0,0 +1,12 @@
|
||||
[hot]
|
||||
from=7-0-67-1:Level
|
||||
to=8-0-67-1:Level;9-0-67-1:Level
|
||||
|
||||
[cold]
|
||||
from=7-0-67-2:Level
|
||||
to=8-0-67-11:Level;9-0-67-11:Level
|
||||
|
||||
[mode]
|
||||
from=7-0-112-8:Level
|
||||
to=8-0-64:Level;9-0-64:Level
|
||||
convert=1-1;2-11
|
||||
@@ -0,0 +1,7 @@
|
||||
[modul]
|
||||
config=private
|
||||
|
||||
[settings]
|
||||
type=mqtt
|
||||
server=localhost
|
||||
guid=linksmart-zwaybot
|
||||
@@ -0,0 +1,7 @@
|
||||
[zway]
|
||||
server=localhost
|
||||
user=admin
|
||||
pass=pass
|
||||
|
||||
[logging]
|
||||
path=/var/log/zwaybot.log
|
||||
@@ -0,0 +1,3 @@
|
||||
[temp]
|
||||
cron=30 * * * *
|
||||
devices=7-0-49-1,8-0-49-1,9-0-49-1,10-0-49-1,11-0-49-1
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
[modul]
|
||||
config=public
|
||||
|
||||
[cold]
|
||||
cron=0 23 * * 0-4
|
||||
set=7-0-67-2:Level-19;7-0-112-8:Level-2
|
||||
|
||||
[hot]
|
||||
cron=0 16 * * 1-5
|
||||
set=7-0-67-1:Level-22;7-0-112-8:Level-1
|
||||
@@ -0,0 +1,6 @@
|
||||
[modul]
|
||||
config=private
|
||||
|
||||
[settings]
|
||||
type=mqtt
|
||||
server=localhost
|
||||
@@ -0,0 +1,24 @@
|
||||
[names]
|
||||
2-0-37=Kühlschrank Schalter
|
||||
2-0-49-4=Kühlschrank Leistung
|
||||
2-0-50-0=Kühlschrank Verbrauch [kWh]
|
||||
2-0-50-2=Kühlschrank Verbrauch [Wh]
|
||||
2-0-112-1=Kühlschrank Config (Immer AN)
|
||||
2-0-112-16=Kühlschrank Config (Zurück zu altem Status nach Stromausfall)
|
||||
2-0-112-34=Kühlschrank Config (Reaktion auf Alarm)
|
||||
2-0-112-35=Kühlschrank Config (Antwort auf Alarmmeldung)
|
||||
2-0-112-39=Kühlschrank Config (Alarm Dauer)
|
||||
2-0-112-40=Kühlschrank Config (Sofortiger Bericht über Stromverbrauch)
|
||||
2-0-112-42=Kühlschrank Config (Änderung der Leistung zum Generieren eines Reports)
|
||||
2-0-112-43=Kühlschrank Config (Regelmäßiges Senden eines Reports über die Stromaufnahme)
|
||||
2-0-112-45=Kühlschrank Config (Automatisches Senden eines Reportes über Stromverbrauch bei Verbrauchsänderung)
|
||||
2-0-112-47=Kühlschrank Config (Zeitinterval zum Senden eines Reportes ohne Änderung im Verbrauch)
|
||||
2-0-112-49=Kühlschrank Config (Messen des Eigenstromverbrauches)
|
||||
2-0-112-50=Kühlschrank Config (unterer Leistungsschwellwert)
|
||||
2-0-112-51=Kühlschrank Config (oberer Leistungsschwellwert)
|
||||
2-0-112-52=Kühlschrank Config (Aktion bei Erreichen des definierten Schwellwertes (Parameter 50/51))
|
||||
2-0-112-60=Kühlschrank Config (Leistungsschwellwert für violettes Blinken)
|
||||
2-0-112-61=Kühlschrank Config (LED Ring Farbe im Einschaltzustand)
|
||||
2-0-112-62=Kühlschrank Config (LED Ring Farbe im Ausschaltzustand)
|
||||
2-0-112-63=Kühlschrank Config (LED Ring Farbe bei Z-Wave Alarmmeldungen)
|
||||
2-0-112-70=Kühlschrank Config (Überlastabschaltung)
|
||||
@@ -0,0 +1,12 @@
|
||||
[hot]
|
||||
from=7-0-67-1:Level
|
||||
to=8-0-67-1:Level;9-0-67-1:Level
|
||||
|
||||
[cold]
|
||||
from=7-0-67-2:Level
|
||||
to=8-0-67-11:Level;9-0-67-11:Level
|
||||
|
||||
[mode]
|
||||
from=7-0-112-8:Level
|
||||
to=8-0-64:Level;9-0-64:Level
|
||||
convert=1-1;2-11
|
||||
@@ -0,0 +1,7 @@
|
||||
[modul]
|
||||
config=private
|
||||
|
||||
[settings]
|
||||
type=mqtt
|
||||
server=localhost
|
||||
guid=linksmart-zwaybot
|
||||
@@ -0,0 +1,7 @@
|
||||
[zway]
|
||||
server=localhost
|
||||
user=admin
|
||||
pass=pass
|
||||
|
||||
[logging]
|
||||
path=/var/log/zwaybot.log
|
||||
@@ -0,0 +1,3 @@
|
||||
[temp]
|
||||
cron=30 * * * *
|
||||
devices=7-0-49-1,8-0-49-1,9-0-49-1,10-0-49-1,11-0-49-1
|
||||
@@ -0,0 +1,10 @@
|
||||
Package: zwaybot
|
||||
Version: x.x-x
|
||||
Section: base
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends: mono-complete (>= 5.4.1.6)
|
||||
Maintainer: BlubbFish <dev@blubbfish.net>
|
||||
Description: Zway-Bot
|
||||
Zway-Bot manage a Zwave.me installation
|
||||
so that it has more features
|
||||
@@ -0,0 +1 @@
|
||||
bash.exe -c "./make-deb.sh armhf"
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
HOMEDIR="/home/blubb"
|
||||
ROOT="$HOMEDIR/deb"
|
||||
OUTPUT="../bin/Release"
|
||||
|
||||
EXEC="$ROOT/usr/local/bin/zwaybot"
|
||||
CONFIG="$ROOT/etc/zwaybot"
|
||||
SYSTEMD="$ROOT/etc/systemd/system"
|
||||
LOGROTATE="$ROOT/etc/logrotate.d"
|
||||
|
||||
DEBIAN="$ROOT/DEBIAN"
|
||||
VMAJOR=$(grep -e "^\[assembly: AssemblyVersion(\"" ../Properties/AssemblyInfo.cs | cut -d'"' -f 2 | cut -d'.' -f 1)
|
||||
VMINOR=$(grep -e "^\[assembly: AssemblyVersion(\"" ../Properties/AssemblyInfo.cs | cut -d'"' -f 2 | cut -d'.' -f 2)
|
||||
VBUILD=$(grep -e "^\[assembly: AssemblyVersion(\"" ../Properties/AssemblyInfo.cs | cut -d'"' -f 2 | cut -d'.' -f 3)
|
||||
ARCHT=$1
|
||||
|
||||
mkdir -p $EXEC
|
||||
mkdir -p $CONFIG
|
||||
mkdir -p $DEBIAN
|
||||
mkdir -p $SYSTEMD
|
||||
mkdir -p $LOGROTATE
|
||||
|
||||
cp control $DEBIAN
|
||||
cp preinst $DEBIAN
|
||||
cp postinst $DEBIAN
|
||||
cp prerm $DEBIAN
|
||||
sed -i s/Version:\ x\.x-x/"Version: $VMAJOR.$VMINOR-$VBUILD"/ $DEBIAN/control
|
||||
sed -i s/Architecture:\ any/"Architecture: $ARCHT"/ $DEBIAN/control
|
||||
chmod 755 $DEBIAN -R
|
||||
|
||||
cp zwaybot.service $SYSTEMD
|
||||
chmod 644 $SYSTEMD/zwaybot.service
|
||||
|
||||
cp $OUTPUT/*.exe $EXEC/
|
||||
find $OUTPUT -name \*.dll ! -name Mono.Posix.dll -exec cp {} $EXEC/ \;
|
||||
chmod 644 $EXEC/*
|
||||
chmod 755 $EXEC
|
||||
|
||||
cp $OUTPUT/config-example/* $CONFIG
|
||||
chmod 644 $CONFIG/*
|
||||
chmod 755 $CONFIG
|
||||
|
||||
cp zwaybot-logrotate $LOGROTATE/zwaybot
|
||||
chmod 644 $LOGROTATE/*
|
||||
|
||||
dpkg-deb --build $ROOT
|
||||
mv $HOMEDIR/deb.deb ../../../Builds/"$ARCHT-zwaybot_$VMAJOR.$VMINOR-$VBUILD.deb"
|
||||
rm $HOMEDIR/deb -r
|
||||
@@ -0,0 +1 @@
|
||||
Name "Example1"
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
systemctl daemon-reload
|
||||
if [[ $(systemctl is-active zwaybot || true) == "active" ]]
|
||||
then
|
||||
service zwaybot restart
|
||||
fi
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
useradd -M zwaybot
|
||||
usermod -L zwaybot
|
||||
groupadd zwaybot
|
||||
usermod -G zwaybot,adm zwaybot
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
service zwaybot stop
|
||||
@@ -0,0 +1,10 @@
|
||||
/var/log/zwaybot.log {
|
||||
compress
|
||||
copytruncate
|
||||
daily
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
rotate 4
|
||||
size=10M
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# If you modify this, please also make sure to edit init.sh
|
||||
|
||||
[Unit]
|
||||
Description=Zway-Bot manage a Zwave.me installation so that it has more features
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
User=zwaybot
|
||||
Group=zwaybot
|
||||
WorkingDirectory=/usr/local/bin/zwaybot
|
||||
# ExecStartPre=/bin/rm /var/log/zwaybot.log && /bin/touch /var/log/zwaybot.log && /bin/chown zwaybot:zwaybot /var/log/zwaybot.log && /bin/chmod 644 /var/log/zwaybot.log
|
||||
ExecStart=/usr/bin/mono /usr/local/bin/zwaybot/Zway-Bot.exe
|
||||
KillMode=control-group
|
||||
Restart=on-failure
|
||||
StandardOutput=null
|
||||
StandardError=syslog
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=zwaybot.service
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="LitJson" version="0.9.0" targetFramework="net461" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user