mirror of
https://github.com/excaliburpartners/OmniLinkBridge.git
synced 2025-04-20 05:17:19 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
efa16fb2c3 | ||
![]() |
f297c2fcaa | ||
![]() |
d7eef51adf | ||
![]() |
73f504ca93 | ||
![]() |
800242a87f | ||
![]() |
84b52c8f30 | ||
![]() |
495ce74149 | ||
![]() |
a016e1cd64 | ||
![]() |
1ce5e3dab9 | ||
![]() |
7c24d9046e | ||
![]() |
41330b9bf4 | ||
![]() |
3b86dd6a3a | ||
![]() |
5cd6048dd5 | ||
![]() |
b5298a1e74 | ||
![]() |
e7613741d3 | ||
![]() |
2f652f7c8a | ||
![]() |
a8d965eb04 | ||
![]() |
a248bd4f30 | ||
![]() |
92012fc0b7 | ||
![]() |
37aed0e891 | ||
![]() |
1cf77ba179 | ||
![]() |
85c549d5dd | ||
![]() |
084fb9a473 | ||
![]() |
8e637db459 |
14
Dockerfile
14
Dockerfile
@ -1,14 +1,22 @@
|
||||
FROM mono:latest AS build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y unixodbc
|
||||
|
||||
WORKDIR /build
|
||||
ADD https://dev.mysql.com/get/Downloads/Connector-ODBC/8.0/mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit.tar.gz /build
|
||||
RUN tar zxf mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit.tar.gz && \
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||
tar zxf mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit.tar.gz && \
|
||||
mkdir -p /usr/lib/odbc/ && \
|
||||
cp mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit/lib/* /usr/lib/odbc/ && \
|
||||
mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit/bin/myodbc-installer -d -a -n "MySQL" -t "DRIVER=/usr/lib/odbc/libmyodbc8w.so"
|
||||
mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit/bin/myodbc-installer -d -a -n "MySQL" -t "DRIVER=/usr/lib/odbc/libmyodbc8w.so"; \
|
||||
else \
|
||||
mkdir -p /usr/lib/odbc/ && \
|
||||
touch /etc/odbcinst.ini; \
|
||||
fi
|
||||
|
||||
COPY . .
|
||||
RUN nuget restore /build/OmniLinkBridge.sln
|
||||
@ -28,4 +36,4 @@ EXPOSE 8000/tcp
|
||||
VOLUME /config
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
CMD [ "mono", "OmniLinkBridge.exe", "-i", "-c", "/config/OmniLinkBridge.ini", "-e", "-s", "/config/WebSubscriptions.json" ]
|
||||
CMD [ "mono", "OmniLinkBridge.exe", "-i", "-c", "/config/OmniLinkBridge.ini", "-e", "-s", "/config/WebSubscriptions.json", "-lf", "disable" ]
|
@ -85,9 +85,9 @@ CREATE TABLE IF NOT EXISTS `log_thermostats` (
|
||||
`humidity` smallint(6) NOT NULL,
|
||||
`humidify` smallint(6) NOT NULL,
|
||||
`dehumidify` smallint(6) NOT NULL,
|
||||
`mode` varchar(5) NOT NULL,
|
||||
`mode` varchar(14) NOT NULL,
|
||||
`fan` varchar(5) NOT NULL,
|
||||
`hold` varchar(5) NOT NULL,
|
||||
`hold` varchar(8) NOT NULL,
|
||||
PRIMARY KEY (`log_tstat_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
|
||||
|
||||
|
@ -1,37 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
|
||||
</configSections>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
<log4net>
|
||||
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<param name="ConversionPattern" value="%date %-5level: %message%newline"/>
|
||||
</layout>
|
||||
</appender>
|
||||
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
|
||||
<file value="log.txt"/>
|
||||
<appendToFile value="true"/>
|
||||
<rollingStyle value="Composite"/>
|
||||
<datePattern value="yyyyMMdd"/>
|
||||
<maxSizeRollBackups value="10"/>
|
||||
<maximumFileSize value="5MB"/>
|
||||
<immediateFlush value="true"/>
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
|
||||
</layout>
|
||||
<filter type="log4net.Filter.LevelRangeFilter">
|
||||
<levelMin value="WARN"/>
|
||||
<levelMax value="FATAL"/>
|
||||
</filter>
|
||||
</appender>
|
||||
<root>
|
||||
<level value="ALL"/>
|
||||
<appender-ref ref="TraceAppender"/>
|
||||
<appender-ref ref="RollingFileAppender"/>
|
||||
</root>
|
||||
</log4net>
|
||||
</configuration>
|
||||
|
16
OmniLinkBridge/ControllerEnricher.cs
Normal file
16
OmniLinkBridge/ControllerEnricher.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public class ControllerEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if(Global.controller_id != Guid.Empty)
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
|
||||
"ControllerId", Global.controller_id));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
using log4net;
|
||||
using OmniLinkBridge.Modules;
|
||||
using OmniLinkBridge.Modules;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -9,11 +11,13 @@ namespace OmniLinkBridge
|
||||
{
|
||||
public class CoreServer
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII omnilink;
|
||||
private readonly List<IModule> modules = new List<IModule>();
|
||||
private readonly List<Task> tasks = new List<Task>();
|
||||
private readonly ManualResetEvent quitEvent = new ManualResetEvent(false);
|
||||
private DateTime startTime;
|
||||
|
||||
public CoreServer()
|
||||
{
|
||||
@ -23,11 +27,6 @@ namespace OmniLinkBridge
|
||||
|
||||
private void Server()
|
||||
{
|
||||
Global.running = true;
|
||||
|
||||
log.Debug("Starting up server " +
|
||||
Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
|
||||
// Controller connection
|
||||
modules.Add(omnilink = new OmniLinkII(Global.controller_address, Global.controller_port, Global.controller_key1, Global.controller_key2));
|
||||
|
||||
@ -43,6 +42,14 @@ namespace OmniLinkBridge
|
||||
if(Global.mqtt_enabled)
|
||||
modules.Add(new MQTTModule(omnilink));
|
||||
|
||||
startTime = DateTime.Now;
|
||||
|
||||
Program.ShowSendLogsWarning();
|
||||
|
||||
using (LogContext.PushProperty("Telemetry", "Startup"))
|
||||
log.Information("Started version {Version} in {Environment} on {OperatingSystem} with {Modules}",
|
||||
Assembly.GetExecutingAssembly().GetName().Version, Program.GetEnvironment(), Environment.OSVersion, modules);
|
||||
|
||||
// Startup modules
|
||||
foreach (IModule module in modules)
|
||||
{
|
||||
@ -52,14 +59,11 @@ namespace OmniLinkBridge
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait for all threads to stop
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
quitEvent.WaitOne();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Global.running = false;
|
||||
|
||||
// Shutdown modules
|
||||
foreach (IModule module in modules)
|
||||
module.Shutdown();
|
||||
@ -68,7 +72,12 @@ namespace OmniLinkBridge
|
||||
if (tasks != null)
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
log.Debug("Shutdown completed");
|
||||
using (LogContext.PushProperty("Telemetry", "Shutdown"))
|
||||
log.Information("Shutdown completed with uptime {Uptime}", (DateTime.Now - startTime).ToString());
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
quitEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string Truncate(this string value, int maxLength)
|
||||
{
|
||||
return value?.Length > maxLength ? value.Substring(0, maxLength) : value;
|
||||
}
|
||||
|
||||
public static double ToCelsius(this double f)
|
||||
{
|
||||
// Convert to celsius
|
||||
@ -22,6 +30,10 @@ namespace OmniLinkBridge
|
||||
{
|
||||
return (b & (1 << pos)) != 0;
|
||||
}
|
||||
public static string ToSpaceTitleCase(this string phrase)
|
||||
{
|
||||
return Regex.Replace(phrase, "(\\B[A-Z])", " $1");
|
||||
}
|
||||
|
||||
public static List<int> ParseRanges(this string ranges)
|
||||
{
|
||||
@ -38,5 +50,14 @@ namespace OmniLinkBridge
|
||||
.Select(t => int.Parse(t)).ToList(); // digit to int
|
||||
return RangeNums.Count.Equals(2) ? Enumerable.Range(RangeNums.Min(), (RangeNums.Max() + 1) - RangeNums.Min()).ToList() : RangeNums;
|
||||
}
|
||||
|
||||
public static Guid ComputeGuid(this string data)
|
||||
{
|
||||
using SHA256 hash = SHA256.Create();
|
||||
byte[] bytes = hash.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
byte[] guidBytes = new byte[16];
|
||||
Array.Copy(bytes, guidBytes, 16);
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mail;
|
||||
@ -7,10 +8,10 @@ namespace OmniLinkBridge
|
||||
{
|
||||
public abstract class Global
|
||||
{
|
||||
public static bool running;
|
||||
|
||||
// Config File
|
||||
public static string config_file;
|
||||
public static bool DebugSettings { get; set; }
|
||||
public static bool UseEnvironment { get; set; }
|
||||
public static bool SendLogs { get; set; }
|
||||
public static Guid SessionID { get; } = Guid.NewGuid();
|
||||
|
||||
// HAI / Leviton Omni Controller
|
||||
public static string controller_address;
|
||||
@ -18,6 +19,7 @@ namespace OmniLinkBridge
|
||||
public static string controller_key1;
|
||||
public static string controller_key2;
|
||||
public static string controller_name;
|
||||
public static Guid controller_id;
|
||||
|
||||
// Time Sync
|
||||
public static bool time_sync;
|
||||
@ -33,6 +35,8 @@ namespace OmniLinkBridge
|
||||
public static bool verbose_thermostat;
|
||||
public static bool verbose_unit;
|
||||
public static bool verbose_message;
|
||||
public static bool verbose_lock;
|
||||
public static bool verbose_audio;
|
||||
|
||||
// mySQL Logging
|
||||
public static bool mysql_logging;
|
||||
@ -55,7 +59,12 @@ namespace OmniLinkBridge
|
||||
public static string mqtt_discovery_name_prefix;
|
||||
public static HashSet<int> mqtt_discovery_ignore_zones;
|
||||
public static HashSet<int> mqtt_discovery_ignore_units;
|
||||
public static ConcurrentDictionary<int, MQTT.OverrideArea> mqtt_discovery_override_area;
|
||||
public static ConcurrentDictionary<int, MQTT.OverrideZone> mqtt_discovery_override_zone;
|
||||
public static ConcurrentDictionary<int, MQTT.OverrideUnit> mqtt_discovery_override_unit;
|
||||
public static Type mqtt_discovery_button_type;
|
||||
public static bool mqtt_audio_local_mute;
|
||||
public static bool mqtt_audio_volume_media_player;
|
||||
|
||||
// Notifications
|
||||
public static bool notify_area;
|
||||
|
@ -1,9 +0,0 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Alarm : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
|
||||
//public string code { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
10
OmniLinkBridge/MQTT/AreaCommandCode.cs
Normal file
10
OmniLinkBridge/MQTT/AreaCommandCode.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class AreaCommandCode
|
||||
{
|
||||
public bool Success { get; set; } = true;
|
||||
public string Command { get; set; }
|
||||
public bool Validate { get; set; }
|
||||
public int Code { get; set; }
|
||||
}
|
||||
}
|
7
OmniLinkBridge/MQTT/Availability.cs
Normal file
7
OmniLinkBridge/MQTT/Availability.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Availability
|
||||
{
|
||||
public string topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using OmniLinkBridge.Modules;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Device
|
||||
{
|
||||
public string unique_id { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_topic { get; set; }
|
||||
|
||||
public string availability_topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeviceRegistry device { get; set; } = MQTTModule.MqttDeviceRegistry;
|
||||
}
|
||||
}
|
66
OmniLinkBridge/MQTT/Extensions.cs
Normal file
66
OmniLinkBridge/MQTT/Extensions.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using HAI_Shared;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static AreaCommandCode ToCommandCode(this string payload, bool supportValidate = false)
|
||||
{
|
||||
string[] payloads = payload.Split(',');
|
||||
int code = 0;
|
||||
|
||||
AreaCommandCode ret = new AreaCommandCode()
|
||||
{
|
||||
Command = payloads[0]
|
||||
};
|
||||
|
||||
if (payload.Length == 1)
|
||||
return ret;
|
||||
|
||||
if (payloads.Length == 2)
|
||||
{
|
||||
ret.Success = int.TryParse(payloads[1], out code);
|
||||
}
|
||||
else if (supportValidate && payloads.Length == 3)
|
||||
{
|
||||
// Special case for Home Assistant when code not required
|
||||
if (string.Compare(payloads[1], "validate", true) == 0 &&
|
||||
string.Compare(payloads[2], "None", true) == 0)
|
||||
{
|
||||
ret.Success = true;
|
||||
}
|
||||
else if (string.Compare(payloads[1], "validate", true) == 0)
|
||||
{
|
||||
ret.Validate = true;
|
||||
ret.Success = int.TryParse(payloads[2], out code);
|
||||
}
|
||||
else
|
||||
ret.Success = false;
|
||||
}
|
||||
|
||||
ret.Code = code;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static UnitType ToUnitType(this clsUnit unit)
|
||||
{
|
||||
Global.mqtt_discovery_override_unit.TryGetValue(unit.Number, out OverrideUnit override_unit);
|
||||
|
||||
if (unit.Type == enuOL2UnitType.Output)
|
||||
return UnitType.@switch;
|
||||
|
||||
if (unit.Type == enuOL2UnitType.Flag)
|
||||
{
|
||||
if (override_unit != null && override_unit.type == UnitType.number)
|
||||
return UnitType.number;
|
||||
|
||||
return UnitType.@switch;
|
||||
}
|
||||
|
||||
if (override_unit != null && override_unit.type == UnitType.@switch)
|
||||
return UnitType.@switch;
|
||||
|
||||
return UnitType.light;
|
||||
}
|
||||
}
|
||||
}
|
30
OmniLinkBridge/MQTT/HomeAssistant/Alarm.cs
Normal file
30
OmniLinkBridge/MQTT/HomeAssistant/Alarm.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Alarm : Device
|
||||
{
|
||||
public Alarm(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string command_template { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string code { get; set; }
|
||||
|
||||
public bool code_arm_required { get; set; } = false;
|
||||
|
||||
public bool code_disarm_required { get; set; } = false;
|
||||
|
||||
public bool code_trigger_required { get; set; } = false;
|
||||
|
||||
public List<string> supported_features { get; set; } = new List<string>(new string[] {
|
||||
"arm_home", "arm_away", "arm_night", "arm_vacation" });
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class BinarySensor : Device
|
||||
{
|
||||
public BinarySensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum DeviceClass
|
||||
{
|
||||
@ -27,5 +32,11 @@ namespace OmniLinkBridge.MQTT
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string value_template { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_off { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_on { get; set; }
|
||||
}
|
||||
}
|
17
OmniLinkBridge/MQTT/HomeAssistant/Button.cs
Normal file
17
OmniLinkBridge/MQTT/HomeAssistant/Button.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Button : Device
|
||||
{
|
||||
public Button(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_press { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Climate : Device
|
||||
{
|
||||
public Climate(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string status { get; set; }
|
||||
|
||||
public string action_topic { get; set; }
|
||||
public string current_temperature_topic { get; set; }
|
||||
|
||||
public string temperature_low_state_topic { get; set; }
|
||||
@ -23,7 +31,8 @@ namespace OmniLinkBridge.MQTT
|
||||
public string fan_mode_command_topic { get; set; }
|
||||
public List<string> fan_modes { get; set; } = new List<string>(new string[] { "auto", "on", "cycle" });
|
||||
|
||||
public string hold_state_topic { get; set; }
|
||||
public string hold_command_topic { get; set; }
|
||||
public string preset_mode_state_topic { get; set; }
|
||||
public string preset_mode_command_topic { get; set; }
|
||||
public List<string> preset_modes { get; set; } = new List<string>(new string[] { "off", "on", "vacation" });
|
||||
}
|
||||
}
|
44
OmniLinkBridge/MQTT/HomeAssistant/Device.cs
Normal file
44
OmniLinkBridge/MQTT/HomeAssistant/Device.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Device
|
||||
{
|
||||
public Device(DeviceRegistry deviceRegistry)
|
||||
{
|
||||
device = deviceRegistry;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum AvailabilityMode
|
||||
{
|
||||
all,
|
||||
any,
|
||||
latest
|
||||
}
|
||||
|
||||
public string unique_id { get; set; }
|
||||
|
||||
public string name { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string icon { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string availability_topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<Availability> availability { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public AvailabilityMode? availability_mode { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeviceRegistry device { get; set; }
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class DeviceRegistry
|
||||
{
|
@ -1,7 +1,12 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Light : Device
|
||||
{
|
||||
public Light(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
public string brightness_state_topic { get; set; }
|
26
OmniLinkBridge/MQTT/HomeAssistant/Lock.cs
Normal file
26
OmniLinkBridge/MQTT/HomeAssistant/Lock.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Lock : Device
|
||||
{
|
||||
public Lock(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_lock { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_unlock { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_locked { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_unlocked { get; set; }
|
||||
}
|
||||
}
|
23
OmniLinkBridge/MQTT/HomeAssistant/Number.cs
Normal file
23
OmniLinkBridge/MQTT/HomeAssistant/Number.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Number : Device
|
||||
{
|
||||
public Number(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int? min { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int? max { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public double? step { get; set; }
|
||||
}
|
||||
}
|
16
OmniLinkBridge/MQTT/HomeAssistant/Select.cs
Normal file
16
OmniLinkBridge/MQTT/HomeAssistant/Select.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Select : Device
|
||||
{
|
||||
public Select(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
public List<string> options { get; set; } = null;
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Sensor : Device
|
||||
{
|
||||
public Sensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum DeviceClass
|
||||
{
|
||||
@ -15,9 +20,6 @@ namespace OmniLinkBridge.MQTT
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeviceClass? device_class { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string icon { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string unit_of_measurement { get; set; }
|
||||
|
23
OmniLinkBridge/MQTT/HomeAssistant/Switch.cs
Normal file
23
OmniLinkBridge/MQTT/HomeAssistant/Switch.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Switch : Device
|
||||
{
|
||||
public Switch(DeviceRegistry deviceRegistry) : base(deviceRegistry)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_off { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_on { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string value_template { get; set; }
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
using HAI_Shared;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using OmniLinkBridge.MQTT.HomeAssistant;
|
||||
using OmniLinkBridge.MQTT.Parser;
|
||||
using OmniLinkBridge.Modules;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
@ -7,16 +11,43 @@ namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public static string ToTopic(this clsArea area, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/area{area.Number.ToString()}/{topic.ToString()}";
|
||||
return $"{Global.mqtt_prefix}/area{area.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Alarm ToConfig(this clsArea area)
|
||||
{
|
||||
Alarm ret = new Alarm();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + area.Name;
|
||||
ret.state_topic = area.ToTopic(Topic.basic_state);
|
||||
ret.command_topic = area.ToTopic(Topic.command);
|
||||
Alarm ret = new Alarm(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + area.Name,
|
||||
state_topic = area.ToTopic(Topic.basic_state),
|
||||
command_topic = area.ToTopic(Topic.command),
|
||||
|
||||
};
|
||||
|
||||
Global.mqtt_discovery_override_area.TryGetValue(area.Number, out OverrideArea override_area);
|
||||
|
||||
if (override_area != null)
|
||||
{
|
||||
if(override_area.code_arm || override_area.code_disarm)
|
||||
{
|
||||
ret.command_template = "{{ action }},validate,{{ code }}";
|
||||
ret.code = "REMOTE_CODE";
|
||||
}
|
||||
ret.code_arm_required = override_area.code_arm;
|
||||
ret.code_disarm_required = override_area.code_disarm;
|
||||
|
||||
ret.supported_features.Clear();
|
||||
if (override_area.arm_home)
|
||||
ret.supported_features.Add("arm_home");
|
||||
if (override_area.arm_away)
|
||||
ret.supported_features.Add("arm_away");
|
||||
if (override_area.arm_night)
|
||||
ret.supported_features.Add("arm_night");
|
||||
if (override_area.arm_vacation)
|
||||
ret.supported_features.Add("arm_vacation");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -27,18 +58,37 @@ namespace OmniLinkBridge.MQTT
|
||||
area.AreaAlarms.IsBitSet(6)) // Duress
|
||||
return "triggered";
|
||||
else if (area.ExitTimer > 0)
|
||||
return "pending";
|
||||
return "arming";
|
||||
|
||||
return area.AreaMode switch
|
||||
{
|
||||
enuSecurityMode.Night => "armed_night",
|
||||
enuSecurityMode.NightDly => "armed_night_delay",
|
||||
enuSecurityMode.Day => "armed_home",
|
||||
enuSecurityMode.DayInst => "armed_home_instant",
|
||||
enuSecurityMode.Away => "armed_away",
|
||||
enuSecurityMode.Vacation => "armed_vacation",
|
||||
_ => "disarmed",
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToBasicState(this clsArea area)
|
||||
{
|
||||
if (area.AreaAlarms.IsBitSet(0) || // Burgulary
|
||||
area.AreaAlarms.IsBitSet(3) || // Auxiliary
|
||||
area.AreaAlarms.IsBitSet(6)) // Duress
|
||||
return "triggered";
|
||||
else if (area.ExitTimer > 0)
|
||||
return "arming";
|
||||
|
||||
switch (area.AreaMode)
|
||||
{
|
||||
case enuSecurityMode.Night:
|
||||
return "armed_night";
|
||||
case enuSecurityMode.NightDly:
|
||||
return "armed_night_delay";
|
||||
return "armed_night";
|
||||
case enuSecurityMode.Day:
|
||||
return "armed_home";
|
||||
case enuSecurityMode.DayInst:
|
||||
return "armed_home_instant";
|
||||
return "armed_home";
|
||||
case enuSecurityMode.Away:
|
||||
return "armed_away";
|
||||
case enuSecurityMode.Vacation:
|
||||
@ -49,123 +99,113 @@ namespace OmniLinkBridge.MQTT
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToBasicState(this clsArea area)
|
||||
{
|
||||
if (area.AreaAlarms.IsBitSet(0) || // Burgulary
|
||||
area.AreaAlarms.IsBitSet(3) || // Auxiliary
|
||||
area.AreaAlarms.IsBitSet(6)) // Duress
|
||||
return "triggered";
|
||||
else if (area.ExitTimer > 0)
|
||||
return "pending";
|
||||
|
||||
switch (area.AreaMode)
|
||||
{
|
||||
case enuSecurityMode.Night:
|
||||
case enuSecurityMode.NightDly:
|
||||
return "armed_night";
|
||||
case enuSecurityMode.Day:
|
||||
case enuSecurityMode.DayInst:
|
||||
return "armed_home";
|
||||
case enuSecurityMode.Away:
|
||||
case enuSecurityMode.Vacation:
|
||||
return "armed_away";
|
||||
case enuSecurityMode.Off:
|
||||
default:
|
||||
return "disarmed";
|
||||
}
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigBurglary(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}burglary";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Burglary";
|
||||
ret.device_class = BinarySensor.DeviceClass.safety;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}burglary",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Burglary",
|
||||
device_class = BinarySensor.DeviceClass.safety,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigFire(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}fire";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Fire";
|
||||
ret.device_class = BinarySensor.DeviceClass.smoke;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.fire_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}fire",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Fire",
|
||||
device_class = BinarySensor.DeviceClass.smoke,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.fire_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigGas(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}gas";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Gas";
|
||||
ret.device_class = BinarySensor.DeviceClass.gas;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.gas_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}gas",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Gas",
|
||||
device_class = BinarySensor.DeviceClass.gas,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.gas_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigAux(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}auxiliary";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Auxiliary";
|
||||
ret.device_class = BinarySensor.DeviceClass.problem;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}auxiliary",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Auxiliary",
|
||||
device_class = BinarySensor.DeviceClass.problem,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigFreeze(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}freeze";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Freeze";
|
||||
ret.device_class = BinarySensor.DeviceClass.cold;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.freeze_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}freeze",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Freeze",
|
||||
device_class = BinarySensor.DeviceClass.cold,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.freeze_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigWater(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}water";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Water";
|
||||
ret.device_class = BinarySensor.DeviceClass.moisture;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.water_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}water",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Water",
|
||||
device_class = BinarySensor.DeviceClass.moisture,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.water_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigDuress(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}duress";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Duress";
|
||||
ret.device_class = BinarySensor.DeviceClass.safety;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.duress_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}duress",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Duress",
|
||||
device_class = BinarySensor.DeviceClass.safety,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.duress_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfigTemp(this clsArea area)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}area{area.Number.ToString()}temp";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Temp";
|
||||
ret.device_class = BinarySensor.DeviceClass.heat;
|
||||
ret.state_topic = area.ToTopic(Topic.json_state);
|
||||
ret.value_template = "{% if value_json.temperature_alarm %} ON {%- else -%} OFF {%- endif %}";
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}area{area.Number}temp",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Temp",
|
||||
device_class = BinarySensor.DeviceClass.heat,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.temperature_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToJsonState(this clsArea area)
|
||||
{
|
||||
AreaState state = new AreaState()
|
||||
AreaState state = new AreaState
|
||||
{
|
||||
arming = area.ExitTimer > 0,
|
||||
burglary_alarm = area.AreaAlarms.IsBitSet(0),
|
||||
@ -175,70 +215,59 @@ namespace OmniLinkBridge.MQTT
|
||||
freeze_alarm = area.AreaAlarms.IsBitSet(4),
|
||||
water_alarm = area.AreaAlarms.IsBitSet(5),
|
||||
duress_alarm = area.AreaAlarms.IsBitSet(6),
|
||||
temperature_alarm = area.AreaAlarms.IsBitSet(7)
|
||||
temperature_alarm = area.AreaAlarms.IsBitSet(7),
|
||||
mode = area.AreaMode switch
|
||||
{
|
||||
enuSecurityMode.Night => "night",
|
||||
enuSecurityMode.NightDly => "night_delay",
|
||||
enuSecurityMode.Day => "home",
|
||||
enuSecurityMode.DayInst => "home_instant",
|
||||
enuSecurityMode.Away => "away",
|
||||
enuSecurityMode.Vacation => "vacation",
|
||||
_ => "off",
|
||||
}
|
||||
};
|
||||
|
||||
switch (area.AreaMode)
|
||||
{
|
||||
case enuSecurityMode.Night:
|
||||
state.mode = "night";
|
||||
break;
|
||||
case enuSecurityMode.NightDly:
|
||||
state.mode = "night_delay";
|
||||
break;
|
||||
case enuSecurityMode.Day:
|
||||
state.mode = "home";
|
||||
break;
|
||||
case enuSecurityMode.DayInst:
|
||||
state.mode = "home_instant";
|
||||
break;
|
||||
case enuSecurityMode.Away:
|
||||
state.mode = "away";
|
||||
break;
|
||||
case enuSecurityMode.Vacation:
|
||||
state.mode = "vacation";
|
||||
break;
|
||||
case enuSecurityMode.Off:
|
||||
default:
|
||||
state.mode = "off";
|
||||
break;
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(state);
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsZone zone, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/zone{zone.Number.ToString()}/{topic.ToString()}";
|
||||
return $"{Global.mqtt_prefix}/zone{zone.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Sensor ToConfigTemp(this clsZone zone, enuTempFormat format)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}zone{zone.Number.ToString()}temp";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Temp";
|
||||
ret.device_class = Sensor.DeviceClass.temperature;
|
||||
ret.state_topic = zone.ToTopic(Topic.current_temperature);
|
||||
ret.unit_of_measurement = (format == enuTempFormat.Fahrenheit ? "°F" : "°C");
|
||||
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}temp",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Temp",
|
||||
device_class = Sensor.DeviceClass.temperature,
|
||||
state_topic = zone.ToTopic(Topic.current_temperature),
|
||||
unit_of_measurement = (format == enuTempFormat.Fahrenheit ? "°F" : "°C")
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Sensor ToConfigHumidity(this clsZone zone)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}zone{zone.Number.ToString()}humidity";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Humidity";
|
||||
ret.device_class = Sensor.DeviceClass.humidity;
|
||||
ret.state_topic = zone.ToTopic(Topic.current_humidity);
|
||||
ret.unit_of_measurement = "%";
|
||||
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}humidity",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Humidity",
|
||||
device_class = Sensor.DeviceClass.humidity,
|
||||
state_topic = zone.ToTopic(Topic.current_humidity),
|
||||
unit_of_measurement = "%"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Sensor ToConfigSensor(this clsZone zone)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}zone{zone.Number.ToString()}";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + zone.Name;
|
||||
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + zone.Name
|
||||
};
|
||||
|
||||
switch (zone.ZoneType)
|
||||
{
|
||||
@ -274,11 +303,28 @@ namespace OmniLinkBridge.MQTT
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Switch ToConfigSwitch(this clsZone zone)
|
||||
{
|
||||
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}switch",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Bypass",
|
||||
state_topic = zone.ToTopic(Topic.state),
|
||||
command_topic = zone.ToTopic(Topic.command),
|
||||
payload_off = "restore",
|
||||
payload_on = "bypass",
|
||||
value_template = "{% if value == 'bypassed' %} bypass {%- else -%} restore {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfig(this clsZone zone)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}zone{zone.Number.ToString()}binary";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + zone.Name;
|
||||
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}binary",
|
||||
name = Global.mqtt_discovery_name_prefix + zone.Name
|
||||
};
|
||||
|
||||
Global.mqtt_discovery_override_zone.TryGetValue(zone.Number, out OverrideZone override_zone);
|
||||
|
||||
@ -344,28 +390,46 @@ namespace OmniLinkBridge.MQTT
|
||||
|
||||
public static string ToTopic(this clsUnit unit, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/unit{unit.Number.ToString()}/{topic.ToString()}";
|
||||
return $"{Global.mqtt_prefix}/unit{unit.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Light ToConfig(this clsUnit unit)
|
||||
{
|
||||
Light ret = new Light();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}unit{unit.Number.ToString()}light";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + unit.Name;
|
||||
ret.state_topic = unit.ToTopic(Topic.state);
|
||||
ret.command_topic = unit.ToTopic(Topic.command);
|
||||
ret.brightness_state_topic = unit.ToTopic(Topic.brightness_state);
|
||||
ret.brightness_command_topic = unit.ToTopic(Topic.brightness_command);
|
||||
Light ret = new Light(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}light",
|
||||
name = Global.mqtt_discovery_name_prefix + unit.Name,
|
||||
state_topic = unit.ToTopic(Topic.state),
|
||||
command_topic = unit.ToTopic(Topic.command),
|
||||
brightness_state_topic = unit.ToTopic(Topic.brightness_state),
|
||||
brightness_command_topic = unit.ToTopic(Topic.brightness_command)
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Switch ToConfigSwitch(this clsUnit unit)
|
||||
{
|
||||
Switch ret = new Switch();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}unit{unit.Number.ToString()}switch";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + unit.Name;
|
||||
ret.state_topic = unit.ToTopic(Topic.state);
|
||||
ret.command_topic = unit.ToTopic(Topic.command);
|
||||
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}switch",
|
||||
name = Global.mqtt_discovery_name_prefix + unit.Name,
|
||||
state_topic = unit.ToTopic(Topic.state),
|
||||
command_topic = unit.ToTopic(Topic.command)
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Number ToConfigNumber(this clsUnit unit)
|
||||
{
|
||||
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}number",
|
||||
name = Global.mqtt_discovery_name_prefix + unit.Name,
|
||||
state_topic = unit.ToTopic(Topic.flag_state),
|
||||
command_topic = unit.ToTopic(Topic.flag_command),
|
||||
min = 0,
|
||||
max = 255
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -384,94 +448,182 @@ namespace OmniLinkBridge.MQTT
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static string ToSceneState(this clsUnit unit)
|
||||
{
|
||||
if (unit.Status >= 2 && unit.Status <= 13)
|
||||
// 2-13 maps to scene A-L respectively
|
||||
return ((char)(unit.Status + 63)).ToString();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsThermostat thermostat, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/thermostat{thermostat.Number.ToString()}/{topic.ToString()}";
|
||||
return $"{Global.mqtt_prefix}/thermostat{thermostat.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Sensor ToConfigTemp(this clsThermostat thermostat, enuTempFormat format)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number.ToString()}temp";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Temp";
|
||||
ret.device_class = Sensor.DeviceClass.temperature;
|
||||
ret.state_topic = thermostat.ToTopic(Topic.current_temperature);
|
||||
ret.unit_of_measurement = (format == enuTempFormat.Fahrenheit ? "°F" : "°C");
|
||||
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}temp",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Temp",
|
||||
device_class = Sensor.DeviceClass.temperature,
|
||||
state_topic = thermostat.ToTopic(Topic.current_temperature),
|
||||
unit_of_measurement = (format == enuTempFormat.Fahrenheit ? "°F" : "°C")
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Number ToConfigHumidify(this clsThermostat thermostat)
|
||||
{
|
||||
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidify",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidify",
|
||||
icon = "mdi:water-percent",
|
||||
state_topic = thermostat.ToTopic(Topic.humidify_state),
|
||||
command_topic = thermostat.ToTopic(Topic.humidify_command),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Number ToConfigDehumidify(this clsThermostat thermostat)
|
||||
{
|
||||
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}dehumidify",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Dehumidify",
|
||||
icon = "mdi:water-percent",
|
||||
state_topic = thermostat.ToTopic(Topic.dehumidify_state),
|
||||
command_topic = thermostat.ToTopic(Topic.dehumidify_command),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Sensor ToConfigHumidity(this clsThermostat thermostat)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number.ToString()}humidity";
|
||||
ret.name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidity";
|
||||
ret.device_class = Sensor.DeviceClass.humidity;
|
||||
ret.state_topic = thermostat.ToTopic(Topic.current_humidity);
|
||||
ret.unit_of_measurement = "%";
|
||||
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidity",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidity",
|
||||
device_class = Sensor.DeviceClass.humidity,
|
||||
state_topic = thermostat.ToTopic(Topic.current_humidity),
|
||||
unit_of_measurement = "%"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Climate ToConfig(this clsThermostat thermostat, enuTempFormat format)
|
||||
{
|
||||
Climate ret = new Climate();
|
||||
Climate ret = new Climate(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + thermostat.Name,
|
||||
|
||||
if(format == enuTempFormat.Celsius)
|
||||
availability_topic = null,
|
||||
availability_mode = Device.AvailabilityMode.all,
|
||||
availability = new List<Availability>()
|
||||
{
|
||||
new Availability(),
|
||||
new Availability() { topic = thermostat.ToTopic(Topic.status) }
|
||||
},
|
||||
|
||||
modes = thermostat.Type switch
|
||||
{
|
||||
enuThermostatType.AutoHeatCool => new List<string>(new string[] { "auto", "off", "cool", "heat" }),
|
||||
enuThermostatType.HeatCool => new List<string>(new string[] { "off", "cool", "heat" }),
|
||||
enuThermostatType.HeatOnly => new List<string>(new string[] { "off", "heat" }),
|
||||
enuThermostatType.CoolOnly => new List<string>(new string[] { "off", "cool" }),
|
||||
_ => new List<string>(new string[] { "off" }),
|
||||
},
|
||||
|
||||
action_topic = thermostat.ToTopic(Topic.current_operation),
|
||||
current_temperature_topic = thermostat.ToTopic(Topic.current_temperature),
|
||||
|
||||
temperature_low_state_topic = thermostat.ToTopic(Topic.temperature_heat_state),
|
||||
temperature_low_command_topic = thermostat.ToTopic(Topic.temperature_heat_command),
|
||||
|
||||
temperature_high_state_topic = thermostat.ToTopic(Topic.temperature_cool_state),
|
||||
temperature_high_command_topic = thermostat.ToTopic(Topic.temperature_cool_command),
|
||||
|
||||
mode_state_topic = thermostat.ToTopic(Topic.mode_basic_state),
|
||||
mode_command_topic = thermostat.ToTopic(Topic.mode_command),
|
||||
|
||||
fan_mode_state_topic = thermostat.ToTopic(Topic.fan_mode_state),
|
||||
fan_mode_command_topic = thermostat.ToTopic(Topic.fan_mode_command),
|
||||
|
||||
preset_mode_state_topic = thermostat.ToTopic(Topic.hold_state),
|
||||
preset_mode_command_topic = thermostat.ToTopic(Topic.hold_command)
|
||||
};
|
||||
|
||||
if (format == enuTempFormat.Celsius)
|
||||
{
|
||||
ret.min_temp = "7";
|
||||
ret.max_temp = "35";
|
||||
}
|
||||
|
||||
ret.unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number.ToString()}";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + thermostat.Name;
|
||||
ret.current_temperature_topic = thermostat.ToTopic(Topic.current_temperature);
|
||||
|
||||
ret.temperature_low_state_topic = thermostat.ToTopic(Topic.temperature_heat_state);
|
||||
ret.temperature_low_command_topic = thermostat.ToTopic(Topic.temperature_heat_command);
|
||||
|
||||
ret.temperature_high_state_topic = thermostat.ToTopic(Topic.temperature_cool_state);
|
||||
ret.temperature_high_command_topic = thermostat.ToTopic(Topic.temperature_cool_command);
|
||||
|
||||
ret.mode_state_topic = thermostat.ToTopic(Topic.mode_state);
|
||||
ret.mode_command_topic = thermostat.ToTopic(Topic.mode_command);
|
||||
|
||||
ret.fan_mode_state_topic = thermostat.ToTopic(Topic.fan_mode_state);
|
||||
ret.fan_mode_command_topic = thermostat.ToTopic(Topic.fan_mode_command);
|
||||
|
||||
ret.hold_state_topic = thermostat.ToTopic(Topic.hold_state);
|
||||
ret.hold_command_topic = thermostat.ToTopic(Topic.hold_command);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToOperationState(this clsThermostat thermostat)
|
||||
{
|
||||
string status = thermostat.HorC_StatusText();
|
||||
|
||||
if (status.Contains("COOLING"))
|
||||
return "cool";
|
||||
else if (status.Contains("HEATING"))
|
||||
return "heat";
|
||||
if (thermostat.HorC_Status.IsBitSet(0))
|
||||
return "heating";
|
||||
else if (thermostat.HorC_Status.IsBitSet(1))
|
||||
return "cooling";
|
||||
else
|
||||
return "idle";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsButton button, Topic topic)
|
||||
public static string ToModeState(this clsThermostat thermostat)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/button{button.Number.ToString()}/{topic.ToString()}";
|
||||
if (thermostat.Mode == enuThermostatMode.E_Heat)
|
||||
return "e_heat";
|
||||
else
|
||||
return thermostat.ModeText().ToLower();
|
||||
}
|
||||
|
||||
public static Switch ToConfig(this clsButton button)
|
||||
public static string ToModeBasicState(this clsThermostat thermostat)
|
||||
{
|
||||
Switch ret = new Switch();
|
||||
ret.unique_id = $"{Global.mqtt_prefix}button{button.Number.ToString()}";
|
||||
ret.name = Global.mqtt_discovery_name_prefix + button.Name;
|
||||
ret.state_topic = button.ToTopic(Topic.state);
|
||||
ret.command_topic = button.ToTopic(Topic.command);
|
||||
if (thermostat.Mode == enuThermostatMode.E_Heat)
|
||||
return "heat";
|
||||
else
|
||||
return thermostat.ModeText().ToLower();
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsButton button, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/button{button.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Switch ToConfigSwitch(this clsButton button)
|
||||
{
|
||||
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}button{button.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + button.Name,
|
||||
state_topic = button.ToTopic(Topic.state),
|
||||
command_topic = button.ToTopic(Topic.command)
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Button ToConfigButton(this clsButton button)
|
||||
{
|
||||
Button ret = new Button(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}button{button.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + button.Name,
|
||||
command_topic = button.ToTopic(Topic.command),
|
||||
payload_press = "ON"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsMessage message, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/message{message.Number.ToString()}/{topic.ToString()}";
|
||||
return $"{Global.mqtt_prefix}/message{message.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static string ToState(this clsMessage message)
|
||||
@ -483,5 +635,135 @@ namespace OmniLinkBridge.MQTT
|
||||
else
|
||||
return "off";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsAccessControlReader reader, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/lock{reader.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Lock ToConfig(this clsAccessControlReader reader)
|
||||
{
|
||||
Lock ret = new Lock(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}lock{reader.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + reader.Name,
|
||||
state_topic = reader.ToTopic(Topic.state),
|
||||
command_topic = reader.ToTopic(Topic.command),
|
||||
payload_lock = "lock",
|
||||
payload_unlock = "unlock",
|
||||
state_locked = "locked",
|
||||
state_unlocked = "unlocked"
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsAccessControlReader reader)
|
||||
{
|
||||
if (reader.LockStatus == 0)
|
||||
return "locked";
|
||||
else
|
||||
return "unlocked";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsAudioSource audioSource, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/source{audioSource.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsAudioZone audioZone, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/audio{audioZone.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Switch ToConfig(this clsAudioZone audioZone)
|
||||
{
|
||||
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}audio{audioZone.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + audioZone.rawName,
|
||||
icon = "mdi:speaker",
|
||||
state_topic = audioZone.ToTopic(Topic.state),
|
||||
command_topic = audioZone.ToTopic(Topic.command)
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsAudioZone audioZone)
|
||||
{
|
||||
return audioZone.Power ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
public static Switch ToConfigMute(this clsAudioZone audioZone)
|
||||
{
|
||||
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}audio{audioZone.Number}mute",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{audioZone.rawName} Mute",
|
||||
icon = "mdi:volume-mute",
|
||||
state_topic = audioZone.ToTopic(Topic.mute_state),
|
||||
command_topic = audioZone.ToTopic(Topic.mute_command)
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToMuteState(this clsAudioZone audioZone)
|
||||
{
|
||||
if(Global.mqtt_audio_local_mute)
|
||||
return audioZone.Volume == 0 ? "ON" : "OFF";
|
||||
else
|
||||
return audioZone.Mute ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
public static Select ToConfigSource(this clsAudioZone audioZone, List<string> audioSources)
|
||||
{
|
||||
Select ret = new Select(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}audio{audioZone.Number}source",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{audioZone.rawName} Source",
|
||||
icon = "mdi:volume-source",
|
||||
state_topic = audioZone.ToTopic(Topic.source_state),
|
||||
command_topic = audioZone.ToTopic(Topic.source_command),
|
||||
options = audioSources
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static int ToSourceState(this clsAudioZone audioZone)
|
||||
{
|
||||
return audioZone.Source;
|
||||
}
|
||||
|
||||
public static Number ToConfigVolume(this clsAudioZone audioZone)
|
||||
{
|
||||
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}audio{audioZone.Number}volume",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}{audioZone.rawName} Volume",
|
||||
icon = "mdi:volume-low",
|
||||
state_topic = audioZone.ToTopic(Topic.volume_state),
|
||||
command_topic = audioZone.ToTopic(Topic.volume_command),
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
};
|
||||
|
||||
if(Global.mqtt_audio_volume_media_player)
|
||||
{
|
||||
ret.min = 0;
|
||||
ret.max = 1;
|
||||
ret.step = 0.01;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static double ToVolumeState(this clsAudioZone audioZone)
|
||||
{
|
||||
if (Global.mqtt_audio_volume_media_player)
|
||||
return audioZone.Volume * 0.01;
|
||||
else
|
||||
return audioZone.Volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,33 @@
|
||||
using HAI_Shared;
|
||||
using log4net;
|
||||
using OmniLinkBridge.MQTT.Parser;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class MessageProcessor
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
|
||||
|
||||
private IOmniLinkII OmniLink { get; set; }
|
||||
private readonly int[] audioMuteVolumes;
|
||||
private const int VOLUME_DEFAULT = 10;
|
||||
|
||||
public MessageProcessor(IOmniLinkII omni)
|
||||
private IOmniLinkII OmniLink { get; }
|
||||
private Dictionary<string, int> AudioSources { get; }
|
||||
|
||||
public MessageProcessor(IOmniLinkII omni, Dictionary<string, int> audioSources, int numAudioZones)
|
||||
{
|
||||
OmniLink = omni;
|
||||
AudioSources = audioSources;
|
||||
|
||||
audioMuteVolumes = new int[numAudioZones];
|
||||
}
|
||||
|
||||
public void Process(string messageTopic, string payload)
|
||||
@ -28,16 +37,17 @@ namespace OmniLinkBridge.MQTT
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
if (!Enum.TryParse(match.Groups[1].Value, true, out CommandTypes type)
|
||||
|| !Enum.TryParse(match.Groups[3].Value, true, out Topic topic)
|
||||
if (!Enum.TryParse(match.Groups[1].Value, true, out CommandTypes type)
|
||||
|| !Enum.TryParse(match.Groups[3].Value, true, out Topic topic)
|
||||
|| !ushort.TryParse(match.Groups[2].Value, out ushort id))
|
||||
return;
|
||||
|
||||
log.Debug($"Received: Type: {type.ToString()}, Id: {id}, Command: {topic.ToString()}, Value: {payload}");
|
||||
log.Debug("Received: Type: {type}, Id: {id}, Command: {command}, Value: {value}",
|
||||
type.ToString(), id, topic.ToString(), payload);
|
||||
|
||||
if (type == CommandTypes.area && id <= OmniLink.Controller.Areas.Count)
|
||||
ProcessAreaReceived(OmniLink.Controller.Areas[id], topic, payload);
|
||||
else if (type == CommandTypes.zone && id > 0 && id <= OmniLink.Controller.Zones.Count)
|
||||
else if (type == CommandTypes.zone && id <= OmniLink.Controller.Zones.Count)
|
||||
ProcessZoneReceived(OmniLink.Controller.Zones[id], topic, payload);
|
||||
else if (type == CommandTypes.unit && id > 0 && id <= OmniLink.Controller.Units.Count)
|
||||
ProcessUnitReceived(OmniLink.Controller.Units[id], topic, payload);
|
||||
@ -47,6 +57,10 @@ namespace OmniLinkBridge.MQTT
|
||||
ProcessButtonReceived(OmniLink.Controller.Buttons[id], topic, payload);
|
||||
else if (type == CommandTypes.message && id > 0 && id <= OmniLink.Controller.Messages.Count)
|
||||
ProcessMessageReceived(OmniLink.Controller.Messages[id], topic, payload);
|
||||
else if (type == CommandTypes.@lock && id <= OmniLink.Controller.AccessControlReaders.Count)
|
||||
ProcessLockReceived(OmniLink.Controller.AccessControlReaders[id], topic, payload);
|
||||
else if (type == CommandTypes.audio && id <= OmniLink.Controller.AudioZones.Count)
|
||||
ProcessAudioReceived(OmniLink.Controller.AudioZones[id], topic, payload);
|
||||
}
|
||||
|
||||
private static readonly IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
|
||||
@ -55,21 +69,77 @@ namespace OmniLinkBridge.MQTT
|
||||
{ AreaCommands.arm_home, enuUnitCommand.SecurityDay },
|
||||
{ AreaCommands.arm_away, enuUnitCommand.SecurityAway },
|
||||
{ AreaCommands.arm_night, enuUnitCommand.SecurityNight },
|
||||
{ AreaCommands.arm_vacation, enuUnitCommand.SecurityVac },
|
||||
// The below aren't supported by Home Assistant
|
||||
{ AreaCommands.arm_home_instant, enuUnitCommand.SecurityDyi },
|
||||
{ AreaCommands.arm_night_delay, enuUnitCommand.SecurityNtd },
|
||||
{ AreaCommands.arm_vacation, enuUnitCommand.SecurityVac },
|
||||
{ AreaCommands.arm_night_delay, enuUnitCommand.SecurityNtd }
|
||||
};
|
||||
|
||||
private void ProcessAreaReceived(clsArea area, Topic command, string payload)
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out AreaCommands cmd))
|
||||
AreaCommandCode parser = payload.ToCommandCode(supportValidate: true);
|
||||
|
||||
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out AreaCommands cmd))
|
||||
{
|
||||
if (area.Number == 0)
|
||||
log.Debug("SetArea: 0 implies all areas will be changed");
|
||||
|
||||
log.Debug("SetArea: " + area.Number + " to " + cmd.ToString().Replace("arm_", "").Replace("_", " "));
|
||||
OmniLink.SendCommand(AreaMapping[cmd], 0, (ushort)area.Number);
|
||||
if (parser.Validate)
|
||||
{
|
||||
string sCode = parser.Code.ToString();
|
||||
|
||||
if (sCode.Length != 4)
|
||||
{
|
||||
log.Warning("SetArea: {id}, Invalid security code: must be 4 digits", area.Number);
|
||||
return;
|
||||
}
|
||||
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgRequestValidateCode(OmniLink.Controller.Connection)
|
||||
{
|
||||
Area = (byte)area.Number,
|
||||
Digit1 = (byte)int.Parse(sCode[0].ToString()),
|
||||
Digit2 = (byte)int.Parse(sCode[1].ToString()),
|
||||
Digit3 = (byte)int.Parse(sCode[2].ToString()),
|
||||
Digit4 = (byte)int.Parse(sCode[3].ToString())
|
||||
}, (M, B, Timeout) =>
|
||||
{
|
||||
if (Timeout || !((B.Length > 3) && (B[0] == 0x21) && (enuOmniLink2MessageType)B[2] == enuOmniLink2MessageType.ValidateCode))
|
||||
return;
|
||||
|
||||
var validateCode = new clsOL2MsgValidateCode(OmniLink.Controller.Connection, B);
|
||||
|
||||
if (validateCode.AuthorityLevel == 0)
|
||||
{
|
||||
log.Warning("SetArea: {id}, Invalid security code: validation failed", area.Number);
|
||||
return;
|
||||
}
|
||||
|
||||
log.Debug("SetArea: {id}, Validated security code, Code Number: {code}, Authority: {authority}",
|
||||
area.Number, validateCode.CodeNumber, validateCode.AuthorityLevel.ToString());
|
||||
|
||||
log.Debug("SetArea: {id} to {value}, Code Number: {code}",
|
||||
area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "), validateCode.CodeNumber);
|
||||
|
||||
OmniLink.SendCommand(AreaMapping[cmd], validateCode.CodeNumber, (ushort)area.Number);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.Debug("SetArea: {id} to {value}, Code Number: {code}",
|
||||
area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "), parser.Code);
|
||||
|
||||
OmniLink.SendCommand(AreaMapping[cmd], (byte)parser.Code, (ushort)area.Number);
|
||||
}
|
||||
else if (command == Topic.alarm_command && area.Number > 0 && Enum.TryParse(parser.Command, true, out AlarmCommands alarm))
|
||||
{
|
||||
log.Debug("SetAreaAlarm: {id} to {value}", area.Number, parser.Command);
|
||||
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgActivateKeypadEmg(OmniLink.Controller.Connection)
|
||||
{
|
||||
Area = (byte)area.Number,
|
||||
EmgType = (byte)alarm
|
||||
}, (M, B, Timeout) => { });
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,10 +151,16 @@ namespace OmniLinkBridge.MQTT
|
||||
|
||||
private void ProcessZoneReceived(clsZone zone, Topic command, string payload)
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out ZoneCommands cmd))
|
||||
AreaCommandCode parser = payload.ToCommandCode();
|
||||
|
||||
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out ZoneCommands cmd) &&
|
||||
!(zone.Number == 0 && cmd == ZoneCommands.bypass))
|
||||
{
|
||||
log.Debug("SetZone: " + zone.Number + " to " + payload);
|
||||
OmniLink.SendCommand(ZoneMapping[cmd], 0, (ushort)zone.Number);
|
||||
if (zone.Number == 0)
|
||||
log.Debug("SetZone: 0 implies all zones will be restored");
|
||||
|
||||
log.Debug("SetZone: {id} to {value}", zone.Number, parser.Command);
|
||||
OmniLink.SendCommand(ZoneMapping[cmd], (byte)parser.Code, (ushort)zone.Number);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,14 +176,20 @@ namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
if (string.Compare(unit.ToState(), cmd.ToString()) != 0)
|
||||
{
|
||||
log.Debug("SetUnit: " + unit.Number + " to " + cmd.ToString());
|
||||
log.Debug("SetUnit: {id} to {value}", unit.Number, cmd.ToString());
|
||||
OmniLink.SendCommand(UnitMapping[cmd], 0, (ushort)unit.Number);
|
||||
}
|
||||
}
|
||||
else if (command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
|
||||
else if (unit.Type == enuOL2UnitType.Flag &&
|
||||
command == Topic.flag_command && int.TryParse(payload, out int flagValue))
|
||||
{
|
||||
log.Debug("SetUnit: " + unit.Number + " to " + payload + "%");
|
||||
|
||||
log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Set, BitConverter.GetBytes(flagValue)[0], (ushort)unit.Number);
|
||||
}
|
||||
else if (unit.Type != enuOL2UnitType.Output &&
|
||||
command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
|
||||
{
|
||||
log.Debug("SetUnit: {id} to {value}%", unit.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unitValue)[0], (ushort)unit.Number);
|
||||
|
||||
// Force status change instead of waiting on controller to update
|
||||
@ -115,6 +197,12 @@ namespace OmniLinkBridge.MQTT
|
||||
// which will cause light to go to 100% brightness
|
||||
unit.Status = (byte)(100 + unitValue);
|
||||
}
|
||||
else if (unit.Type != enuOL2UnitType.Output &&
|
||||
command == Topic.scene_command && char.TryParse(payload, out char scene))
|
||||
{
|
||||
log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Compose, (byte)(scene - 63), (ushort)unit.Number);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessThermostatReceived(clsThermostat thermostat, Topic command, string payload)
|
||||
@ -129,7 +217,8 @@ namespace OmniLinkBridge.MQTT
|
||||
}
|
||||
|
||||
int temp = tempLow.ToOmniTemp();
|
||||
log.Debug("SetThermostatHeatSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
|
||||
log.Debug("SetThermostatHeatSetpoint: {id} to {value}{temperatureUnit} ({temp})",
|
||||
thermostat.Number, payload, tempUnit, temp);
|
||||
OmniLink.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (command == Topic.temperature_cool_command && double.TryParse(payload, out double tempHigh))
|
||||
@ -142,35 +231,45 @@ namespace OmniLinkBridge.MQTT
|
||||
}
|
||||
|
||||
int temp = tempHigh.ToOmniTemp();
|
||||
log.Debug("SetThermostatCoolSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
|
||||
log.Debug("SetThermostatCoolSetpoint: {id} to {value}{temperatureUnit} ({temp})",
|
||||
thermostat.Number, payload, tempUnit, temp);
|
||||
OmniLink.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (command == Topic.humidify_command && double.TryParse(payload, out double humidify))
|
||||
{
|
||||
// Humidity is reported where Fahrenheit temperatures 0-100 correspond to 0-100% relative humidity
|
||||
int level = humidify.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatHumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
|
||||
log.Debug("SetThermostatHumidifySetpoint: {id} to {value}% ({level})", thermostat.Number, payload, level);
|
||||
OmniLink.SendCommand(enuUnitCommand.SetHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (command == Topic.dehumidify_command && double.TryParse(payload, out double dehumidify))
|
||||
{
|
||||
int level = dehumidify.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatDehumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
|
||||
log.Debug("SetThermostatDehumidifySetpoint: {id} to {value}% ({level})", thermostat.Number, payload, level);
|
||||
OmniLink.SendCommand(enuUnitCommand.SetDeHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (command == Topic.mode_command && Enum.TryParse(payload, true, out enuThermostatMode mode))
|
||||
{
|
||||
log.Debug("SetThermostatMode: " + thermostat.Number + " to " + payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes((int)mode)[0], (ushort)thermostat.Number);
|
||||
if (thermostat.Type == enuThermostatType.AutoHeatCool ||
|
||||
(thermostat.Type == enuThermostatType.HeatCool && mode != enuThermostatMode.Auto) ||
|
||||
(thermostat.Type == enuThermostatType.CoolOnly &&
|
||||
(mode == enuThermostatMode.Off || mode == enuThermostatMode.Cool)) ||
|
||||
(thermostat.Type == enuThermostatType.HeatOnly &&
|
||||
(mode == enuThermostatMode.Off || mode == enuThermostatMode.Heat || mode == enuThermostatMode.E_Heat)) ||
|
||||
mode == enuThermostatMode.Off)
|
||||
{
|
||||
log.Debug("SetThermostatMode: {id} to {value}", thermostat.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes((int)mode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
}
|
||||
else if (command == Topic.fan_mode_command && Enum.TryParse(payload, true, out enuThermostatFanMode fanMode))
|
||||
{
|
||||
log.Debug("SetThermostatFanMode: " + thermostat.Number + " to " + payload);
|
||||
log.Debug("SetThermostatFanMode: {id} to {value}", thermostat.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes((int)fanMode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (command == Topic.hold_command && Enum.TryParse(payload, true, out enuThermostatHoldMode holdMode))
|
||||
{
|
||||
log.Debug("SetThermostatHold: " + thermostat.Number + " to " + payload);
|
||||
log.Debug("SetThermostatHold: {id} to {value}", thermostat.Number, payload);
|
||||
OmniLink.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes((int)holdMode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
}
|
||||
@ -179,7 +278,7 @@ namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out UnitCommands cmd) && cmd == UnitCommands.ON)
|
||||
{
|
||||
log.Debug("PushButton: " + button.Number);
|
||||
log.Debug("PushButton: {id}", button.Number);
|
||||
OmniLink.SendCommand(enuUnitCommand.Button, 0, (ushort)button.Number);
|
||||
}
|
||||
}
|
||||
@ -196,7 +295,7 @@ namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out MessageCommands cmd))
|
||||
{
|
||||
log.Debug("SetMessage: " + message.Number + " to " + cmd.ToString().Replace("_", " "));
|
||||
log.Debug("SetMessage: {id} to {value}", message.Number, cmd.ToString().Replace("_", " "));
|
||||
|
||||
byte par = 0;
|
||||
if (cmd == MessageCommands.show_no_beep)
|
||||
@ -207,5 +306,111 @@ namespace OmniLinkBridge.MQTT
|
||||
OmniLink.SendCommand(MessageMapping[cmd], par, (ushort)message.Number);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IDictionary<LockCommands, enuUnitCommand> LockMapping = new Dictionary<LockCommands, enuUnitCommand>
|
||||
{
|
||||
{ LockCommands.@lock, enuUnitCommand.Lock },
|
||||
{ LockCommands.unlock, enuUnitCommand.Unlock },
|
||||
};
|
||||
|
||||
private void ProcessLockReceived(clsAccessControlReader reader, Topic command, string payload)
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out LockCommands cmd))
|
||||
{
|
||||
if (reader.Number == 0)
|
||||
log.Debug("SetLock: 0 implies all locks will be changed");
|
||||
|
||||
log.Debug("SetLock: {id} to {value}", reader.Number, payload);
|
||||
|
||||
OmniLink.SendCommand(LockMapping[cmd], 0, (ushort)reader.Number);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessAudioReceived(clsAudioZone audioZone, Topic command, string payload)
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out UnitCommands cmd))
|
||||
{
|
||||
if (audioZone.Number == 0)
|
||||
log.Debug("SetAudio: 0 implies all audio zones will be changed");
|
||||
|
||||
log.Debug("SetAudio: {id} to {value}", audioZone.Number, payload);
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioZone, (byte)cmd, (ushort)audioZone.Number);
|
||||
|
||||
// Send power ON twice to workaround Russound standby
|
||||
if(cmd == UnitCommands.ON)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioZone, (byte)cmd, (ushort)audioZone.Number);
|
||||
}
|
||||
}
|
||||
else if (command == Topic.mute_command && Enum.TryParse(payload, true, out UnitCommands mute))
|
||||
{
|
||||
if (audioZone.Number == 0)
|
||||
{
|
||||
if (Global.mqtt_audio_local_mute)
|
||||
{
|
||||
log.Warning("SetAudioMute: 0 not supported with local mute");
|
||||
return;
|
||||
}
|
||||
else
|
||||
log.Debug("SetAudioMute: 0 implies all audio zones will be changed");
|
||||
}
|
||||
|
||||
if (Global.mqtt_audio_local_mute)
|
||||
{
|
||||
if (mute == UnitCommands.ON)
|
||||
{
|
||||
log.Debug("SetAudioMute: {id} local mute, previous volume {level}",
|
||||
audioZone.Number, audioZone.Volume);
|
||||
audioMuteVolumes[audioZone.Number] = audioZone.Volume;
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioVolume, 0, (ushort)audioZone.Number);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (audioMuteVolumes[audioZone.Number] == 0)
|
||||
{
|
||||
log.Debug("SetAudioMute: {id} local mute, defaulting to volume {level}",
|
||||
audioZone.Number, VOLUME_DEFAULT);
|
||||
audioMuteVolumes[audioZone.Number] = VOLUME_DEFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug("SetAudioMute: {id} local mute, restoring to volume {level}",
|
||||
audioZone.Number, audioMuteVolumes[audioZone.Number]);
|
||||
}
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioVolume, (byte)audioMuteVolumes[audioZone.Number], (ushort)audioZone.Number);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug("SetAudioMute: {id} to {value}", audioZone.Number, payload);
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioZone, (byte)(mute + 2), (ushort)audioZone.Number);
|
||||
}
|
||||
}
|
||||
else if (command == Topic.source_command && AudioSources.TryGetValue(payload, out int source))
|
||||
{
|
||||
log.Debug("SetAudioSource: {id} to {value}", audioZone.Number, payload);
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioSource, (byte)source, (ushort)audioZone.Number);
|
||||
}
|
||||
else if (command == Topic.volume_command && double.TryParse(payload, out double volume))
|
||||
{
|
||||
if (Global.mqtt_audio_volume_media_player)
|
||||
volume *= 100;
|
||||
|
||||
if (volume > 100)
|
||||
volume = 100;
|
||||
else if (volume < 0)
|
||||
volume = 0;
|
||||
|
||||
log.Debug("SetAudioVolume: {id} to {value}", audioZone.Number, volume);
|
||||
|
||||
OmniLink.SendCommand(enuUnitCommand.AudioVolume, (byte)volume, (ushort)audioZone.Number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
OmniLinkBridge/MQTT/OverrideArea.cs
Normal file
19
OmniLinkBridge/MQTT/OverrideArea.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class OverrideArea
|
||||
{
|
||||
public bool code_arm { get; set; }
|
||||
|
||||
public bool code_disarm { get; set; }
|
||||
|
||||
public bool arm_home { get; set; } = true;
|
||||
|
||||
public bool arm_away { get; set; } = true;
|
||||
|
||||
public bool arm_night { get; set; } = true;
|
||||
|
||||
public bool arm_vacation { get; set; } = true;
|
||||
}
|
||||
}
|
7
OmniLinkBridge/MQTT/OverrideUnit.cs
Normal file
7
OmniLinkBridge/MQTT/OverrideUnit.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class OverrideUnit
|
||||
{
|
||||
public UnitType type { get; set; }
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
using OmniLinkBridge.MQTT.HomeAssistant;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class OverrideZone
|
||||
{
|
||||
|
9
OmniLinkBridge/MQTT/Parser/AlarmCommands.cs
Normal file
9
OmniLinkBridge/MQTT/Parser/AlarmCommands.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum AlarmCommands
|
||||
{
|
||||
burglary = 1,
|
||||
fire = 2,
|
||||
auxiliary = 3
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum AreaCommands
|
||||
{
|
||||
@ -6,9 +6,9 @@
|
||||
arm_home,
|
||||
arm_away,
|
||||
arm_night,
|
||||
arm_vacation,
|
||||
// The below aren't supported by Home Assistant
|
||||
arm_home_instant,
|
||||
arm_night_delay,
|
||||
arm_vacation
|
||||
arm_night_delay
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum CommandTypes
|
||||
{
|
||||
@ -7,6 +7,8 @@
|
||||
unit,
|
||||
thermostat,
|
||||
button,
|
||||
message
|
||||
message,
|
||||
@lock,
|
||||
audio
|
||||
}
|
||||
}
|
8
OmniLinkBridge/MQTT/Parser/LockCommands.cs
Normal file
8
OmniLinkBridge/MQTT/Parser/LockCommands.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum LockCommands
|
||||
{
|
||||
@lock,
|
||||
unlock
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum MessageCommands
|
||||
{
|
@ -1,14 +1,20 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
public enum Topic
|
||||
{
|
||||
name,
|
||||
status,
|
||||
state,
|
||||
command,
|
||||
alarm_command,
|
||||
basic_state,
|
||||
json_state,
|
||||
brightness_state,
|
||||
brightness_command,
|
||||
flag_state,
|
||||
flag_command,
|
||||
scene_state,
|
||||
scene_command,
|
||||
current_operation,
|
||||
current_temperature,
|
||||
current_humidity,
|
||||
@ -21,10 +27,17 @@
|
||||
dehumidify_state,
|
||||
dehumidify_command,
|
||||
mode_state,
|
||||
mode_basic_state,
|
||||
mode_command,
|
||||
fan_mode_state,
|
||||
fan_mode_command,
|
||||
hold_state,
|
||||
hold_command
|
||||
hold_command,
|
||||
mute_state,
|
||||
mute_command,
|
||||
source_state,
|
||||
source_command,
|
||||
volume_state,
|
||||
volume_command,
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum UnitCommands
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum ZoneCommands
|
||||
{
|
@ -1,7 +0,0 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Switch : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
}
|
||||
}
|
9
OmniLinkBridge/MQTT/UnitType.cs
Normal file
9
OmniLinkBridge/MQTT/UnitType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public enum UnitType
|
||||
{
|
||||
@switch,
|
||||
light,
|
||||
number
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using log4net;
|
||||
using HAI_Shared;
|
||||
using OmniLinkBridge.Notifications;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
@ -12,7 +14,9 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class LoggerModule : IModule
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool running = true;
|
||||
|
||||
private readonly OmniLinkII omnilink;
|
||||
private readonly List<string> alarms = new List<string>();
|
||||
@ -29,11 +33,14 @@ namespace OmniLinkBridge.Modules
|
||||
public LoggerModule(OmniLinkII omni)
|
||||
{
|
||||
omnilink = omni;
|
||||
omnilink.OnConnect += Omnilink_OnConnect;
|
||||
omnilink.OnAreaStatus += Omnilink_OnAreaStatus;
|
||||
omnilink.OnZoneStatus += Omnilink_OnZoneStatus;
|
||||
omnilink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
omnilink.OnUnitStatus += Omnilink_OnUnitStatus;
|
||||
omnilink.OnMessageStatus += Omnilink_OnMessageStatus;
|
||||
omnilink.OnLockStatus += Omnilink_OnLockStatus;
|
||||
omnilink.OnAudioZoneStatus += Omnilink_OnAudioZoneStatus;
|
||||
omnilink.OnSystemStatus += Omnilink_OnSystemStatus;
|
||||
}
|
||||
|
||||
@ -41,26 +48,23 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
if (Global.mysql_logging)
|
||||
{
|
||||
log.Info("Connecting to database");
|
||||
log.Warning("MySQL logging is deprecated");
|
||||
log.Information("Connecting to database");
|
||||
|
||||
mysql_conn = new OdbcConnection(Global.mysql_connection);
|
||||
|
||||
// Must make an initial connection
|
||||
if (!DBOpen())
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// End gracefully when not logging or database queue empty
|
||||
if (!Global.running && (!Global.mysql_logging || DBQueueCount() == 0))
|
||||
if (!running && (!Global.mysql_logging || DBQueueCount() == 0))
|
||||
break;
|
||||
|
||||
// Make sure database connection is active
|
||||
if (Global.mysql_logging && mysql_conn.State != ConnectionState.Open)
|
||||
{
|
||||
// Nothing we can do if shutting down
|
||||
if (!Global.running)
|
||||
if (!running)
|
||||
break;
|
||||
|
||||
if (mysql_retry < DateTime.Now)
|
||||
@ -100,11 +104,11 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
if (mysql_conn.State != ConnectionState.Open)
|
||||
{
|
||||
log.Warn("Lost connection to database");
|
||||
log.Warning("Lost connection to database");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error("Error executing query\r\n" + query, ex);
|
||||
log.Error(ex, "Error executing {query}", query);
|
||||
|
||||
// Prevent an endless loop from failed query
|
||||
lock (mysql_lock)
|
||||
@ -119,67 +123,143 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
running = false;
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
private void Omnilink_OnConnect(object sender, EventArgs e)
|
||||
{
|
||||
ushort areaUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.Areas.Count; i++)
|
||||
{
|
||||
clsArea area = omnilink.Controller.Areas[i];
|
||||
|
||||
if (i > 1 && area.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
areaUsage++;
|
||||
|
||||
if (Global.verbose_area)
|
||||
{
|
||||
string status = area.ModeText();
|
||||
|
||||
if (area.ExitTimer > 0)
|
||||
status = "ARMING " + status;
|
||||
|
||||
if (area.EntryTimer > 0)
|
||||
status = "TRIPPED " + status;
|
||||
|
||||
log.Verbose("Initial AreaStatus {id} {name}, Status: {status}, Alarms: {alarms}", i, area.Name, status, area.AreaAlarms);
|
||||
}
|
||||
}
|
||||
|
||||
ushort zoneUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = omnilink.Controller.Zones[i];
|
||||
|
||||
if (zone.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
zoneUsage++;
|
||||
|
||||
if (Global.verbose_zone)
|
||||
{
|
||||
if (zone.IsTemperatureZone())
|
||||
log.Verbose("Initial ZoneStatus {id} {name}, Temp: {temp}", i, zone.Name, zone.TempText());
|
||||
else
|
||||
log.Verbose("Initial ZoneStatus {id} {name}, Status: {status}", i, zone.Name, zone.StatusText());
|
||||
}
|
||||
}
|
||||
|
||||
ushort unitUsage = 0, outputUsage = 0, flagUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.Units.Count; i++)
|
||||
{
|
||||
clsUnit unit = omnilink.Controller.Units[i];
|
||||
|
||||
if (unit.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
if (unit.Type == enuOL2UnitType.Output)
|
||||
outputUsage++;
|
||||
else if (unit.Type == enuOL2UnitType.Flag)
|
||||
flagUsage++;
|
||||
else
|
||||
unitUsage++;
|
||||
|
||||
if (Global.verbose_unit)
|
||||
log.Verbose("Initial UnitStatus {id} {name}, Status: {status}", i, unit.Name, unit.StatusText);
|
||||
}
|
||||
|
||||
ushort thermostatUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.Thermostats.Count; i++)
|
||||
{
|
||||
clsThermostat thermostat = omnilink.Controller.Thermostats[i];
|
||||
|
||||
if (thermostat.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
thermostatUsage++;
|
||||
}
|
||||
|
||||
ushort lockUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.AccessControlReaders.Count; i++)
|
||||
{
|
||||
clsAccessControlReader reader = omnilink.Controller.AccessControlReaders[i];
|
||||
|
||||
if (reader.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
lockUsage++;
|
||||
|
||||
if(Global.verbose_lock)
|
||||
log.Verbose("Initial LockStatus {id} {name}, Status: {status}", i, reader.Name, reader.LockStatusText());
|
||||
}
|
||||
|
||||
ushort audioSourceUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.AudioSources.Count; i++)
|
||||
{
|
||||
clsAudioSource audioSource = omnilink.Controller.AudioSources[i];
|
||||
|
||||
if (audioSource.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
audioSourceUsage++;
|
||||
|
||||
if (Global.verbose_audio)
|
||||
log.Verbose("Initial AudioSource {id} {name}", i, audioSource.rawName);
|
||||
}
|
||||
|
||||
ushort audioZoneUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.AudioZones.Count; i++)
|
||||
{
|
||||
clsAudioZone audioZone = omnilink.Controller.AudioZones[i];
|
||||
|
||||
if (audioZone.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
audioZoneUsage++;
|
||||
|
||||
if (Global.verbose_audio)
|
||||
log.Verbose("Initial AudioZoneStatus {id} {name}, Power: {power}, Source: {source}, Volume: {volume}, Mute: {mute}",
|
||||
i, audioZone.rawName, audioZone.Power, audioZone.Source, audioZone.Volume, audioZone.Mute);
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Telemetry", "ControllerUsage"))
|
||||
log.Debug("Controller has {AreaUsage} areas, {ZoneUsage} zones, {UnitUsage} units, " +
|
||||
"{OutputUsage} outputs, {FlagUsage} flags, {ThermostatUsage} thermostats, {LockUsage} locks, " +
|
||||
"{AudioSourceUsage} audio sources, {AudioZoneUsage} audio zones",
|
||||
areaUsage, zoneUsage, unitUsage, outputUsage, flagUsage, thermostatUsage, lockUsage,
|
||||
audioSourceUsage, audioZoneUsage);
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
// Alarm notifcation
|
||||
if (e.Area.AreaFireAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "FIRE " + e.Area.Name + " " + e.Area.AreaFireAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("FIRE" + e.ID))
|
||||
alarms.Add("FIRE" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("FIRE" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "FIRE " + e.Area.Name + " " + e.Area.AreaFireAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("FIRE" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaBurglaryAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "BURGLARY " + e.Area.Name + " " + e.Area.AreaBurglaryAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("BURGLARY" + e.ID))
|
||||
alarms.Add("BURGLARY" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("BURGLARY" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "BURGLARY " + e.Area.Name + " " + e.Area.AreaBurglaryAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("BURGLARY" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaAuxAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "AUX " + e.Area.Name + " " + e.Area.AreaAuxAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("AUX" + e.ID))
|
||||
alarms.Add("AUX" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("AUX" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "AUX " + e.Area.Name + " " + e.Area.AreaAuxAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("AUX" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaDuressAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "DURESS " + e.Area.Name + " " + e.Area.AreaDuressAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("DURESS" + e.ID))
|
||||
alarms.Add("DURESS" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("DURESS" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "DURESS " + e.Area.Name + " " + e.Area.AreaDuressAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("DURESS" + e.ID);
|
||||
}
|
||||
AlarmNotification(e, 0, "BURGLARY", e.Area.AreaBurglaryAlarmText);
|
||||
AlarmNotification(e, 1, "FIRE", e.Area.AreaFireAlarmText);
|
||||
AlarmNotification(e, 2, "GAS", e.Area.AreaGasAlarmText);
|
||||
AlarmNotification(e, 3, "AUX", e.Area.AreaAuxAlarmText);
|
||||
AlarmNotification(e, 6, "DURESS", e.Area.AreaDuressAlarmText);
|
||||
|
||||
string status = e.Area.ModeText();
|
||||
|
||||
@ -198,12 +278,29 @@ namespace OmniLinkBridge.Modules
|
||||
e.Area.AreaDuressAlarmText + "','" + status + "')");
|
||||
|
||||
if (Global.verbose_area)
|
||||
log.Debug("AreaStatus " + e.ID + " " + e.Area.Name + ", Status: " + status);
|
||||
log.Verbose("AreaStatus {id} {name}, Status: {status}, Alarms: {alarms}", e.ID, e.Area.Name, status, e.Area.AreaAlarms);
|
||||
|
||||
if (Global.notify_area && e.Area.LastMode != e.Area.AreaMode)
|
||||
Notification.Notify("Security", e.Area.Name + " " + e.Area.ModeText());
|
||||
}
|
||||
|
||||
private void AlarmNotification(AreaStatusEventArgs e, int alarmBit, string alarmType, string alarmText)
|
||||
{
|
||||
if (e.Area.AreaAlarms.IsBitSet(alarmBit))
|
||||
{
|
||||
Notification.Notify("ALARM", $"{alarmType} {e.Area.Name} {alarmText}", NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains(alarmType + e.ID))
|
||||
alarms.Add(alarmType + e.ID);
|
||||
}
|
||||
else if (alarms.Contains(alarmType + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", $"{alarmType} {e.Area.Name} {alarmText}", NotificationPriority.High);
|
||||
|
||||
alarms.Remove(alarmType + e.ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void Omnilink_OnZoneStatus(object sender, ZoneStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
@ -213,9 +310,9 @@ namespace OmniLinkBridge.Modules
|
||||
if (Global.verbose_zone)
|
||||
{
|
||||
if (e.Zone.IsTemperatureZone())
|
||||
log.Debug("ZoneStatus " + e.ID + " " + e.Zone.Name + ", Temp: " + e.Zone.TempText());
|
||||
log.Verbose("ZoneStatus {id} {name}, Temp: {temp}", e.ID, e.Zone.Name, e.Zone.TempText());
|
||||
else
|
||||
log.Debug("ZoneStatus " + e.ID + " " + e.Zone.Name + ", Status: " + e.Zone.StatusText());
|
||||
log.Verbose("ZoneStatus {id} {name}, Status: {status}", e.ID, e.Zone.Name, e.Zone.StatusText());
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,15 +336,21 @@ namespace OmniLinkBridge.Modules
|
||||
humidity + "','" + humidify + "','" + dehumidify + "','" +
|
||||
e.Thermostat.ModeText() + "','" + e.Thermostat.FanModeText() + "','" + e.Thermostat.HoldStatusText() + "')");
|
||||
|
||||
if (e.Offline)
|
||||
log.Warning("Unknown temp for Thermostat {thermostatName}, verify thermostat is online",
|
||||
e.Thermostat.Name);
|
||||
|
||||
// Ignore events fired by thermostat polling
|
||||
if (!e.EventTimer && Global.verbose_thermostat)
|
||||
log.Debug("ThermostatStatus " + e.ID + " " + e.Thermostat.Name +
|
||||
", Status: " + e.Thermostat.TempText() + " " + e.Thermostat.HorC_StatusText() +
|
||||
", Heat: " + e.Thermostat.HeatSetpointText() +
|
||||
", Cool: " + e.Thermostat.CoolSetpointText() +
|
||||
", Mode: " + e.Thermostat.ModeText() +
|
||||
", Fan: " + e.Thermostat.FanModeText() +
|
||||
", Hold: " + e.Thermostat.HoldStatusText());
|
||||
log.Verbose("ThermostatStatus {id} {name}, Status: {temp} {status}, " +
|
||||
"Heat {heat}, Cool: {cool}, Mode: {mode}, Fan: {fan}, Hold: {hold}",
|
||||
e.ID, e.Thermostat.Name,
|
||||
e.Thermostat.TempText(), e.Thermostat.HorC_StatusText(),
|
||||
e.Thermostat.HeatSetpointText(),
|
||||
e.Thermostat.CoolSetpointText(),
|
||||
e.Thermostat.ModeText(),
|
||||
e.Thermostat.FanModeText(),
|
||||
e.Thermostat.HoldStatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnUnitStatus(object sender, UnitStatusEventArgs e)
|
||||
@ -266,7 +369,7 @@ namespace OmniLinkBridge.Modules
|
||||
status + "','" + e.Unit.Status + "','" + e.Unit.StatusTime + "')");
|
||||
|
||||
if (Global.verbose_unit)
|
||||
log.Debug("UnitStatus " + e.ID + " " + e.Unit.Name + ", Status: " + status);
|
||||
log.Verbose("UnitStatus {id} {name}, Status: {status}, Value: {value}", e.ID, e.Unit.Name, status, e.Unit.Status);
|
||||
}
|
||||
|
||||
private void Omnilink_OnMessageStatus(object sender, MessageStatusEventArgs e)
|
||||
@ -276,12 +379,25 @@ namespace OmniLinkBridge.Modules
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID + "','" + e.Message.Name + "','" + e.Message.StatusText() + "')");
|
||||
|
||||
if (Global.verbose_message)
|
||||
log.Debug("MessageStatus " + e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());
|
||||
log.Verbose("MessageStatus {id} {name}, Status: {status}", e.ID, e.Message.Name, e.Message.StatusText());
|
||||
|
||||
if (Global.notify_message)
|
||||
Notification.Notify("Message", e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnLockStatus(object sender, LockStatusEventArgs e)
|
||||
{
|
||||
if (Global.verbose_lock)
|
||||
log.Verbose("LockStatus {id} {name}, Status: {status}", e.ID, e.Reader.Name, e.Reader.LockStatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnAudioZoneStatus(object sender, AudioZoneStatusEventArgs e)
|
||||
{
|
||||
if (Global.verbose_audio)
|
||||
log.Verbose("AudioZoneStatus {id} {name}, Power: {power}, Source: {source}, Volume: {volume}, Mute: {mute}",
|
||||
e.ID, e.AudioZone.rawName, e.AudioZone.Power, e.AudioZone.Source, e.AudioZone.Volume, e.AudioZone.Mute);
|
||||
}
|
||||
|
||||
private void Omnilink_OnSystemStatus(object sender, SystemStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
@ -289,7 +405,7 @@ namespace OmniLinkBridge.Modules
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.Type.ToString() + "','" + e.Value + "')");
|
||||
|
||||
if (Global.verbose_event)
|
||||
log.Debug("SystemEvent " + e.Type.ToString() + " " + e.Value);
|
||||
log.Verbose("SystemEvent {name}, Status: {status}", e.Type.ToString(), e.Value);
|
||||
|
||||
if (e.SendNotification)
|
||||
Notification.Notify("SystemEvent", e.Type.ToString() + " " + e.Value);
|
||||
@ -306,7 +422,7 @@ namespace OmniLinkBridge.Modules
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to connect to database", ex);
|
||||
log.Error(ex, "Failed to connect to database");
|
||||
mysql_retry = DateTime.Now.AddMinutes(1);
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using HAI_Shared;
|
||||
using log4net;
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Connecting;
|
||||
@ -10,9 +9,13 @@ using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Protocol;
|
||||
using Newtonsoft.Json;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using OmniLinkBridge.MQTT.HomeAssistant;
|
||||
using OmniLinkBridge.MQTT.Parser;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@ -22,7 +25,7 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class MQTTModule : IModule
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public static DeviceRegistry MqttDeviceRegistry { get; set; }
|
||||
|
||||
@ -31,8 +34,16 @@ namespace OmniLinkBridge.Modules
|
||||
private bool ControllerConnected { get; set; }
|
||||
private MessageProcessor MessageProcessor { get; set; }
|
||||
|
||||
private Dictionary<string, int> AudioSources { get; set; } = new Dictionary<string, int>();
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
||||
private const string ONLINE = "online";
|
||||
private const string OFFLINE = "offline";
|
||||
|
||||
private const string SECURE = "secure";
|
||||
private const string TROUBLE = "trouble";
|
||||
|
||||
public MQTTModule(OmniLinkII omni)
|
||||
{
|
||||
OmniLink = omni;
|
||||
@ -44,8 +55,11 @@ namespace OmniLinkBridge.Modules
|
||||
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
OmniLink.OnButtonStatus += OmniLink_OnButtonStatus;
|
||||
OmniLink.OnMessageStatus += OmniLink_OnMessageStatus;
|
||||
OmniLink.OnLockStatus += OmniLink_OnLockStatus;
|
||||
OmniLink.OnAudioZoneStatus += OmniLink_OnAudioZoneStatus;
|
||||
OmniLink.OnSystemStatus += OmniLink_OnSystemStatus;
|
||||
|
||||
MessageProcessor = new MessageProcessor(omni);
|
||||
MessageProcessor = new MessageProcessor(omni, AudioSources, omni.Controller.CAP.numAudioZones);
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
@ -53,7 +67,7 @@ namespace OmniLinkBridge.Modules
|
||||
MqttApplicationMessage lastwill = new MqttApplicationMessage()
|
||||
{
|
||||
Topic = $"{Global.mqtt_prefix}/status",
|
||||
Payload = Encoding.UTF8.GetBytes("offline"),
|
||||
Payload = Encoding.UTF8.GetBytes(OFFLINE),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
|
||||
Retain = true
|
||||
};
|
||||
@ -80,7 +94,7 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
identifiers = Global.mqtt_prefix,
|
||||
name = Global.mqtt_prefix,
|
||||
sw_version = $"{OmniLink.Controller.GetVersionText()} - OmniLinkBridge {Assembly.GetExecutingAssembly().GetName().Version.ToString()}",
|
||||
sw_version = $"{OmniLink.Controller.GetVersionText()} - OmniLinkBridge {Assembly.GetExecutingAssembly().GetName().Version}",
|
||||
model = OmniLink.Controller.GetModelText(),
|
||||
manufacturer = "Leviton"
|
||||
};
|
||||
@ -90,10 +104,10 @@ namespace OmniLinkBridge.Modules
|
||||
if (ControllerConnected)
|
||||
PublishConfig();
|
||||
});
|
||||
MqttClient.ConnectingFailedHandler = new ConnectingFailedHandlerDelegate((e) => log.Debug("Error connecting " + e.Exception.Message));
|
||||
MqttClient.ConnectingFailedHandler = new ConnectingFailedHandlerDelegate((e) => log.Error("Error connecting {reason}", e.Exception.Message));
|
||||
MqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate((e) => log.Debug("Disconnected"));
|
||||
|
||||
MqttClient.StartAsync(manoptions);
|
||||
MqttClient.StartAsync(manoptions).Wait();
|
||||
|
||||
MqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate((e) =>
|
||||
MessageProcessor.Process(e.ApplicationMessage.Topic, Encoding.UTF8.GetString(e.ApplicationMessage.Payload)));
|
||||
@ -102,26 +116,31 @@ namespace OmniLinkBridge.Modules
|
||||
List<Topic> toSubscribe = new List<Topic>()
|
||||
{
|
||||
Topic.command,
|
||||
Topic.alarm_command,
|
||||
Topic.brightness_command,
|
||||
Topic.flag_command,
|
||||
Topic.scene_command,
|
||||
Topic.temperature_heat_command,
|
||||
Topic.temperature_cool_command,
|
||||
Topic.humidify_command,
|
||||
Topic.dehumidify_command,
|
||||
Topic.mode_command,
|
||||
Topic.fan_mode_command,
|
||||
Topic.hold_command
|
||||
Topic.hold_command,
|
||||
Topic.mute_command,
|
||||
Topic.source_command,
|
||||
Topic.volume_command
|
||||
};
|
||||
|
||||
toSubscribe.ForEach((command) => MqttClient.SubscribeAsync(
|
||||
new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{command.ToString()}").Build()));
|
||||
new MqttTopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{command}").Build()));
|
||||
|
||||
// Wait until shutdown
|
||||
trigger.WaitOne();
|
||||
|
||||
log.Debug("Publishing controller offline");
|
||||
PublishAsync($"{Global.mqtt_prefix}/status", "offline");
|
||||
PublishControllerStatus(OFFLINE);
|
||||
|
||||
MqttClient.StopAsync();
|
||||
MqttClient.StopAsync().Wait();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
@ -142,79 +161,122 @@ namespace OmniLinkBridge.Modules
|
||||
ControllerConnected = false;
|
||||
|
||||
if (MqttClient.IsConnected)
|
||||
{
|
||||
log.Debug("Publishing controller offline");
|
||||
PublishAsync($"{Global.mqtt_prefix}/status", "offline");
|
||||
}
|
||||
PublishControllerStatus(OFFLINE);
|
||||
}
|
||||
|
||||
private void PublishControllerStatus(string status)
|
||||
{
|
||||
log.Information("Publishing controller {status}", status);
|
||||
PublishAsync($"{Global.mqtt_prefix}/{Topic.status}", status);
|
||||
}
|
||||
|
||||
private void PublishConfig()
|
||||
{
|
||||
PublishSystem();
|
||||
PublishAreas();
|
||||
PublishZones();
|
||||
PublishUnits();
|
||||
PublishThermostats();
|
||||
PublishButtons();
|
||||
PublishMessages();
|
||||
PublishLocks();
|
||||
PublishAudioSources();
|
||||
PublishAudioZones();
|
||||
|
||||
log.Debug("Publishing controller online");
|
||||
PublishAsync($"{Global.mqtt_prefix}/status", "online");
|
||||
PublishControllerStatus(ONLINE);
|
||||
PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText());
|
||||
PublishAsync($"{Global.mqtt_prefix}/version", OmniLink.Controller.GetVersionText());
|
||||
}
|
||||
|
||||
private void PublishSystem()
|
||||
{
|
||||
log.Debug("Publishing {type}", "system");
|
||||
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_phone/config",
|
||||
JsonConvert.SerializeObject(SystemTroubleConfig("phone", "Phone")));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_ac/config",
|
||||
JsonConvert.SerializeObject(SystemTroubleConfig("ac", "AC")));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_battery/config",
|
||||
JsonConvert.SerializeObject(SystemTroubleConfig("battery", "Battery")));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_dcm/config",
|
||||
JsonConvert.SerializeObject(SystemTroubleConfig("dcm", "DCM")));
|
||||
|
||||
PublishAsync(SystemTroubleTopic("phone"), OmniLink.TroublePhone ? TROUBLE : SECURE);
|
||||
PublishAsync(SystemTroubleTopic("ac"), OmniLink.TroubleAC ? TROUBLE : SECURE);
|
||||
PublishAsync(SystemTroubleTopic("battery"), OmniLink.TroubleBattery ? TROUBLE : SECURE);
|
||||
PublishAsync(SystemTroubleTopic("dcm"), OmniLink.TroubleDCM ? TROUBLE : SECURE);
|
||||
}
|
||||
|
||||
public string SystemTroubleTopic(string type)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/system/{type}/{Topic.state}";
|
||||
}
|
||||
|
||||
public BinarySensor SystemTroubleConfig(string type, string name)
|
||||
{
|
||||
return new BinarySensor(MQTTModule.MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}system{type}",
|
||||
name = $"{Global.mqtt_discovery_name_prefix}System {name}",
|
||||
state_topic = SystemTroubleTopic(type),
|
||||
device_class = BinarySensor.DeviceClass.problem,
|
||||
payload_off = SECURE,
|
||||
payload_on = TROUBLE
|
||||
};
|
||||
}
|
||||
|
||||
private void PublishAreas()
|
||||
{
|
||||
log.Debug("Publishing areas");
|
||||
log.Debug("Publishing {type}", "areas");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Areas.Count; i++)
|
||||
{
|
||||
clsArea area = OmniLink.Controller.Areas[i];
|
||||
|
||||
// PC Access doesn't let you customize the area name for the Omni LTe or Omni IIe
|
||||
// (configured for 1 area). To workaround ignore default properties for the first area.
|
||||
// PC Access doesn't let you customize the area name when configured for one area.
|
||||
// Ignore default properties for the first area.
|
||||
if (i > 1 && area.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(area.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}burglary/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}fire/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}gas/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}aux/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}freeze/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}water/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}duress/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}temp/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishAreaState(area);
|
||||
|
||||
PublishAsync(area.ToTopic(Topic.name), area.Name);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i}/config",
|
||||
JsonConvert.SerializeObject(area.ToConfig()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}burglary/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigBurglary()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}fire/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigFire()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}gas/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigGas()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}aux/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigAux()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}freeze/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigFreeze()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}water/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigWater()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}duress/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigDuress()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i}temp/config",
|
||||
JsonConvert.SerializeObject(area.ToConfigTemp()));
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishZones()
|
||||
{
|
||||
log.Debug("Publishing zones");
|
||||
log.Debug("Publishing {type}", "zones");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
@ -232,34 +294,38 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
if (zone.DefaultProperties == true || Global.mqtt_discovery_ignore_zones.Contains(zone.Number))
|
||||
{
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/zone{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}temp/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}humidity/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfig()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigSensor()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/zone{i}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigSwitch()));
|
||||
|
||||
if (zone.IsTemperatureZone())
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}temp/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigTemp(OmniLink.Controller.TempFormat)));
|
||||
else if (zone.IsHumidityZone())
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i}humidity/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigHumidity()));
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishUnits()
|
||||
{
|
||||
log.Debug("Publishing units");
|
||||
log.Debug("Publishing {type}", "units");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Units.Count; i++)
|
||||
{
|
||||
clsUnit unit = OmniLink.Controller.Units[i];
|
||||
UnitType unitType = unit.ToUnitType();
|
||||
|
||||
if (unit.DefaultProperties == true)
|
||||
{
|
||||
@ -273,23 +339,32 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
|
||||
{
|
||||
string type = i < 385 ? "light" : "switch";
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/{Global.mqtt_prefix}/unit{i.ToString()}/config", null);
|
||||
foreach(UnitType entry in Enum.GetValues(typeof(UnitType)))
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{entry}/{Global.mqtt_prefix}/unit{i}/config", null);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < 385)
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/light/{Global.mqtt_prefix}/unit{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(unit.ToConfig()));
|
||||
else
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/unit{i.ToString()}/config",
|
||||
foreach (UnitType entry in Enum.GetValues(typeof(UnitType)).Cast<UnitType>().Where(x => x != unitType))
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{entry}/{Global.mqtt_prefix}/unit{i}/config", null);
|
||||
|
||||
log.Verbose("Publishing {type} {id} {name} as {unitType}", "units", i, unit.Name, unitType);
|
||||
|
||||
if (unitType == UnitType.@switch)
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
|
||||
JsonConvert.SerializeObject(unit.ToConfigSwitch()));
|
||||
else if (unitType == UnitType.light)
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
|
||||
JsonConvert.SerializeObject(unit.ToConfig()));
|
||||
else if (unitType == UnitType.number)
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
|
||||
JsonConvert.SerializeObject(unit.ToConfigNumber()));
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishThermostats()
|
||||
{
|
||||
log.Debug("Publishing thermostats");
|
||||
log.Debug("Publishing {type}", "thermostats");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Thermostats.Count; i++)
|
||||
{
|
||||
@ -298,27 +373,37 @@ namespace OmniLinkBridge.Modules
|
||||
if (thermostat.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(thermostat.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}humidify/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}dehumidify/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i}temp/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i}humidity/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishThermostatState(thermostat);
|
||||
|
||||
PublishAsync(thermostat.ToTopic(Topic.name), thermostat.Name);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config",
|
||||
PublishAsync(thermostat.ToTopic(Topic.status), ONLINE);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i}/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfig(OmniLink.Controller.TempFormat)));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}humidify/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfigHumidify()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}dehumidify/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfigDehumidify()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i}temp/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfigTemp(OmniLink.Controller.TempFormat)));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config",
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i}humidity/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfigHumidity()));
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishButtons()
|
||||
{
|
||||
log.Debug("Publishing buttons");
|
||||
log.Debug("Publishing {type}", "buttons");
|
||||
|
||||
if (Global.mqtt_discovery_button_type == typeof(Switch))
|
||||
log.Information("See {setting} for new option when publishing {type}", "mqtt_discovery_button_type", "buttons");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Buttons.Count; i++)
|
||||
{
|
||||
@ -327,7 +412,8 @@ namespace OmniLinkBridge.Modules
|
||||
if (button.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(button.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -335,14 +421,25 @@ namespace OmniLinkBridge.Modules
|
||||
PublishAsync(button.ToTopic(Topic.state), "OFF");
|
||||
|
||||
PublishAsync(button.ToTopic(Topic.name), button.Name);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(button.ToConfig()));
|
||||
|
||||
if (Global.mqtt_discovery_button_type == typeof(Switch))
|
||||
{
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config",
|
||||
JsonConvert.SerializeObject(button.ToConfigSwitch()));
|
||||
}
|
||||
else if (Global.mqtt_discovery_button_type == typeof(Button))
|
||||
{
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config",
|
||||
JsonConvert.SerializeObject(button.ToConfigButton()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishMessages()
|
||||
{
|
||||
log.Debug("Publishing messages");
|
||||
log.Debug("Publishing {type}", "messages");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.Messages.Count; i++)
|
||||
{
|
||||
@ -354,12 +451,103 @@ namespace OmniLinkBridge.Modules
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishMessageState(message);
|
||||
PublishMessageStateAsync(message);
|
||||
|
||||
PublishAsync(message.ToTopic(Topic.name), message.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishLocks()
|
||||
{
|
||||
log.Debug("Publishing {type}", "locks");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.AccessControlReaders.Count; i++)
|
||||
{
|
||||
clsAccessControlReader reader = OmniLink.Controller.AccessControlReaders[i];
|
||||
|
||||
if (reader.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(reader.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/lock/{Global.mqtt_prefix}/lock{i}/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishLockStateAsync(reader);
|
||||
|
||||
PublishAsync(reader.ToTopic(Topic.name), reader.Name);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/lock/{Global.mqtt_prefix}/lock{i}/config",
|
||||
JsonConvert.SerializeObject(reader.ToConfig()));
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishAudioSources()
|
||||
{
|
||||
log.Debug("Publishing {type}", "audio sources");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.AudioSources.Count; i++)
|
||||
{
|
||||
clsAudioSource audioSource = OmniLink.Controller.AudioSources[i];
|
||||
|
||||
if (audioSource.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(audioSource.ToTopic(Topic.name), null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishAsync(audioSource.ToTopic(Topic.name), audioSource.rawName);
|
||||
|
||||
if (AudioSources.ContainsKey(audioSource.rawName))
|
||||
{
|
||||
log.Warning("Duplicate audio source name {name}", audioSource.rawName);
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioSources.Add(audioSource.rawName, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishAudioZones()
|
||||
{
|
||||
log.Debug("Publishing {type}", "audio zones");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.AudioZones.Count; i++)
|
||||
{
|
||||
clsAudioZone audioZone = OmniLink.Controller.AudioZones[i];
|
||||
|
||||
if (audioZone.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(audioZone.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/audio{i}/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/audio{i}mute/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/select/{Global.mqtt_prefix}/audio{i}source/config", null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/audio{i}volume/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishAudioZoneStateAsync(audioZone);
|
||||
|
||||
PublishAsync(audioZone.ToTopic(Topic.name), audioZone.rawName);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/audio{i}/config",
|
||||
JsonConvert.SerializeObject(audioZone.ToConfig()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/audio{i}mute/config",
|
||||
JsonConvert.SerializeObject(audioZone.ToConfigMute()));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/select/{Global.mqtt_prefix}/audio{i}source/config",
|
||||
JsonConvert.SerializeObject(audioZone.ToConfigSource(new List<string>(AudioSources.Keys))));
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/audio{i}volume/config",
|
||||
JsonConvert.SerializeObject(audioZone.ToConfigVolume()));
|
||||
}
|
||||
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/audio0/config",
|
||||
JsonConvert.SerializeObject(new Button(MqttDeviceRegistry)
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}audio0",
|
||||
name = Global.mqtt_discovery_name_prefix + "Audio All Off",
|
||||
icon = "mdi:speaker",
|
||||
command_topic = $"{Global.mqtt_prefix}/audio0/{Topic.command}",
|
||||
payload_press = "OFF"
|
||||
}));
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
@ -416,8 +604,17 @@ namespace OmniLinkBridge.Modules
|
||||
return;
|
||||
|
||||
// Ignore events fired by thermostat polling
|
||||
if (!e.EventTimer)
|
||||
PublishThermostatState(e.Thermostat);
|
||||
if (e.EventTimer)
|
||||
return;
|
||||
|
||||
if (e.Offline)
|
||||
{
|
||||
PublishAsync(e.Thermostat.ToTopic(Topic.status), OFFLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
PublishAsync(e.Thermostat.ToTopic(Topic.status), ONLINE);
|
||||
PublishThermostatState(e.Thermostat);
|
||||
}
|
||||
|
||||
private async void OmniLink_OnButtonStatus(object sender, ButtonStatusEventArgs e)
|
||||
@ -425,7 +622,7 @@ namespace OmniLinkBridge.Modules
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
await PublishButtonState(e.Button);
|
||||
await PublishButtonStateAsync(e.Button);
|
||||
}
|
||||
|
||||
private void OmniLink_OnMessageStatus(object sender, MessageStatusEventArgs e)
|
||||
@ -433,7 +630,38 @@ namespace OmniLinkBridge.Modules
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
PublishMessageState(e.Message);
|
||||
PublishMessageStateAsync(e.Message);
|
||||
}
|
||||
|
||||
private void OmniLink_OnLockStatus(object sender, LockStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
PublishLockStateAsync(e.Reader);
|
||||
}
|
||||
|
||||
private void OmniLink_OnAudioZoneStatus(object sender, AudioZoneStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
PublishAudioZoneStateAsync(e.AudioZone);
|
||||
}
|
||||
|
||||
private void OmniLink_OnSystemStatus(object sender, SystemStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
if(e.Type == SystemEventType.Phone)
|
||||
PublishAsync(SystemTroubleTopic("phone"), e.Trouble ? TROUBLE : SECURE);
|
||||
else if (e.Type == SystemEventType.AC)
|
||||
PublishAsync(SystemTroubleTopic("ac"), e.Trouble ? TROUBLE : SECURE);
|
||||
else if (e.Type == SystemEventType.Button)
|
||||
PublishAsync(SystemTroubleTopic("battery"), e.Trouble ? TROUBLE : SECURE);
|
||||
else if (e.Type == SystemEventType.DCM)
|
||||
PublishAsync(SystemTroubleTopic("dcm"), e.Trouble ? TROUBLE : SECURE);
|
||||
}
|
||||
|
||||
private void PublishAreaState(clsArea area)
|
||||
@ -458,8 +686,15 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
PublishAsync(unit.ToTopic(Topic.state), unit.ToState());
|
||||
|
||||
if(unit.Number < 385)
|
||||
if (unit.Type == enuOL2UnitType.Flag)
|
||||
{
|
||||
PublishAsync(unit.ToTopic(Topic.flag_state), ((ushort)unit.Status).ToString());
|
||||
}
|
||||
else if(unit.Type != enuOL2UnitType.Output)
|
||||
{
|
||||
PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString());
|
||||
PublishAsync(unit.ToTopic(Topic.scene_state), unit.ToSceneState());
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishThermostatState(clsThermostat thermostat)
|
||||
@ -471,12 +706,13 @@ namespace OmniLinkBridge.Modules
|
||||
PublishAsync(thermostat.ToTopic(Topic.temperature_cool_state), thermostat.CoolSetpointText());
|
||||
PublishAsync(thermostat.ToTopic(Topic.humidify_state), thermostat.HumidifySetpointText());
|
||||
PublishAsync(thermostat.ToTopic(Topic.dehumidify_state), thermostat.DehumidifySetpointText());
|
||||
PublishAsync(thermostat.ToTopic(Topic.mode_state), thermostat.ModeText().ToLower());
|
||||
PublishAsync(thermostat.ToTopic(Topic.mode_state), thermostat.ToModeState());
|
||||
PublishAsync(thermostat.ToTopic(Topic.mode_basic_state), thermostat.ToModeBasicState());
|
||||
PublishAsync(thermostat.ToTopic(Topic.fan_mode_state), thermostat.FanModeText().ToLower());
|
||||
PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower());
|
||||
}
|
||||
|
||||
private async Task PublishButtonState(clsButton button)
|
||||
private async Task PublishButtonStateAsync(clsButton button)
|
||||
{
|
||||
// Simulate a momentary press
|
||||
await PublishAsync(button.ToTopic(Topic.state), "ON");
|
||||
@ -484,9 +720,23 @@ namespace OmniLinkBridge.Modules
|
||||
await PublishAsync(button.ToTopic(Topic.state), "OFF");
|
||||
}
|
||||
|
||||
private void PublishMessageState(clsMessage message)
|
||||
private Task PublishMessageStateAsync(clsMessage message)
|
||||
{
|
||||
PublishAsync(message.ToTopic(Topic.state), message.ToState());
|
||||
return PublishAsync(message.ToTopic(Topic.state), message.ToState());
|
||||
}
|
||||
|
||||
private Task PublishLockStateAsync(clsAccessControlReader reader)
|
||||
{
|
||||
return PublishAsync(reader.ToTopic(Topic.state), reader.ToState());
|
||||
}
|
||||
|
||||
private void PublishAudioZoneStateAsync(clsAudioZone audioZone)
|
||||
{
|
||||
PublishAsync(audioZone.ToTopic(Topic.state), audioZone.ToState());
|
||||
PublishAsync(audioZone.ToTopic(Topic.mute_state), audioZone.ToMuteState());
|
||||
PublishAsync(audioZone.ToTopic(Topic.source_state),
|
||||
OmniLink.Controller.AudioSources[audioZone.ToSourceState()].rawName);
|
||||
PublishAsync(audioZone.ToTopic(Topic.volume_state), audioZone.ToVolumeState().ToString());
|
||||
}
|
||||
|
||||
private Task PublishAsync(string topic, string payload)
|
||||
|
@ -1,6 +1,7 @@
|
||||
using HAI_Shared;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
@ -12,12 +13,19 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class OmniLinkII : IModule, IOmniLinkII
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private bool running = true;
|
||||
|
||||
// OmniLink Controller
|
||||
public clsHAC Controller { get; private set; }
|
||||
private DateTime retry = DateTime.MinValue;
|
||||
|
||||
public bool TroublePhone { get; set; }
|
||||
public bool TroubleAC { get; set; }
|
||||
public bool TroubleBattery { get; set; }
|
||||
public bool TroubleDCM { get; set; }
|
||||
|
||||
// Thermostats
|
||||
private readonly Dictionary<ushort, DateTime> tstats = new Dictionary<ushort, DateTime>();
|
||||
private readonly System.Timers.Timer tstat_timer = new System.Timers.Timer();
|
||||
@ -32,6 +40,8 @@ namespace OmniLinkBridge.Modules
|
||||
public event EventHandler<UnitStatusEventArgs> OnUnitStatus;
|
||||
public event EventHandler<ButtonStatusEventArgs> OnButtonStatus;
|
||||
public event EventHandler<MessageStatusEventArgs> OnMessageStatus;
|
||||
public event EventHandler<LockStatusEventArgs> OnLockStatus;
|
||||
public event EventHandler<AudioZoneStatusEventArgs> OnAudioZoneStatus;
|
||||
public event EventHandler<SystemStatusEventArgs> OnSystemStatus;
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
@ -43,7 +53,7 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
Controller.Connection.NetworkAddress = address;
|
||||
Controller.Connection.NetworkPort = (ushort)port;
|
||||
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(String.Concat(key1, key2));
|
||||
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(string.Concat(key1, key2));
|
||||
|
||||
Controller.PreferredNetworkProtocol = clsHAC.enuPreferredNetworkProtocol.TCP;
|
||||
Controller.Connection.ConnectionType = enuOmniLinkConnectionType.Network_TCP;
|
||||
@ -54,8 +64,8 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
while (Global.running)
|
||||
{
|
||||
while(running)
|
||||
{
|
||||
// Make sure controller connection is active
|
||||
if (Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Offline &&
|
||||
retry < DateTime.Now)
|
||||
@ -71,11 +81,13 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
running = false;
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2)
|
||||
{
|
||||
log.Verbose("Sending: {command}, Par1: {par1}, Par2: {par2}", Cmd, Par, Pr2);
|
||||
return Controller.SendCommand(Cmd, Par, Pr2);
|
||||
}
|
||||
|
||||
@ -86,202 +98,50 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
retry = DateTime.Now.AddMinutes(1);
|
||||
|
||||
log.Debug("Controller: {connectionStatus}", "Connect");
|
||||
Controller.Connection.Connect(HandleConnectStatus, HandleUnsolicitedPackets);
|
||||
}
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
log.Info("CONNECTION STATUS: Disconnecting");
|
||||
|
||||
if (Controller.Connection.ConnectionState != enuOmniLinkConnectionState.Offline)
|
||||
{
|
||||
log.Debug("Controller: {connectionStatus}", "Disconnect");
|
||||
Controller.Connection.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnectStatus(enuOmniLinkCommStatus CS)
|
||||
{
|
||||
var status = CS.ToString().ToSpaceTitleCase();
|
||||
|
||||
switch (CS)
|
||||
{
|
||||
case enuOmniLinkCommStatus.NoReply:
|
||||
log.Error("CONNECTION STATUS: No Reply");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnrecognizedReply:
|
||||
log.Error("CONNECTION STATUS: Unrecognized Reply");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnsupportedProtocol:
|
||||
log.Error("CONNECTION STATUS: Unsupported Protocol");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ClientSessionTerminated:
|
||||
log.Error("CONNECTION STATUS: Client Session Terminated");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ControllerSessionTerminated:
|
||||
log.Error("CONNECTION STATUS: Controller Session Terminated");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.CannotStartNewSession:
|
||||
log.Error("CONNECTION STATUS: Cannot Start New Session");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.LoginFailed:
|
||||
log.Error("CONNECTION STATUS: Login Failed");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToOpenSocket:
|
||||
log.Error("CONNECTION STATUS: Unable To Open Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToConnect:
|
||||
log.Error("CONNECTION STATUS: Unable To Connect");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketClosed:
|
||||
log.Error("CONNECTION STATUS: Socket Closed");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnexpectedError:
|
||||
log.Error("CONNECTION STATUS: Unexpected Error");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToCreateSocket:
|
||||
log.Error("CONNECTION STATUS: Unable To Create Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Retrying:
|
||||
log.Warn("CONNECTION STATUS: Retrying");
|
||||
case enuOmniLinkCommStatus.Connecting:
|
||||
log.Debug("Controller Status: {connectionStatus}", status);
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Connected:
|
||||
IdentifyController();
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Connecting:
|
||||
log.Info("CONNECTION STATUS: Connecting");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Disconnected:
|
||||
log.Info("CONNECTION STATUS: Disconnected");
|
||||
log.Information("Controller Status: {connectionStatus}", status);
|
||||
OnDisconnect?.Invoke(this, new EventArgs());
|
||||
break;
|
||||
case enuOmniLinkCommStatus.InterruptedFunctionCall:
|
||||
if (Global.running)
|
||||
log.Error("CONNECTION STATUS: Interrupted Function Call");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.PermissionDenied:
|
||||
log.Error("CONNECTION STATUS: Permission Denied");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.BadAddress:
|
||||
log.Error("CONNECTION STATUS: Bad Address");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.InvalidArgument:
|
||||
log.Error("CONNECTION STATUS: Invalid Argument");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.TooManyOpenFiles:
|
||||
log.Error("CONNECTION STATUS: Too Many Open Files");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ResourceTemporarilyUnavailable:
|
||||
log.Error("CONNECTION STATUS: Resource Temporarily Unavailable");
|
||||
if (running)
|
||||
log.Error("Controller Status: {connectionStatus}", status);
|
||||
break;
|
||||
|
||||
case enuOmniLinkCommStatus.Retrying:
|
||||
case enuOmniLinkCommStatus.OperationNowInProgress:
|
||||
log.Warn("CONNECTION STATUS: Operation Now In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.OperationAlreadyInProgress:
|
||||
log.Warn("CONNECTION STATUS: Operation Already In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketOperationOnNonSocket:
|
||||
log.Error("CONNECTION STATUS: Socket Operation On Non Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.DestinationAddressRequired:
|
||||
log.Error("CONNECTION STATUS: Destination Address Required");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.MessgeTooLong:
|
||||
log.Error("CONNECTION STATUS: Message Too Long");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.WrongProtocolType:
|
||||
log.Error("CONNECTION STATUS: Wrong Protocol Type");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.BadProtocolOption:
|
||||
log.Error("CONNECTION STATUS: Bad Protocol Option");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ProtocolNotSupported:
|
||||
log.Error("CONNECTION STATUS: Protocol Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketTypeNotSupported:
|
||||
log.Error("CONNECTION STATUS: Socket Type Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.OperationNotSupported:
|
||||
log.Error("CONNECTION STATUS: Operation Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ProtocolFamilyNotSupported:
|
||||
log.Error("CONNECTION STATUS: Protocol Family Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressFamilyNotSupported:
|
||||
log.Error("CONNECTION STATUS: Address Family Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressInUse:
|
||||
log.Error("CONNECTION STATUS: Address In Use");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressNotAvailable:
|
||||
log.Error("CONNECTION STATUS: Address Not Available");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkIsDown:
|
||||
log.Error("CONNECTION STATUS: Network Is Down");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkIsUnreachable:
|
||||
log.Error("CONNECTION STATUS: Network Is Unreachable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkReset:
|
||||
log.Error("CONNECTION STATUS: Network Reset");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionAborted:
|
||||
log.Error("CONNECTION STATUS: Connection Aborted");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionResetByPeer:
|
||||
log.Error("CONNECTION STATUS: Connection Reset By Peer");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NoBufferSpaceAvailable:
|
||||
log.Error("CONNECTION STATUS: No Buffer Space Available");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AlreadyConnected:
|
||||
log.Warn("CONNECTION STATUS: Already Connected");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NotConnected:
|
||||
log.Error("CONNECTION STATUS: Not Connected");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.CannotSendAfterShutdown:
|
||||
log.Error("CONNECTION STATUS: Cannot Send After Shutdown");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionTimedOut:
|
||||
log.Error("CONNECTION STATUS: Connection Timed Out");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionRefused:
|
||||
log.Error("CONNECTION STATUS: Connection Refused");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostIsDown:
|
||||
log.Error("CONNECTION STATUS: Host Is Down");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostUnreachable:
|
||||
log.Error("CONNECTION STATUS: Host Unreachable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.TooManyProcesses:
|
||||
log.Error("CONNECTION STATUS: Too Many Processes");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkSubsystemIsUnavailable:
|
||||
log.Error("CONNECTION STATUS: Network Subsystem Is Unavailable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnsupportedVersion:
|
||||
log.Error("CONNECTION STATUS: Unsupported Version");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NotInitialized:
|
||||
log.Error("CONNECTION STATUS: Not Initialized");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ShutdownInProgress:
|
||||
log.Error("CONNECTION STATUS: Shutdown In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ClassTypeNotFound:
|
||||
log.Error("CONNECTION STATUS: Class Type Not Found");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostNotFound:
|
||||
log.Error("CONNECTION STATUS: Host Not Found");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostNotFoundTryAgain:
|
||||
log.Error("CONNECTION STATUS: Host Not Found Try Again");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NonRecoverableError:
|
||||
log.Error("CONNECTION STATUS: Non Recoverable Error");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NoDataOfRequestedType:
|
||||
log.Error("CONNECTION STATUS: No Data Of Requested Type");
|
||||
log.Warning("Controller Status: {connectionStatus}", status);
|
||||
break;
|
||||
|
||||
default:
|
||||
log.Error("Controller Status: {connectionStatus}", status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -295,7 +155,7 @@ namespace OmniLinkBridge.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleIdentifyController(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
private async void HandleIdentifyController(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
@ -316,10 +176,12 @@ namespace OmniLinkBridge.Modules
|
||||
if (Controller.Model == MSG.ModelNumber)
|
||||
{
|
||||
Controller.CopySystemInformation(MSG);
|
||||
log.Info("CONTROLLER IS: " + Controller.GetModelText() + " (" + Controller.GetVersionText() + ")");
|
||||
|
||||
_ = Connected();
|
||||
using (LogContext.PushProperty("Telemetry", "Controller"))
|
||||
log.Information("Controller is {ControllerModel} firmware {ControllerVersion}",
|
||||
Controller.GetModelText(), Controller.GetVersionText());
|
||||
|
||||
await ConnectedAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -328,7 +190,7 @@ namespace OmniLinkBridge.Modules
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Connected()
|
||||
private async Task ConnectedAsync()
|
||||
{
|
||||
retry = DateTime.MinValue;
|
||||
|
||||
@ -339,6 +201,8 @@ namespace OmniLinkBridge.Modules
|
||||
tstat_timer.Start();
|
||||
|
||||
OnConnect?.Invoke(this, new EventArgs());
|
||||
|
||||
Program.ShowSendLogsWarning();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -348,42 +212,62 @@ namespace OmniLinkBridge.Modules
|
||||
log.Debug("Retrieving named units");
|
||||
|
||||
await GetSystemFormats();
|
||||
await GetSystemTroubles();
|
||||
await GetNamed(enuObjectType.Area);
|
||||
await GetNamed(enuObjectType.Zone);
|
||||
await GetNamed(enuObjectType.Thermostat);
|
||||
await GetNamed(enuObjectType.Unit);
|
||||
await GetNamed(enuObjectType.Message);
|
||||
await GetNamed(enuObjectType.Button);
|
||||
await GetNamed(enuObjectType.AccessControlReader);
|
||||
await GetNamed(enuObjectType.AudioSource);
|
||||
await GetNamed(enuObjectType.AudioZone);
|
||||
}
|
||||
|
||||
private async Task GetSystemFormats()
|
||||
{
|
||||
log.Debug("Waiting for system formats");
|
||||
|
||||
clsOL2MsgRequestSystemFormats MSG = new clsOL2MsgRequestSystemFormats(Controller.Connection);
|
||||
var MSG = new clsOL2MsgRequestSystemFormats(Controller.Connection);
|
||||
Controller.Connection.Send(MSG, HandleNamedPropertiesResponse);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
nameWait.WaitOne(new TimeSpan(0, 0, 10));
|
||||
if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
|
||||
log.Error("Timeout occurred waiting system formats");
|
||||
});
|
||||
}
|
||||
|
||||
private async Task GetSystemTroubles()
|
||||
{
|
||||
log.Debug("Waiting for system troubles");
|
||||
|
||||
var MSG = new clsOL2MsgRequestSystemTroubles(Controller.Connection);
|
||||
Controller.Connection.Send(MSG, HandleNamedPropertiesResponse);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
|
||||
log.Error("Timeout occurred waiting for system troubles");
|
||||
});
|
||||
}
|
||||
|
||||
private async Task GetNamed(enuObjectType type)
|
||||
{
|
||||
log.Debug("Waiting for named units " + type.ToString());
|
||||
log.Debug("Waiting for named units {unitType}", type.ToString());
|
||||
|
||||
GetNextNamed(type, 0);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
nameWait.WaitOne(new TimeSpan(0, 0, 10));
|
||||
if (!nameWait.WaitOne(new TimeSpan(0, 0, 30)))
|
||||
log.Error("Timeout occurred waiting for named units {unitType}", type.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
private void GetNextNamed(enuObjectType type, int ix)
|
||||
{
|
||||
clsOL2MsgRequestProperties MSG = new clsOL2MsgRequestProperties(Controller.Connection)
|
||||
var MSG = new clsOL2MsgRequestProperties(Controller.Connection)
|
||||
{
|
||||
ObjectType = type,
|
||||
IndexNumber = (UInt16)ix,
|
||||
@ -409,13 +293,25 @@ namespace OmniLinkBridge.Modules
|
||||
nameWait.Set();
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemFormats:
|
||||
clsOL2MsgSystemFormats MSG2 = new clsOL2MsgSystemFormats(Controller.Connection, B);
|
||||
var systemFormats = new clsOL2MsgSystemFormats(Controller.Connection, B);
|
||||
|
||||
Controller.DateFormat = MSG2.Date;
|
||||
Controller.TimeFormat = MSG2.Time;
|
||||
Controller.TempFormat = MSG2.Temp;
|
||||
Controller.DateFormat = systemFormats.Date;
|
||||
Controller.TimeFormat = systemFormats.Time;
|
||||
Controller.TempFormat = systemFormats.Temp;
|
||||
|
||||
log.Debug("Temperature format: " + (Controller.TempFormat == enuTempFormat.Fahrenheit ? "Fahrenheit" : "Celsius"));
|
||||
using (LogContext.PushProperty("Telemetry", "TemperatureFormat"))
|
||||
log.Debug("Temperature format is {TemperatureFormat}",
|
||||
(Controller.TempFormat == enuTempFormat.Fahrenheit ? "Fahrenheit" : "Celsius"));
|
||||
|
||||
nameWait.Set();
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemTroubles:
|
||||
var systemTroubles = new clsOL2MsgSystemTroubles(Controller.Connection, B);
|
||||
|
||||
TroublePhone = systemTroubles.Contains(enuTroubles.PhoneLine);
|
||||
TroubleAC = systemTroubles.Contains(enuTroubles.AC);
|
||||
TroubleBattery = systemTroubles.Contains(enuTroubles.BatteryLow);
|
||||
TroubleDCM = systemTroubles.Contains(enuTroubles.DCM);
|
||||
|
||||
nameWait.Set();
|
||||
break;
|
||||
@ -431,7 +327,8 @@ namespace OmniLinkBridge.Modules
|
||||
Controller.Zones.CopyProperties(MSG);
|
||||
|
||||
if (Controller.Zones[MSG.ObjectNumber].IsTemperatureZone() || Controller.Zones[MSG.ObjectNumber].IsHumidityZone())
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus);
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
|
||||
enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus);
|
||||
|
||||
break;
|
||||
case enuObjectType.Thermostat:
|
||||
@ -442,8 +339,10 @@ namespace OmniLinkBridge.Modules
|
||||
else
|
||||
tstats[MSG.ObjectNumber] = DateTime.MinValue;
|
||||
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
|
||||
log.Debug("Added thermostat to watch list " + Controller.Thermostats[MSG.ObjectNumber].Name);
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
|
||||
enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
|
||||
log.Debug("Added thermostat to watch list {thermostatName}",
|
||||
Controller.Thermostats[MSG.ObjectNumber].Name);
|
||||
break;
|
||||
case enuObjectType.Unit:
|
||||
Controller.Units.CopyProperties(MSG);
|
||||
@ -454,6 +353,17 @@ namespace OmniLinkBridge.Modules
|
||||
case enuObjectType.Button:
|
||||
Controller.Buttons.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.AccessControlReader:
|
||||
Controller.AccessControlReaders.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.AudioSource:
|
||||
Controller.AudioSources.CopyProperties(MSG);
|
||||
Controller.AudioSources[MSG.ObjectNumber].rawName = MSG.ObjectName;
|
||||
break;
|
||||
case enuObjectType.AudioZone:
|
||||
Controller.AudioZones.CopyProperties(MSG);
|
||||
Controller.AudioZones[MSG.ObjectNumber].rawName = MSG.ObjectName;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -471,7 +381,7 @@ namespace OmniLinkBridge.Modules
|
||||
#region Notifications
|
||||
private void UnsolicitedNotifications(bool enable)
|
||||
{
|
||||
log.Info("Unsolicited notifications " + (enable ? "enabled" : "disabled"));
|
||||
log.Debug("Unsolicited notifications {status}", (enable ? "enabled" : "disabled"));
|
||||
Controller.Connection.Send(new clsOL2EnableNotifications(Controller.Connection, enable), null);
|
||||
}
|
||||
|
||||
@ -534,6 +444,8 @@ namespace OmniLinkBridge.Modules
|
||||
case enuOmniLink2MessageType.CmdExtSecurity:
|
||||
break;
|
||||
case enuOmniLink2MessageType.AudioSourceStatus:
|
||||
// Ignore audio source metadata status updates
|
||||
handled = true;
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemEvents:
|
||||
HandleUnsolicitedSystemEvent(B);
|
||||
@ -564,7 +476,7 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
if (MSG.SystemEvent >= 1 && MSG.SystemEvent <= 255)
|
||||
{
|
||||
eventargs.Type = enuEventType.USER_MACRO_BUTTON;
|
||||
eventargs.Type = SystemEventType.Button;
|
||||
eventargs.Value = ((int)MSG.SystemEvent).ToString() + " " + Controller.Buttons[MSG.SystemEvent].Name;
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
@ -575,87 +487,103 @@ namespace OmniLinkBridge.Modules
|
||||
Button = Controller.Buttons[MSG.SystemEvent]
|
||||
});
|
||||
}
|
||||
else if (MSG.SystemEvent >= 768 && MSG.SystemEvent <= 771)
|
||||
else if (MSG.SystemEvent >= (ushort)enuEventType.PHONE_LINE_DEAD &&
|
||||
MSG.SystemEvent <= (ushort)enuEventType.PHONE_LINE_ON_HOOK)
|
||||
{
|
||||
eventargs.Type = enuEventType.PHONE_;
|
||||
eventargs.Type = SystemEventType.Phone;
|
||||
|
||||
if (MSG.SystemEvent == 768)
|
||||
if (MSG.SystemEvent == (ushort)enuEventType.PHONE_)
|
||||
{
|
||||
eventargs.Value = "DEAD";
|
||||
eventargs.Trouble = true;
|
||||
eventargs.SendNotification = true;
|
||||
}
|
||||
else if (MSG.SystemEvent == 769)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.PHONE_LINE_RING)
|
||||
eventargs.Value = "RING";
|
||||
else if (MSG.SystemEvent == 770)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.PHONE_LINE_OFF_HOOK)
|
||||
eventargs.Value = "OFF HOOK";
|
||||
else if (MSG.SystemEvent == 771)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.PHONE_LINE_ON_HOOK)
|
||||
eventargs.Value = "ON HOOK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 772 && MSG.SystemEvent <= 773)
|
||||
else if (MSG.SystemEvent >= (ushort)enuEventType.AC_POWER_OFF &&
|
||||
MSG.SystemEvent <= (ushort)enuEventType.AC_POWER_RESTORED)
|
||||
{
|
||||
eventargs.Type = enuEventType.AC_POWER_;
|
||||
eventargs.Type = SystemEventType.AC;
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 772)
|
||||
if (MSG.SystemEvent == (ushort)enuEventType.AC_POWER_OFF)
|
||||
{
|
||||
eventargs.Value = "OFF";
|
||||
else if (MSG.SystemEvent == 773)
|
||||
eventargs.Trouble = true;
|
||||
}
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.AC_POWER_RESTORED)
|
||||
eventargs.Value = "RESTORED";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 774 && MSG.SystemEvent <= 775)
|
||||
else if (MSG.SystemEvent >= (ushort)enuEventType.BATTERY_LOW &&
|
||||
MSG.SystemEvent <= (ushort)enuEventType.BATTERY_OK)
|
||||
{
|
||||
eventargs.Type = enuEventType.BATTERY_;
|
||||
eventargs.Type = SystemEventType.Battery;
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 774)
|
||||
if (MSG.SystemEvent == (ushort)enuEventType.BATTERY_LOW)
|
||||
{
|
||||
eventargs.Value = "LOW";
|
||||
else if (MSG.SystemEvent == 775)
|
||||
eventargs.Trouble = true;
|
||||
}
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.BATTERY_OK)
|
||||
eventargs.Value = "OK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 776 && MSG.SystemEvent <= 777)
|
||||
else if (MSG.SystemEvent >= (ushort)enuEventType.DCM_TROUBLE &&
|
||||
MSG.SystemEvent <= (ushort)enuEventType.DCM_OK)
|
||||
{
|
||||
eventargs.Type = enuEventType.DCM_;
|
||||
eventargs.Type = SystemEventType.DCM;
|
||||
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 776)
|
||||
if (MSG.SystemEvent == (ushort)enuEventType.DCM_TROUBLE)
|
||||
{
|
||||
eventargs.Value = "TROUBLE";
|
||||
else if (MSG.SystemEvent == 777)
|
||||
eventargs.Trouble = true;
|
||||
}
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.DCM_OK)
|
||||
eventargs.Value = "OK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 778 && MSG.SystemEvent <= 781)
|
||||
else if (MSG.SystemEvent >= (ushort)enuEventType.ENERGY_COST_LOW &&
|
||||
MSG.SystemEvent <= (ushort)enuEventType.ENERGY_COST_CRITICAL)
|
||||
{
|
||||
eventargs.Type = enuEventType.ENERGY_COST_;
|
||||
eventargs.Type = SystemEventType.EnergyCost;
|
||||
|
||||
if (MSG.SystemEvent == 778)
|
||||
if (MSG.SystemEvent == (ushort)enuEventType.ENERGY_COST_LOW)
|
||||
eventargs.Value = "LOW";
|
||||
else if (MSG.SystemEvent == 779)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.ENERGY_COST_MID)
|
||||
eventargs.Value = "MID";
|
||||
else if (MSG.SystemEvent == 780)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.ENERGY_COST_HIGH)
|
||||
eventargs.Value = "HIGH";
|
||||
else if (MSG.SystemEvent == 781)
|
||||
else if (MSG.SystemEvent == (ushort)enuEventType.ENERGY_COST_CRITICAL)
|
||||
eventargs.Value = "CRITICAL";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 782 && MSG.SystemEvent <= 787)
|
||||
{
|
||||
eventargs.Type = enuEventType.CAMERA;
|
||||
eventargs.Type = SystemEventType.Camera;
|
||||
eventargs.Value = (MSG.SystemEvent - 781).ToString();
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 61440 && MSG.SystemEvent <= 64511)
|
||||
{
|
||||
eventargs.Type = enuEventType.SWITCH_PRESS;
|
||||
int state = (int)MSG.Data[1] - 240;
|
||||
int id = (int)MSG.Data[2];
|
||||
eventargs.Type = SystemEventType.SwitchPress;
|
||||
int state = MSG.Data[1] - 240;
|
||||
int id = MSG.Data[2];
|
||||
|
||||
eventargs.Value = "Unit: " + id + ", State: " + state;
|
||||
|
||||
@ -663,9 +591,9 @@ namespace OmniLinkBridge.Modules
|
||||
}
|
||||
else if (MSG.SystemEvent >= 64512 && MSG.SystemEvent <= 65535)
|
||||
{
|
||||
eventargs.Type = enuEventType.UPB_LINK;
|
||||
int state = (int)MSG.Data[1] - 252;
|
||||
int id = (int)MSG.Data[2];
|
||||
eventargs.Type = SystemEventType.UPBLink;
|
||||
int state = MSG.Data[1] - 252;
|
||||
int id = MSG.Data[2];
|
||||
|
||||
eventargs.Value = "Link: " + id + ", State: " + state;
|
||||
|
||||
@ -676,9 +604,9 @@ namespace OmniLinkBridge.Modules
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < MSG.MessageLength; i++)
|
||||
sb.Append(MSG.Data[i].ToString() + " ");
|
||||
log.Debug("Unhandled SystemEvent Raw: " + sb.ToString() + "Num: " + MSG.SystemEvent);
|
||||
log.Debug("Unhandled SystemEvent Raw: {raw}, Num: {num}", sb.ToString(), MSG.SystemEvent);
|
||||
|
||||
int num = ((int)MSG.MessageLength - 1) / 2;
|
||||
int num = (MSG.MessageLength - 1) / 2;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
log.Debug("Unhandled SystemEvent: " +
|
||||
@ -734,18 +662,13 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
|
||||
// Don't fire event when invalid temperature of 0 is sometimes received
|
||||
if (Controller.Thermostats[MSG.ObjectNumber(i)].Temp > 0)
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
|
||||
EventTimer = false
|
||||
});
|
||||
}
|
||||
else if (Global.verbose_thermostat_timer)
|
||||
log.Warn("Ignoring unsolicited unknown temp for Thermostat " + Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
|
||||
Offline = Controller.Thermostats[MSG.ObjectNumber(i)].Temp == 0,
|
||||
EventTimer = false
|
||||
});
|
||||
|
||||
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
|
||||
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
|
||||
@ -753,7 +676,8 @@ namespace OmniLinkBridge.Modules
|
||||
tstats[MSG.ObjectNumber(i)] = DateTime.Now;
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Unsolicited status received for Thermostat " + Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
log.Debug("Unsolicited status received for Thermostat {thermostatName}",
|
||||
Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -779,6 +703,28 @@ namespace OmniLinkBridge.Modules
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.AccessControlLock:
|
||||
for (byte i = 0; i < MSG.AccessControlLockCount(); i++)
|
||||
{
|
||||
Controller.AccessControlReaders[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnLockStatus?.Invoke(this, new LockStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Reader = Controller.AccessControlReaders[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.AudioZone:
|
||||
for (byte i = 0; i < MSG.AudioZoneStatusCount(); i++)
|
||||
{
|
||||
Controller.AudioZones[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnAudioZoneStatus?.Invoke(this, new AudioZoneStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
AudioZone = Controller.AudioZones[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (Global.verbose_unhandled)
|
||||
{
|
||||
@ -812,12 +758,16 @@ namespace OmniLinkBridge.Modules
|
||||
foreach (KeyValuePair<ushort, DateTime> tstat in tstats)
|
||||
{
|
||||
// Poll every 4 minutes if no prior update
|
||||
if (RoundToMinute(tstat.Value).AddMinutes(4) <= RoundToMinute(DateTime.Now))
|
||||
if (RoundToMinute(tstat.Value).AddMinutes(4) <= RoundToMinute(DateTime.Now) &&
|
||||
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
||||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
|
||||
{
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus);
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
|
||||
enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus);
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Polling status for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
log.Debug("Polling status for Thermostat {thermostatName}",
|
||||
Controller.Thermostats[tstat.Key].Name);
|
||||
}
|
||||
|
||||
// Log every minute if update within 5 minutes and connected
|
||||
@ -825,21 +775,17 @@ namespace OmniLinkBridge.Modules
|
||||
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
||||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
|
||||
{
|
||||
// Don't fire event when invalid temperature of 0 is sometimes received
|
||||
if (Controller.Thermostats[tstat.Key].Temp > 0)
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
ID = tstat.Key,
|
||||
Thermostat = Controller.Thermostats[tstat.Key],
|
||||
EventTimer = true
|
||||
});
|
||||
}
|
||||
else if (Global.verbose_thermostat_timer)
|
||||
log.Warn("Ignoring unknown temp for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
ID = tstat.Key,
|
||||
Thermostat = Controller.Thermostats[tstat.Key],
|
||||
Offline = Controller.Thermostats[tstat.Key].Temp == 0,
|
||||
EventTimer = true
|
||||
});
|
||||
}
|
||||
else if (Global.verbose_thermostat_timer)
|
||||
log.Warn("Not logging out of date status for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
log.Warning("Not logging out of date status for Thermostat {thermostatName}",
|
||||
Controller.Thermostats[tstat.Key].Name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,7 +812,8 @@ namespace OmniLinkBridge.Modules
|
||||
tstats[MSG.ObjectNumber(i)] = DateTime.Now;
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Polling status received for Thermostat " + Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
log.Debug("Polling status received for Thermostat {thermostatName}",
|
||||
Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using HAI_Shared;
|
||||
using log4net;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -8,7 +8,7 @@ namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class TimeSyncModule : IModule
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII OmniLink { get; set; }
|
||||
|
||||
@ -74,14 +74,15 @@ namespace OmniLinkBridge.Modules
|
||||
// Extract the 2 digit prefix to use when parsing the time
|
||||
int year = DateTime.Now.Year / 100;
|
||||
|
||||
time = new DateTime((int)MSG.Year + (year * 100), (int)MSG.Month, (int)MSG.Day, (int)MSG.Hour, (int)MSG.Minute, (int)MSG.Second);
|
||||
time = new DateTime(MSG.Year + (year * 100), MSG.Month, MSG.Day, MSG.Hour, MSG.Minute, MSG.Second);
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Warn("Controller time could not be parsed");
|
||||
log.Warning("Controller time could not be parsed");
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection,
|
||||
(byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
|
||||
|
||||
return;
|
||||
@ -91,10 +92,12 @@ namespace OmniLinkBridge.Modules
|
||||
|
||||
if (adj > Global.time_drift)
|
||||
{
|
||||
log.Warn("Controller time " + time.ToString("MM/dd/yyyy HH:mm:ss") + " out of sync by " + adj + " seconds");
|
||||
log.Warning("Controller time {controllerTime} out of sync by {driftSeconds} seconds",
|
||||
time.ToString("MM/dd/yyyy HH:mm:ss"), adj);
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection,
|
||||
(byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using OmniLinkBridge.Modules;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using OmniLinkBridge.WebAPI;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.ServiceModel;
|
||||
@ -14,7 +14,7 @@ namespace OmniLinkBridge
|
||||
{
|
||||
public class WebServiceModule : IModule
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public static OmniLinkII OmniLink { get; private set; }
|
||||
|
||||
@ -33,6 +33,8 @@ namespace OmniLinkBridge
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
log.Warning("WebAPI is deprecated");
|
||||
|
||||
WebNotification.RestoreSubscriptions();
|
||||
|
||||
Uri uri = new Uri("http://0.0.0.0:" + Global.webapi_port + "/");
|
||||
@ -43,11 +45,11 @@ namespace OmniLinkBridge
|
||||
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IOmniLinkService), new WebHttpBinding(), "");
|
||||
host.Open();
|
||||
|
||||
log.Info("Listening on " + uri.ToString());
|
||||
log.Information("Listening on {url}", uri);
|
||||
}
|
||||
catch (CommunicationException ex)
|
||||
{
|
||||
log.Error("An exception occurred starting web service", ex);
|
||||
log.Error(ex, "An exception occurred starting web service");
|
||||
host.Abort();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using log4net;
|
||||
using OmniLinkBridge.Modules;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
@ -8,7 +9,7 @@ namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class EmailNotification : INotification
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public void Notify(string source, string description, NotificationPriority priority)
|
||||
{
|
||||
@ -25,23 +26,21 @@ namespace OmniLinkBridge.Notifications
|
||||
};
|
||||
mail.To.Add(address);
|
||||
|
||||
using (SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port))
|
||||
using SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port);
|
||||
smtp.EnableSsl = Global.mail_tls;
|
||||
if (!string.IsNullOrEmpty(Global.mail_username))
|
||||
{
|
||||
smtp.EnableSsl = Global.mail_tls;
|
||||
if (!string.IsNullOrEmpty(Global.mail_username))
|
||||
{
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
|
||||
}
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
smtp.Send(mail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred sending email notification", ex);
|
||||
}
|
||||
try
|
||||
{
|
||||
smtp.Send(mail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex, "An error occurred sending email notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using log4net;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
@ -8,7 +8,7 @@ namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public static class Notification
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static readonly List<INotification> providers = new List<INotification>()
|
||||
{
|
||||
@ -27,7 +27,7 @@ namespace OmniLinkBridge.Notifications
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to send notification", ex);
|
||||
log.Error(ex, "Failed to send notification");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using log4net;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
@ -8,7 +8,7 @@ namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class ProwlNotification : INotification
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static readonly Uri URI = new Uri("https://api.prowlapp.com/publicapi/add");
|
||||
|
||||
@ -25,19 +25,17 @@ namespace OmniLinkBridge.Notifications
|
||||
"description=" + description
|
||||
};
|
||||
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
client.UploadStringAsync(URI, string.Join("&", parameters.ToArray()));
|
||||
client.UploadStringCompleted += Client_UploadStringCompleted;
|
||||
}
|
||||
using WebClient client = new WebClient();
|
||||
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
client.UploadStringAsync(URI, string.Join("&", parameters.ToArray()));
|
||||
client.UploadStringCompleted += Client_UploadStringCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
private void Client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
log.Error("An error occurred sending prowl notification", e.Error);
|
||||
log.Error(e.Error, "An error occurred sending prowl notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using log4net;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
@ -8,7 +8,7 @@ namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class PushoverNotification : INotification
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static readonly Uri URI = new Uri("https://api.pushover.net/1/messages.json");
|
||||
|
||||
@ -24,18 +24,16 @@ namespace OmniLinkBridge.Notifications
|
||||
{ "message", description }
|
||||
};
|
||||
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.UploadValues(URI, parameters);
|
||||
client.UploadStringCompleted += Client_UploadStringCompleted;
|
||||
}
|
||||
using WebClient client = new WebClient();
|
||||
client.UploadValues(URI, parameters);
|
||||
client.UploadStringCompleted += Client_UploadStringCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
private void Client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
log.Error("An error occurred sending pushover notification", e.Error);
|
||||
log.Error(e.Error, "An error occurred sending pushover notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
OmniLinkBridge/OmniLink/AudioZoneStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/AudioZoneStatusEventArgs.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class AudioZoneStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsAudioZone AudioZone { get; set; }
|
||||
}
|
||||
}
|
11
OmniLinkBridge/OmniLink/LockStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/LockStatusEventArgs.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class LockStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsAccessControlReader Reader { get; set; }
|
||||
}
|
||||
}
|
15
OmniLinkBridge/OmniLink/SystemEventType.cs
Normal file
15
OmniLinkBridge/OmniLink/SystemEventType.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public enum SystemEventType
|
||||
{
|
||||
Button,
|
||||
Phone,
|
||||
AC,
|
||||
Battery,
|
||||
DCM,
|
||||
EnergyCost,
|
||||
Camera,
|
||||
SwitchPress,
|
||||
UPBLink,
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using HAI_Shared;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class SystemStatusEventArgs : EventArgs
|
||||
{
|
||||
public enuEventType Type { get; set; }
|
||||
public SystemEventType Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
public bool Trouble { get; set; }
|
||||
public bool SendNotification { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,11 @@ namespace OmniLinkBridge.OmniLink
|
||||
public ushort ID { get; set; }
|
||||
public clsThermostat Thermostat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true when thermostat is offline, indicated by a temperature of 0
|
||||
/// </summary>
|
||||
public bool Offline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true when fired by thermostat polling
|
||||
/// </summary>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>OmniLinkBridge</RootNamespace>
|
||||
<AssemblyName>OmniLinkBridge</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
@ -24,6 +24,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
@ -34,6 +35,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -43,6 +45,8 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<NoWarn>IDE1006</NoWarn>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
@ -52,6 +56,8 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<NoWarn>IDE1006</NoWarn>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
@ -74,26 +80,39 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ControllerEnricher.cs" />
|
||||
<Compile Include="CoreServer.cs" />
|
||||
<Compile Include="Modules\TimeSyncModule.cs" />
|
||||
<Compile Include="MQTT\Alarm.cs" />
|
||||
<Compile Include="MQTT\AreaCommands.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Button.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Alarm.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Lock.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Select.cs" />
|
||||
<Compile Include="MQTT\OverrideArea.cs" />
|
||||
<Compile Include="MQTT\OverrideUnit.cs" />
|
||||
<Compile Include="MQTT\Parser\AlarmCommands.cs" />
|
||||
<Compile Include="MQTT\AreaCommandCode.cs" />
|
||||
<Compile Include="MQTT\Parser\AreaCommands.cs" />
|
||||
<Compile Include="MQTT\AreaState.cs" />
|
||||
<Compile Include="MQTT\BinarySensor.cs" />
|
||||
<Compile Include="MQTT\CommandTypes.cs" />
|
||||
<Compile Include="MQTT\Device.cs" />
|
||||
<Compile Include="MQTT\Climate.cs" />
|
||||
<Compile Include="MQTT\DeviceRegistry.cs" />
|
||||
<Compile Include="MQTT\MessageCommands.cs" />
|
||||
<Compile Include="MQTT\Availability.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\BinarySensor.cs" />
|
||||
<Compile Include="MQTT\Parser\CommandTypes.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Device.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Climate.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\DeviceRegistry.cs" />
|
||||
<Compile Include="MQTT\Extensions.cs" />
|
||||
<Compile Include="MQTT\Parser\LockCommands.cs" />
|
||||
<Compile Include="MQTT\Parser\MessageCommands.cs" />
|
||||
<Compile Include="MQTT\MessageProcessor.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Number.cs" />
|
||||
<Compile Include="MQTT\OverrideZone.cs" />
|
||||
<Compile Include="MQTT\Switch.cs" />
|
||||
<Compile Include="MQTT\Light.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Switch.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Light.cs" />
|
||||
<Compile Include="MQTT\MappingExtensions.cs" />
|
||||
<Compile Include="MQTT\Sensor.cs" />
|
||||
<Compile Include="MQTT\Topic.cs" />
|
||||
<Compile Include="MQTT\UnitCommands.cs" />
|
||||
<Compile Include="MQTT\ZoneCommands.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Sensor.cs" />
|
||||
<Compile Include="MQTT\Parser\Topic.cs" />
|
||||
<Compile Include="MQTT\Parser\UnitCommands.cs" />
|
||||
<Compile Include="MQTT\Parser\ZoneCommands.cs" />
|
||||
<Compile Include="MQTT\UnitType.cs" />
|
||||
<Compile Include="Notifications\EmailNotification.cs" />
|
||||
<Compile Include="Notifications\INotification.cs" />
|
||||
<Compile Include="Notifications\Notification.cs" />
|
||||
@ -101,6 +120,9 @@
|
||||
<Compile Include="Notifications\PushoverNotification.cs" />
|
||||
<Compile Include="OmniLink\ButtonStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\IOmniLinkII.cs" />
|
||||
<Compile Include="OmniLink\AudioZoneStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\SystemEventType.cs" />
|
||||
<Compile Include="OmniLink\LockStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\UnitStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\ThermostatStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\MessageStatusEventArgs.cs" />
|
||||
@ -161,17 +183,35 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net">
|
||||
<Version>2.0.8</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Mono.Posix-4.5">
|
||||
<Version>4.5.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet.Extensions.ManagedClient">
|
||||
<Version>3.0.8</Version>
|
||||
<Version>3.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.3</Version>
|
||||
<Version>13.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog">
|
||||
<Version>3.1.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Formatting.Compact">
|
||||
<Version>2.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.Async">
|
||||
<Version>1.5.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.Console">
|
||||
<Version>5.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.File">
|
||||
<Version>5.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.Http">
|
||||
<Version>7.2.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.ValueTuple">
|
||||
<Version>4.5.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
10
OmniLinkBridge/OmniLinkBridge.csproj.user
Normal file
10
OmniLinkBridge/OmniLinkBridge.csproj.user
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<StartArguments>
|
||||
</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectView>ProjectFiles</ProjectView>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,5 +1,5 @@
|
||||
# HAI / Leviton Omni Controller
|
||||
controller_address =
|
||||
controller_address =
|
||||
controller_port = 4369
|
||||
controller_key1 = 00-00-00-00-00-00-00-00
|
||||
controller_key2 = 00-00-00-00-00-00-00-00
|
||||
@ -22,6 +22,8 @@ verbose_thermostat_timer = yes
|
||||
verbose_thermostat = yes
|
||||
verbose_unit = yes
|
||||
verbose_message = yes
|
||||
verbose_lock = yes
|
||||
verbose_audio = yes
|
||||
|
||||
# mySQL Logging (yes/no)
|
||||
mysql_logging = no
|
||||
@ -49,13 +51,44 @@ mqtt_prefix = omnilink
|
||||
mqtt_discovery_prefix = homeassistant
|
||||
# Prefix for Home Assistant entity names
|
||||
mqtt_discovery_name_prefix =
|
||||
# Specify a range of numbers like 1,2,3,5-10
|
||||
# Skip publishing Home Assistant discovery topics for zones/units
|
||||
# Specify a range of numbers 1,2,3,5-10
|
||||
mqtt_discovery_ignore_zones =
|
||||
mqtt_discovery_ignore_units =
|
||||
# device_class must be battery, door, garage_door, gas, moisture, motion, problem, smoke, or window
|
||||
|
||||
# Override the area Home Assistant alarm control panel
|
||||
# Prompt for user code
|
||||
# code_arm: true or false, defaults to false
|
||||
# code_disarm: true or false, defaults to false
|
||||
# Show these modes
|
||||
# arm_home: true or false, defaults to true
|
||||
# arm_away: true or false, defaults to true
|
||||
# arm_night: true or false, defaults to true
|
||||
# arm_vacation: true or false, defaults to true
|
||||
#mqtt_discovery_override_area = id=1;code_disarm=true;arm_vacation=false
|
||||
|
||||
# Override the zone Home Assistant binary sensor device_class
|
||||
# device_class: must be battery, cold, door, garage_door, gas,
|
||||
# heat, moisture, motion, problem, safety, smoke, or window
|
||||
#mqtt_discovery_override_zone = id=5;device_class=garage_door
|
||||
#mqtt_discovery_override_zone = id=6;device_class=garage_door
|
||||
|
||||
# Override the unit Home Assistant device type
|
||||
# type:
|
||||
# Units (LTe 1-32, IIe 1-64, Pro 1-256) light or switch, defaults to light
|
||||
# Flags (LTe 41-88, IIe 73-128, Pro 393-511) switch or number, defaults to switch
|
||||
#mqtt_discovery_override_unit = id=1;type=switch
|
||||
#mqtt_discovery_override_unit = id=395;type=number
|
||||
|
||||
# Publish buttons as this Home Assistant device type
|
||||
# must be button (recommended) or switch (default, previous behavior)
|
||||
mqtt_discovery_button_type = switch
|
||||
# Handle mute locally by setting volume to 0 and restoring to previous value
|
||||
mqtt_audio_local_mute = no
|
||||
# Change audio volume scaling for Home Assistant media player
|
||||
# yes 0.00-1.00, no 0-100 (default, previous behavior)
|
||||
mqtt_audio_volume_media_player = no
|
||||
|
||||
# Notifications (yes/no)
|
||||
# Always sent for area alarms and critical system events
|
||||
# Optionally enable for area status changes and console messages
|
||||
|
@ -1,21 +1,32 @@
|
||||
using Mono.Unix;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Filters;
|
||||
using Serilog.Formatting.Compact;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
class Program
|
||||
internal class Program
|
||||
{
|
||||
static CoreServer server;
|
||||
private static CoreServer server;
|
||||
|
||||
static void Main(string[] args)
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
bool interactive = false;
|
||||
|
||||
string config_file = "OmniLinkBridge.ini";
|
||||
string log_file = "log.txt";
|
||||
bool log_clef = false;
|
||||
LogEventLevel log_level = LogEventLevel.Information;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
@ -24,15 +35,31 @@ namespace OmniLinkBridge
|
||||
case "-h":
|
||||
case "-help":
|
||||
ShowHelp();
|
||||
return;
|
||||
return 0;
|
||||
case "-c":
|
||||
Global.config_file = args[++i];
|
||||
config_file = args[++i];
|
||||
break;
|
||||
case "-e":
|
||||
Settings.UseEnvironment = true;
|
||||
Global.UseEnvironment = true;
|
||||
break;
|
||||
case "-d":
|
||||
Settings.ShowDebug = true;
|
||||
Global.DebugSettings = true;
|
||||
break;
|
||||
case "-lf":
|
||||
log_file = args[++i];
|
||||
|
||||
if (string.Compare(log_file, "disable", true) == 0)
|
||||
log_file = null;
|
||||
break;
|
||||
case "-lj":
|
||||
log_clef = true;
|
||||
break;
|
||||
case "-ll":
|
||||
Enum.TryParse(args[++i], out log_level);
|
||||
break;
|
||||
case "-ld":
|
||||
Global.DebugSettings = true;
|
||||
Global.SendLogs = true;
|
||||
break;
|
||||
case "-s":
|
||||
Global.webapi_subscriptions_file = args[++i];
|
||||
@ -43,27 +70,62 @@ namespace OmniLinkBridge
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Global.config_file))
|
||||
Global.config_file = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
|
||||
Path.DirectorySeparatorChar + "OmniLinkBridge.ini";
|
||||
if (string.Compare(Environment.GetEnvironmentVariable("SEND_LOGS"), "1") == 0)
|
||||
{
|
||||
Global.DebugSettings = true;
|
||||
Global.SendLogs = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Global.webapi_subscriptions_file))
|
||||
Global.webapi_subscriptions_file = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
|
||||
Path.DirectorySeparatorChar + "WebSubscriptions.json";
|
||||
config_file = GetFullPath(config_file);
|
||||
|
||||
log4net.Config.XmlConfigurator.Configure();
|
||||
Global.webapi_subscriptions_file = GetFullPath(Global.webapi_subscriptions_file ?? "WebSubscriptions.json");
|
||||
|
||||
if(Environment.UserInteractive || interactive)
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
|
||||
// Use TLS 1.2 as default connection
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
||||
|
||||
string log_format = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{SourceContext} {Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
var log_config = new LoggerConfiguration()
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.WithProperty("Application", "OmniLinkBridge")
|
||||
.Enrich.WithProperty("Session", Global.SessionID)
|
||||
.Enrich.With<ControllerEnricher>()
|
||||
.Enrich.FromLogContext();
|
||||
|
||||
if (log_file != null)
|
||||
{
|
||||
log_file = GetFullPath(log_file);
|
||||
|
||||
if (log_clef)
|
||||
log_config = log_config.WriteTo.Async(a => a.File(new CompactJsonFormatter(), log_file, log_level,
|
||||
rollingInterval: RollingInterval.Day, retainedFileCountLimit: 15));
|
||||
else
|
||||
log_config = log_config.WriteTo.Async(a => a.File(log_file, log_level, log_format,
|
||||
rollingInterval: RollingInterval.Day, retainedFileCountLimit: 15));
|
||||
}
|
||||
|
||||
if (Global.SendLogs)
|
||||
log_config = log_config.WriteTo.Logger(lc => lc
|
||||
.WriteTo.Http("https://telemetry.excalibur-partners.com"));
|
||||
else if (UseTelemetry())
|
||||
log_config = log_config.WriteTo.Logger(lc => lc
|
||||
.Filter.ByIncludingOnly(Matching.WithProperty("Telemetry"))
|
||||
.WriteTo.Http("https://telemetry.excalibur-partners.com"));
|
||||
|
||||
if (Environment.UserInteractive || interactive)
|
||||
log_config = log_config.WriteTo.Console(outputTemplate: log_format);
|
||||
|
||||
Log.Logger = log_config.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
Settings.LoadSettings(Global.config_file);
|
||||
Settings.LoadSettings(config_file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Errors are logged in LoadSettings();
|
||||
Environment.Exit(1);
|
||||
Log.CloseAndFlush();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Environment.UserInteractive || interactive)
|
||||
@ -104,6 +166,16 @@ namespace OmniLinkBridge
|
||||
|
||||
ServiceBase.Run(ServicesToRun);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string GetFullPath(string file)
|
||||
{
|
||||
if (Path.IsPathRooted(file))
|
||||
return file;
|
||||
|
||||
return Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), file);
|
||||
}
|
||||
|
||||
protected static void myHandler(object sender, ConsoleCancelEventArgs args)
|
||||
@ -112,21 +184,56 @@ namespace OmniLinkBridge
|
||||
args.Cancel = true;
|
||||
}
|
||||
|
||||
static bool IsRunningOnMono()
|
||||
private static bool IsRunningOnMono()
|
||||
{
|
||||
return Type.GetType("Mono.Runtime") != null;
|
||||
}
|
||||
|
||||
static void ShowHelp()
|
||||
public static string GetEnvironment()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("HASSIO_TOKEN") != null)
|
||||
return "Home Assistant";
|
||||
else if (IsRunningOnMono())
|
||||
return Process.GetProcesses().Any(w => w.Id == 2) ? "Mono" : "Docker";
|
||||
else
|
||||
return "Native";
|
||||
}
|
||||
|
||||
private static bool UseTelemetry()
|
||||
{
|
||||
return string.Compare(Environment.GetEnvironmentVariable("TELEMETRY_OPTOUT"), "1") != 0;
|
||||
}
|
||||
|
||||
public static void ShowSendLogsWarning()
|
||||
{
|
||||
if (Global.SendLogs)
|
||||
Log.Warning("SENDING LOGS TO DEVELOPER Controller: {ControllerID}, Session: {Session}",
|
||||
Global.controller_id, Global.SessionID);
|
||||
}
|
||||
|
||||
private static void ShowHelp()
|
||||
{
|
||||
Console.WriteLine(
|
||||
AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-e] [-d] [-s subscriptions_file] [-i]\n" +
|
||||
"\t[-debug-config] [-ignore-env]\n" +
|
||||
"\t-c Specifies the configuration file. Default is OmniLinkBridge.ini\n" +
|
||||
"\t-e Check environment variables for configuration settings\n" +
|
||||
"\t-d Show debug output for configuration loading\n" +
|
||||
"\t-s Specifies the web api subscriptions file. Default is WebSubscriptions.json\n" +
|
||||
"\t-i Run in interactive mode");
|
||||
AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-e] [-d] [-j] [-s subscriptions_file]\n" +
|
||||
"\t[-lf log_file|disable] [-lj [-ll verbose|debug|information|warning|error] [-ld] [-i]\n" +
|
||||
"\t-c Specifies the configuration file. Default is OmniLinkBridge.ini\n" +
|
||||
"\t-e Check environment variables for configuration settings\n" +
|
||||
"\t-d Show debug ouput for configuration loading\n" +
|
||||
"\t-s Specifies the web api subscriptions file. Default is WebSubscriptions.json\n" +
|
||||
"\t-lf Specifies the rolling log file. Retention is 15 days. Default is log.txt.\n" +
|
||||
"\t-lj Write logs as CLEF (compact log event format) JSON.\n" +
|
||||
"\t-ll Minimum level at which events will be logged. Default is information.\n" +
|
||||
"\t-ld Send logs to developer. ONLY USE WHEN ASKED.\n" +
|
||||
"\t Also enabled by setting a SEND_LOGS environment variable to 1.\n" +
|
||||
"\t-i Run in interactive mode");
|
||||
|
||||
Console.WriteLine(
|
||||
"\nVersion: " + Assembly.GetExecutingAssembly().GetName().Version +
|
||||
"\nEnvironment: " + GetEnvironment());
|
||||
|
||||
Console.WriteLine(
|
||||
"\nOmniLink Bridge collects anonymous telemetry data to help improve the software.\n" +
|
||||
"You can opt of telemetry by setting a TELEMETRY_OPTOUT environment variable to 1.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ using System.Runtime.InteropServices;
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OmniLinkBridge")]
|
||||
[assembly: AssemblyTitle("OmniLink Bridge")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridge")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2019")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.1.8.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.8.0")]
|
||||
[assembly: AssemblyVersion("1.1.19.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.19.0")]
|
||||
|
@ -1,4 +1,5 @@
|
||||
using log4net;
|
||||
using OmniLinkBridge.MQTT.HomeAssistant;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -7,14 +8,13 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
using ha = OmniLinkBridge.MQTT.HomeAssistant;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
public static bool ShowDebug { get; set; }
|
||||
public static bool UseEnvironment { get; set; }
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public static void LoadSettings(string file)
|
||||
{
|
||||
@ -31,9 +31,10 @@ namespace OmniLinkBridge
|
||||
// HAI / Leviton Omni Controller
|
||||
Global.controller_address = settings.ValidateHasValue("controller_address");
|
||||
Global.controller_port = settings.ValidatePort("controller_port");
|
||||
Global.controller_key1 = settings.ValidateHasValue("controller_key1");
|
||||
Global.controller_key2 = settings.ValidateHasValue("controller_key2");
|
||||
Global.controller_key1 = settings.ValidateEncryptionKey("controller_key1");
|
||||
Global.controller_key2 = settings.ValidateEncryptionKey("controller_key2");
|
||||
Global.controller_name = settings.CheckEnv("controller_name") ?? "OmniLinkBridge";
|
||||
Global.controller_id = (Global.controller_address + Global.controller_key1 + Global.controller_key2).ComputeGuid();
|
||||
|
||||
// Controller Time Sync
|
||||
Global.time_sync = settings.ValidateBool("time_sync");
|
||||
@ -53,10 +54,12 @@ namespace OmniLinkBridge
|
||||
Global.verbose_thermostat = settings.ValidateBool("verbose_thermostat");
|
||||
Global.verbose_unit = settings.ValidateBool("verbose_unit");
|
||||
Global.verbose_message = settings.ValidateBool("verbose_message");
|
||||
Global.verbose_lock = settings.ValidateBool("verbose_lock");
|
||||
Global.verbose_audio = settings.ValidateBool("verbose_audio");
|
||||
|
||||
// mySQL Logging
|
||||
Global.mysql_logging = settings.ValidateBool("mysql_logging");
|
||||
Global.mysql_connection = settings.CheckEnv("mysql_connection");
|
||||
Global.mysql_connection = settings.CheckEnv("mysql_connection", true);
|
||||
|
||||
// Web Service
|
||||
Global.webapi_enabled = settings.ValidateBool("webapi_enabled");
|
||||
@ -74,8 +77,8 @@ namespace OmniLinkBridge
|
||||
{
|
||||
Global.mqtt_server = settings.CheckEnv("mqtt_server");
|
||||
Global.mqtt_port = settings.ValidatePort("mqtt_port");
|
||||
Global.mqtt_username = settings.CheckEnv("mqtt_username");
|
||||
Global.mqtt_password = settings.CheckEnv("mqtt_password");
|
||||
Global.mqtt_username = settings.CheckEnv("mqtt_username", true);
|
||||
Global.mqtt_password = settings.CheckEnv("mqtt_password", true);
|
||||
Global.mqtt_prefix = settings.CheckEnv("mqtt_prefix") ?? "omnilink";
|
||||
Global.mqtt_discovery_prefix = settings.CheckEnv("mqtt_discovery_prefix") ?? "homeassistant";
|
||||
Global.mqtt_discovery_name_prefix = settings.CheckEnv("mqtt_discovery_name_prefix") ?? string.Empty;
|
||||
@ -85,7 +88,12 @@ namespace OmniLinkBridge
|
||||
|
||||
Global.mqtt_discovery_ignore_zones = settings.ValidateRange("mqtt_discovery_ignore_zones");
|
||||
Global.mqtt_discovery_ignore_units = settings.ValidateRange("mqtt_discovery_ignore_units");
|
||||
Global.mqtt_discovery_override_area = settings.LoadOverrideArea<MQTT.OverrideArea>("mqtt_discovery_override_area");
|
||||
Global.mqtt_discovery_override_zone = settings.LoadOverrideZone<MQTT.OverrideZone>("mqtt_discovery_override_zone");
|
||||
Global.mqtt_discovery_override_unit = settings.LoadOverrideUnit<MQTT.OverrideUnit>("mqtt_discovery_override_unit");
|
||||
Global.mqtt_discovery_button_type = settings.ValidateType("mqtt_discovery_button_type", typeof(Switch), typeof(Button));
|
||||
Global.mqtt_audio_local_mute = settings.ValidateBool("mqtt_audio_local_mute");
|
||||
Global.mqtt_audio_volume_media_player = settings.ValidateBool("mqtt_audio_volume_media_player");
|
||||
}
|
||||
|
||||
// Notifications
|
||||
@ -99,31 +107,113 @@ namespace OmniLinkBridge
|
||||
{
|
||||
Global.mail_tls = settings.ValidateBool("mail_tls");
|
||||
Global.mail_port = settings.ValidatePort("mail_port");
|
||||
Global.mail_username = settings.CheckEnv("mail_username");
|
||||
Global.mail_password = settings.CheckEnv("mail_password");
|
||||
Global.mail_username = settings.CheckEnv("mail_username", true);
|
||||
Global.mail_password = settings.CheckEnv("mail_password", true);
|
||||
Global.mail_from = settings.ValidateMailFrom("mail_from");
|
||||
Global.mail_to = settings.ValidateMailTo("mail_to");
|
||||
}
|
||||
|
||||
// Prowl Notifications
|
||||
Global.prowl_key = settings.ValidateMultipleStrings("prowl_key");
|
||||
Global.prowl_key = settings.ValidateMultipleStrings("prowl_key", true);
|
||||
|
||||
// Pushover Notifications
|
||||
Global.pushover_token = settings.CheckEnv("pushover_token");
|
||||
Global.pushover_user = settings.ValidateMultipleStrings("pushover_user");
|
||||
Global.pushover_token = settings.CheckEnv("pushover_token", true);
|
||||
Global.pushover_user = settings.ValidateMultipleStrings("pushover_user", true);
|
||||
}
|
||||
|
||||
private static string CheckEnv(this NameValueCollection settings, string name)
|
||||
private static string CheckEnv(this NameValueCollection settings, string name, bool sensitive = false)
|
||||
{
|
||||
string env = UseEnvironment ? Environment.GetEnvironmentVariable(name.ToUpper()) : null;
|
||||
string env = Global.UseEnvironment ? Environment.GetEnvironmentVariable(name.ToUpper()) : null;
|
||||
string value = !string.IsNullOrEmpty(env) ? env : settings[name];
|
||||
|
||||
if (ShowDebug)
|
||||
log.Debug((!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(5) + $"{name}: {value}");
|
||||
if (Global.DebugSettings)
|
||||
log.Debug("{ConfigType} {ConfigName}: {ConfigValue}",
|
||||
(!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(4), name,
|
||||
sensitive && value != null ? value.Truncate(3) + "***MASKED***" : value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<int, T> LoadOverrideArea<T>(this NameValueCollection settings, string section) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
ConcurrentDictionary<int, T> ret = new ConcurrentDictionary<int, T>();
|
||||
|
||||
string value = settings.CheckEnv(section);
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return ret;
|
||||
|
||||
string[] ids = value.Split(',');
|
||||
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
Dictionary<string, string> attributes = ids[i].TrimEnd(new char[] { ';' }).Split(';')
|
||||
.Select(s => s.Split('='))
|
||||
.ToDictionary(a => a[0].Trim(), a => a[1].Trim(), StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (!attributes.ContainsKey("id") || !int.TryParse(attributes["id"], out int attrib_id))
|
||||
throw new Exception("Missing or invalid id attribute");
|
||||
|
||||
T override_area = new T();
|
||||
|
||||
if (override_area is MQTT.OverrideArea mqtt_area)
|
||||
{
|
||||
foreach (string attribute in attributes.Keys)
|
||||
{
|
||||
switch(attribute)
|
||||
{
|
||||
case "id":
|
||||
continue;
|
||||
case "code_arm":
|
||||
if (!bool.TryParse(attributes["code_arm"], out bool code_arm))
|
||||
throw new Exception("Invalid code_arm attribute");
|
||||
mqtt_area.code_arm = code_arm;
|
||||
break;
|
||||
case "code_disarm":
|
||||
if (!bool.TryParse(attributes["code_disarm"], out bool code_disarm))
|
||||
throw new Exception("Invalid code_disarm attribute");
|
||||
mqtt_area.code_disarm = code_disarm;
|
||||
break;
|
||||
case "arm_home":
|
||||
if (!bool.TryParse(attributes["arm_home"], out bool arm_home))
|
||||
throw new Exception("Invalid arm_home attribute");
|
||||
mqtt_area.arm_home = arm_home;
|
||||
break;
|
||||
case "arm_away":
|
||||
if (!bool.TryParse(attributes["arm_away"], out bool arm_away))
|
||||
throw new Exception("Invalid arm_away attribute");
|
||||
mqtt_area.arm_away = arm_away;
|
||||
break;
|
||||
case "arm_night":
|
||||
if (!bool.TryParse(attributes["arm_night"], out bool arm_night))
|
||||
throw new Exception("Invalid arm_night attribute");
|
||||
mqtt_area.arm_night = arm_night;
|
||||
break;
|
||||
case "arm_vacation":
|
||||
if (!bool.TryParse(attributes["arm_vacation"], out bool arm_vacation))
|
||||
throw new Exception("Invalid arm_vacation attribute");
|
||||
mqtt_area.arm_vacation = arm_vacation;
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unknown attribute {attribute}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.TryAdd(attrib_id, override_area);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex, "Invalid override area specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<int, T> LoadOverrideZone<T>(this NameValueCollection settings, string section) where T : new()
|
||||
{
|
||||
try
|
||||
@ -157,7 +247,7 @@ namespace OmniLinkBridge
|
||||
}
|
||||
else if (override_zone is MQTT.OverrideZone mqtt_zone)
|
||||
{
|
||||
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out MQTT.BinarySensor.DeviceClass attrib_device_class))
|
||||
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out ha.BinarySensor.DeviceClass attrib_device_class))
|
||||
throw new Exception("Missing or invalid device_class attribute");
|
||||
|
||||
mqtt_zone.device_class = attrib_device_class;
|
||||
@ -170,7 +260,51 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Invalid override zone specified for " + section, ex);
|
||||
log.Error(ex, "Invalid override zone specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<int, T> LoadOverrideUnit<T>(this NameValueCollection settings, string section) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
ConcurrentDictionary<int, T> ret = new ConcurrentDictionary<int, T>();
|
||||
|
||||
string value = settings.CheckEnv(section);
|
||||
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return ret;
|
||||
|
||||
string[] ids = value.Split(',');
|
||||
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
Dictionary<string, string> attributes = ids[i].TrimEnd(new char[] { ';' }).Split(';')
|
||||
.Select(s => s.Split('='))
|
||||
.ToDictionary(a => a[0].Trim(), a => a[1].Trim(), StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (!attributes.ContainsKey("id") || !int.TryParse(attributes["id"], out int attrib_id))
|
||||
throw new Exception("Missing or invalid id attribute");
|
||||
|
||||
T override_unit = new T();
|
||||
|
||||
if (override_unit is MQTT.OverrideUnit mqtt_unit)
|
||||
{
|
||||
if (!attributes.ContainsKey("type") || !Enum.TryParse(attributes["type"], out MQTT.UnitType attrib_type))
|
||||
throw new Exception("Missing or invalid type attribute");
|
||||
|
||||
mqtt_unit.type = attrib_type;
|
||||
}
|
||||
|
||||
ret.TryAdd(attrib_id, override_unit);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex, "Invalid override unit specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -181,7 +315,20 @@ namespace OmniLinkBridge
|
||||
|
||||
if(string.IsNullOrEmpty(value))
|
||||
{
|
||||
log.Error("Empty string specified for " + section);
|
||||
log.Error("Empty string specified for {section}", section);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string ValidateEncryptionKey(this NameValueCollection settings, string section)
|
||||
{
|
||||
string value = settings.CheckEnv(section, true).Replace("-","");
|
||||
|
||||
if (string.IsNullOrEmpty(value) || value.Length != 16)
|
||||
{
|
||||
log.Error("Invalid encryption key specified for {section}", section);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
@ -196,7 +343,7 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid integer specified for " + section);
|
||||
log.Error("Invalid integer specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -214,7 +361,7 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid range specified for " + section);
|
||||
log.Error("Invalid range specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -232,7 +379,7 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid port specified for " + section);
|
||||
log.Error("Invalid port specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -245,7 +392,7 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid email specified for " + section);
|
||||
log.Error("Invalid email specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -269,23 +416,23 @@ namespace OmniLinkBridge
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid email specified for " + section);
|
||||
log.Error("Invalid email specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section)
|
||||
private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section, bool sensitive = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settings.CheckEnv(section) == null)
|
||||
if (settings.CheckEnv(section, true) == null)
|
||||
return new string[] { };
|
||||
|
||||
return settings.CheckEnv(section).Split(',');
|
||||
return settings.CheckEnv(section, sensitive).Split(',');
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid string specified for " + section);
|
||||
log.Error("Invalid string specified for {section}", section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -302,11 +449,26 @@ namespace OmniLinkBridge
|
||||
return false;
|
||||
else
|
||||
{
|
||||
log.Error("Invalid yes/no or true/false specified for " + section);
|
||||
log.Error("Invalid yes/no or true/false specified for {section}", section);
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
private static Type ValidateType(this NameValueCollection settings, string section, params Type[] types)
|
||||
{
|
||||
string value = settings.CheckEnv(section);
|
||||
|
||||
if (value == null)
|
||||
return types[0];
|
||||
|
||||
foreach (Type type in types)
|
||||
if (string.Compare(value, type.Name, true) == 0)
|
||||
return type;
|
||||
|
||||
log.Error("Invalid type specified for {section}", section);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private static NameValueCollection LoadCollection(string[] lines)
|
||||
{
|
||||
NameValueCollection settings = new NameValueCollection();
|
||||
@ -330,24 +492,24 @@ namespace OmniLinkBridge
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static NameValueCollection LoadCollection(string sFile)
|
||||
private static NameValueCollection LoadCollection(string file)
|
||||
{
|
||||
if (ShowDebug)
|
||||
log.Debug($"Using settings file {sFile}");
|
||||
if (Global.DebugSettings)
|
||||
log.Debug("Using settings file {file}", file);
|
||||
|
||||
if(!File.Exists(sFile))
|
||||
if(!File.Exists(file))
|
||||
{
|
||||
log.Warn($"Unable to locate settings file {sFile}");
|
||||
log.Warning("Unable to locate settings file {file}", file);
|
||||
return new NameValueCollection();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return LoadCollection(File.ReadAllLines(sFile));
|
||||
return LoadCollection(File.ReadAllLines(file));
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
log.Error("Error parsing settings file " + sFile, ex);
|
||||
log.Error(ex, "Error parsing settings file {file}", file);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
public enum DeviceType
|
||||
{
|
||||
|
@ -1,9 +1,4 @@
|
||||
using HAI_Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
@ -13,14 +8,15 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public static AreaContract ToContract(this clsArea area)
|
||||
{
|
||||
AreaContract ret = new AreaContract();
|
||||
|
||||
ret.id = (ushort)area.Number;
|
||||
ret.name = area.Name;
|
||||
ret.burglary = area.AreaBurglaryAlarmText;
|
||||
ret.co = area.AreaGasAlarmText;
|
||||
ret.fire = area.AreaFireAlarmText;
|
||||
ret.water = area.AreaWaterAlarmText;
|
||||
AreaContract ret = new AreaContract
|
||||
{
|
||||
id = (ushort)area.Number,
|
||||
name = area.Name,
|
||||
burglary = area.AreaBurglaryAlarmText,
|
||||
co = area.AreaGasAlarmText,
|
||||
fire = area.AreaFireAlarmText,
|
||||
water = area.AreaWaterAlarmText
|
||||
};
|
||||
|
||||
if (area.ExitTimer > 0)
|
||||
{
|
||||
@ -37,12 +33,13 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public static ZoneContract ToContract(this clsZone zone)
|
||||
{
|
||||
ZoneContract ret = new ZoneContract();
|
||||
|
||||
ret.id = (ushort)zone.Number;
|
||||
ret.zonetype = zone.ZoneType;
|
||||
ret.name = zone.Name;
|
||||
ret.status = zone.StatusText();
|
||||
ZoneContract ret = new ZoneContract
|
||||
{
|
||||
id = (ushort)zone.Number,
|
||||
zonetype = zone.ZoneType,
|
||||
name = zone.Name,
|
||||
status = zone.StatusText()
|
||||
};
|
||||
|
||||
if (zone.IsTemperatureZone())
|
||||
ret.temp = zone.TempText();
|
||||
@ -54,10 +51,11 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public static UnitContract ToContract(this clsUnit unit)
|
||||
{
|
||||
UnitContract ret = new UnitContract();
|
||||
|
||||
ret.id = (ushort)unit.Number;
|
||||
ret.name = unit.Name;
|
||||
UnitContract ret = new UnitContract
|
||||
{
|
||||
id = (ushort)unit.Number,
|
||||
name = unit.Name
|
||||
};
|
||||
|
||||
if (unit.Status > 100)
|
||||
ret.level = (ushort)(unit.Status - 100);
|
||||
@ -71,17 +69,16 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public static ThermostatContract ToContract(this clsThermostat unit)
|
||||
{
|
||||
ThermostatContract ret = new ThermostatContract();
|
||||
ThermostatContract ret = new ThermostatContract
|
||||
{
|
||||
id = (ushort)unit.Number,
|
||||
name = unit.Name
|
||||
};
|
||||
|
||||
ret.id = (ushort)unit.Number;
|
||||
ret.name = unit.Name;
|
||||
|
||||
ushort temp, heat, cool, humidity;
|
||||
|
||||
ushort.TryParse(unit.TempText(), out temp);
|
||||
ushort.TryParse(unit.HeatSetpointText(), out heat);
|
||||
ushort.TryParse(unit.CoolSetpointText(), out cool);
|
||||
ushort.TryParse(unit.HumidityText(), out humidity);
|
||||
ushort.TryParse(unit.TempText(), out ushort temp);
|
||||
ushort.TryParse(unit.HeatSetpointText(), out ushort heat);
|
||||
ushort.TryParse(unit.CoolSetpointText(), out ushort cool);
|
||||
ushort.TryParse(unit.HumidityText(), out ushort humidity);
|
||||
|
||||
ret.temp = temp;
|
||||
ret.humidity = humidity;
|
||||
|
@ -1,6 +1,5 @@
|
||||
using HAI_Shared;
|
||||
using log4net;
|
||||
using OmniLinkBridge.WebAPI;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
@ -12,7 +11,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
|
||||
public class OmniLinkService : IOmniLinkService
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public void Subscribe(SubscribeContract contract)
|
||||
{
|
||||
@ -39,7 +38,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public AreaContract GetArea(ushort id)
|
||||
{
|
||||
log.Debug("GetArea: " + id);
|
||||
log.Debug("GetArea: {id}", id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "area");
|
||||
@ -139,7 +138,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public ZoneContract GetZone(ushort id)
|
||||
{
|
||||
log.Debug("GetZone: " + id);
|
||||
log.Debug("GetZone: {id}", id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
|
||||
@ -173,7 +172,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public UnitContract GetUnit(ushort id)
|
||||
{
|
||||
log.Debug("GetUnit: " + id);
|
||||
log.Debug("GetUnit: {id}", id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "unit");
|
||||
@ -183,7 +182,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public void SetUnit(CommandContract unit)
|
||||
{
|
||||
log.Debug("SetUnit: " + unit.id + " to " + unit.value + "%");
|
||||
log.Debug("SetUnit: {id} to {value}%", unit.id, unit.value);
|
||||
|
||||
if (unit.value == 0)
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Off, 0, unit.id);
|
||||
@ -196,7 +195,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public void SetUnitKeypadPress(CommandContract unit)
|
||||
{
|
||||
log.Debug("SetUnitKeypadPress: " + unit.id + " to " + unit.value + " button");
|
||||
log.Debug("SetUnitKeypadPress: {id} to {value}", unit.id, unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.LutronHomeWorksKeypadButtonPress, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
@ -217,7 +216,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public ThermostatContract GetThermostat(ushort id)
|
||||
{
|
||||
log.Debug("GetThermostat: " + id);
|
||||
log.Debug("GetThermostat: {id}", id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "thermostat");
|
||||
@ -236,7 +235,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
}
|
||||
|
||||
int temp = tempHigh.ToOmniTemp();
|
||||
log.Debug("SetThermostatCoolSetpoint: " + unit.id + " to " + unit.value + tempUnit + "(" + temp + ")");
|
||||
log.Debug("SetThermostatCoolSetpoint: {id} to {value}{tempUnit} {temp}", unit.id, unit.value, tempUnit, temp);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
}
|
||||
|
||||
@ -251,25 +250,25 @@ namespace OmniLinkBridge.WebAPI
|
||||
}
|
||||
|
||||
int temp = tempLoad.ToOmniTemp();
|
||||
log.Debug("SetThermostatHeatSetpoint: " + unit.id + " to " + unit.value + tempUnit + "(" + temp + ")");
|
||||
log.Debug("SetThermostatHeatSetpoint: {id} to {value}{tempUnit} {temp}", unit.id, unit.value, tempUnit, temp);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatMode(CommandContract unit)
|
||||
{
|
||||
log.Debug("SetThermostatMode: " + unit.id + " to " + unit.value);
|
||||
log.Debug("SetThermostatMode: {id} to {value}", unit.id, unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatFanMode(CommandContract unit)
|
||||
{
|
||||
log.Debug("SetThermostatFanMode: " + unit.id + " to " + unit.value);
|
||||
log.Debug("SetThermostatFanMode: {id} to {value}", unit.id, unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatHold(CommandContract unit)
|
||||
{
|
||||
log.Debug("SetThermostatHold: " + unit.id + " to " + unit.value);
|
||||
log.Debug("SetThermostatHold: {id} to {value}", unit.id, unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
@ -290,7 +289,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
|
||||
public void PushButton(CommandContract unit)
|
||||
{
|
||||
log.Debug("PushButton: " + unit.id);
|
||||
log.Debug("PushButton: {id}", unit.id);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Button, 0, unit.id);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
public class OverrideZone
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -10,7 +10,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
static class WebNotification
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static List<string> subscriptions = new List<string>();
|
||||
private static readonly object subscriptions_lock = new object();
|
||||
@ -52,7 +52,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred sending notification to " + subscription, ex);
|
||||
log.Error(ex, "An error occurred sending notification to {client}", subscription);
|
||||
subscriptions.Remove(subscription);
|
||||
SaveSubscriptions();
|
||||
}
|
||||
@ -63,7 +63,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
if (e.Error != null)
|
||||
{
|
||||
log.Error("An error occurred sending notification to " + e.UserState.ToString(), e.Error);
|
||||
log.Error(e.Error, "An error occurred sending notification to {client}", e.UserState.ToString());
|
||||
|
||||
lock (subscriptions_lock)
|
||||
subscriptions.Remove(e.UserState.ToString());
|
||||
@ -88,7 +88,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred restoring subscriptions", ex);
|
||||
log.Error(ex, "An error occurred restoring subscriptions");
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ namespace OmniLinkBridge.WebAPI
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred saving subscriptions", ex);
|
||||
log.Error(ex, "An error occurred saving subscriptions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
@ -38,6 +37,64 @@ namespace OmniLinkBridgeTest
|
||||
Assert.AreEqual(true, ((byte)3).IsBitSet(1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestToCommandCode()
|
||||
{
|
||||
string payload;
|
||||
AreaCommandCode parser;
|
||||
|
||||
payload = "disarm";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,1";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 1);
|
||||
|
||||
payload = "disarm,validate,1234";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, true);
|
||||
Assert.AreEqual(parser.Code, 1234);
|
||||
|
||||
// Special case for Home Assistant when code not required
|
||||
payload = "disarm,validate,None";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
// Falures
|
||||
payload = "disarm,1a";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,validate,";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, true);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,test,1234";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestParseRange()
|
||||
{
|
||||
@ -48,4 +105,4 @@ namespace OmniLinkBridgeTest
|
||||
CollectionAssert.AreEqual(new List<int>(new int[] { 1, 2, 3, 5, 6 }), range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using HAI_Shared;
|
||||
using HAI_Shared;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using OmniLinkBridge.Modules;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using OmniLinkBridgeTest.Mock;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
@ -17,8 +20,26 @@ namespace OmniLinkBridgeTest
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
string log_format = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{SourceContext} {Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
var log_config = new LoggerConfiguration()
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: log_format);
|
||||
|
||||
Log.Logger = log_config.CreateLogger();
|
||||
|
||||
Dictionary<string, int> audioSources = new Dictionary<string, int>
|
||||
{
|
||||
{ "Radio", 1 },
|
||||
{ "Streaming", 2 },
|
||||
{ "TV", 4 }
|
||||
};
|
||||
|
||||
omniLink = new MockOmniLinkII();
|
||||
messageProcessor = new MessageProcessor(omniLink);
|
||||
messageProcessor = new MessageProcessor(omniLink, audioSources, 8);
|
||||
|
||||
omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -39,7 +60,7 @@ namespace OmniLinkBridgeTest
|
||||
[TestMethod]
|
||||
public void AreaCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command)
|
||||
void check(ushort id, int code, string payload, enuUnitCommand command)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
@ -47,29 +68,35 @@ namespace OmniLinkBridgeTest
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = 0,
|
||||
Par = (byte)code,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
// First area standard format
|
||||
check(1, "disarm", enuUnitCommand.SecurityOff);
|
||||
check(1, "arm_home", enuUnitCommand.SecurityDay);
|
||||
check(1, "arm_away", enuUnitCommand.SecurityAway);
|
||||
check(1, "arm_night", enuUnitCommand.SecurityNight);
|
||||
check(1, "arm_home_instant", enuUnitCommand.SecurityDyi);
|
||||
check(1, "arm_night_delay", enuUnitCommand.SecurityNtd);
|
||||
check(1, "arm_vacation", enuUnitCommand.SecurityVac);
|
||||
// Standard format
|
||||
check(1, 0, "disarm", enuUnitCommand.SecurityOff);
|
||||
check(1, 0, "arm_home", enuUnitCommand.SecurityDay);
|
||||
check(1, 0, "arm_away", enuUnitCommand.SecurityAway);
|
||||
check(1, 0, "arm_night", enuUnitCommand.SecurityNight);
|
||||
check(1, 0, "arm_home_instant", enuUnitCommand.SecurityDyi);
|
||||
check(1, 0, "arm_night_delay", enuUnitCommand.SecurityNtd);
|
||||
check(1, 0, "arm_vacation", enuUnitCommand.SecurityVac);
|
||||
|
||||
// Last area with case check
|
||||
check(8, "DISARM", enuUnitCommand.SecurityOff);
|
||||
// Check all areas
|
||||
check(0, 0, "disarm", enuUnitCommand.SecurityOff);
|
||||
|
||||
// Check with optional code
|
||||
check(1, 1, "disarm,1", enuUnitCommand.SecurityOff);
|
||||
|
||||
// Check case insensitivity
|
||||
check(8, 0, "DISARM", enuUnitCommand.SecurityOff);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZoneCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command)
|
||||
void check(ushort id, int code, string payload, enuUnitCommand command, bool ensureNull = false)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
@ -77,16 +104,31 @@ namespace OmniLinkBridgeTest
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = 0,
|
||||
Par = (byte)code,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
|
||||
if (ensureNull)
|
||||
Assert.IsNull(actual);
|
||||
else
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "bypass", enuUnitCommand.Bypass);
|
||||
check(1, "restore", enuUnitCommand.Restore);
|
||||
// Standard format
|
||||
check(1, 0, "bypass", enuUnitCommand.Bypass);
|
||||
check(1, 0, "restore", enuUnitCommand.Restore);
|
||||
|
||||
check(2, "BYPASS", enuUnitCommand.Bypass);
|
||||
// Check all zones
|
||||
check(0, 0, "restore", enuUnitCommand.Restore);
|
||||
|
||||
// Not allowed to bypass all zones
|
||||
check(0, 0, "bypass", enuUnitCommand.Bypass, true);
|
||||
|
||||
// Check with optional code
|
||||
check(1, 1, "bypass,1", enuUnitCommand.Bypass);
|
||||
|
||||
// Check case insensitivity
|
||||
check(2, 0, "BYPASS", enuUnitCommand.Bypass);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -113,6 +155,28 @@ namespace OmniLinkBridgeTest
|
||||
check(2, "on", enuUnitCommand.On);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UnitFlagCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int value)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/unit{id}/flag_command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)value,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(395, "0", enuUnitCommand.Set, 0);
|
||||
check(395, "1", enuUnitCommand.Set, 1);
|
||||
check(395, "255", enuUnitCommand.Set, 255);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UnitLevelCommand()
|
||||
{
|
||||
@ -133,6 +197,70 @@ namespace OmniLinkBridgeTest
|
||||
check(1, "50", enuUnitCommand.Level, 50);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThermostatModeCommandInvalid()
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatCool;
|
||||
messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
|
||||
Assert.IsNull(actual);
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.CoolOnly;
|
||||
messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
|
||||
Assert.IsNull(actual);
|
||||
messageProcessor.Process($"omnilink/thermostat1/mode_command", "heat");
|
||||
Assert.IsNull(actual);
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatOnly;
|
||||
messageProcessor.Process($"omnilink/thermostat1/mode_command", "auto");
|
||||
Assert.IsNull(actual);
|
||||
messageProcessor.Process($"omnilink/thermostat1/mode_command", "cool");
|
||||
Assert.IsNull(actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThermostatModeCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int mode)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/thermostat{id}/mode_command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)mode,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.AutoHeatCool;
|
||||
|
||||
check(1, "auto", enuUnitCommand.Mode, (int)enuThermostatMode.Auto);
|
||||
check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
|
||||
check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
|
||||
check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatCool;
|
||||
|
||||
check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
|
||||
check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
|
||||
check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.CoolOnly;
|
||||
|
||||
check(1, "cool", enuUnitCommand.Mode, (int)enuThermostatMode.Cool);
|
||||
check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);
|
||||
|
||||
omniLink.Controller.Thermostats[1].Type = enuThermostatType.HeatOnly;
|
||||
|
||||
check(1, "heat", enuUnitCommand.Mode, (int)enuThermostatMode.Heat);
|
||||
check(1, "off", enuUnitCommand.Mode, (int)enuThermostatMode.Off);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ButtonCommand()
|
||||
{
|
||||
@ -178,6 +306,140 @@ namespace OmniLinkBridgeTest
|
||||
|
||||
check(2, "SHOW", enuUnitCommand.ShowMsgWBeep, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LockCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/lock{id}/command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = 0,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "lock", enuUnitCommand.Lock);
|
||||
check(1, "unlock", enuUnitCommand.Unlock);
|
||||
|
||||
// Check all locks
|
||||
check(0, "lock", enuUnitCommand.Lock);
|
||||
|
||||
// Check case insensitivity
|
||||
check(2, "LOCK", enuUnitCommand.Lock);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int value)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/audio{id}/command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)value,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "ON", enuUnitCommand.AudioZone, 1);
|
||||
check(1, "OFF", enuUnitCommand.AudioZone, 0);
|
||||
|
||||
check(2, "on", enuUnitCommand.AudioZone, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioMuteCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int value)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/audio{id}/mute_command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)value,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "ON", enuUnitCommand.AudioZone, 3);
|
||||
check(1, "OFF", enuUnitCommand.AudioZone, 2);
|
||||
|
||||
Global.mqtt_audio_local_mute = true;
|
||||
omniLink.Controller.AudioZones[2].Volume = 50;
|
||||
|
||||
check(2, "on", enuUnitCommand.AudioVolume, 0);
|
||||
check(2, "off", enuUnitCommand.AudioVolume, 50);
|
||||
|
||||
omniLink.Controller.AudioZones[2].Volume = 0;
|
||||
|
||||
check(2, "on", enuUnitCommand.AudioVolume, 0);
|
||||
check(2, "off", enuUnitCommand.AudioVolume, 10);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioSourceCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int value)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/audio{id}/source_command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)value,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "Radio", enuUnitCommand.AudioSource, 1);
|
||||
check(1, "Streaming", enuUnitCommand.AudioSource, 2);
|
||||
|
||||
check(2, "TV", enuUnitCommand.AudioSource, 4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioVolumeCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command, int value)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/audio{id}/volume_command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = (byte)value,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "100", enuUnitCommand.AudioVolume, 100);
|
||||
check(1, "75", enuUnitCommand.AudioVolume, 75);
|
||||
|
||||
check(2, "0", enuUnitCommand.AudioVolume, 0);
|
||||
|
||||
Global.mqtt_audio_volume_media_player = true;
|
||||
|
||||
check(2, "1", enuUnitCommand.AudioVolume, 100);
|
||||
check(2, "0.75", enuUnitCommand.AudioVolume, 75);
|
||||
check(2, "0", enuUnitCommand.AudioVolume, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,31 @@
|
||||
using HAI_Shared;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridgeTest.Mock
|
||||
{
|
||||
class MockOmniLinkII : IOmniLinkII
|
||||
{
|
||||
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public clsHAC Controller { get; private set; }
|
||||
|
||||
public event EventHandler<SendCommandEventArgs> OnSendCommand;
|
||||
|
||||
public MockOmniLinkII()
|
||||
{
|
||||
Controller = new clsHAC();
|
||||
Controller.Model = enuModel.OMNI_PRO_II;
|
||||
Controller.TempFormat = enuTempFormat.Fahrenheit;
|
||||
Controller = new clsHAC
|
||||
{
|
||||
Model = enuModel.OMNI_PRO_II,
|
||||
TempFormat = enuTempFormat.Fahrenheit
|
||||
};
|
||||
}
|
||||
|
||||
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2)
|
||||
{
|
||||
log.Verbose("Sending: {command}, Par1: {par1}, Par2: {par2}", Cmd, Par, Pr2);
|
||||
OnSendCommand?.Invoke(null, new SendCommandEventArgs() { Cmd = Cmd, Par = Par, Pr2 = Pr2 });
|
||||
return true;
|
||||
}
|
||||
|
@ -1,9 +1,5 @@
|
||||
using HAI_Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridgeTest.Mock
|
||||
{
|
||||
@ -13,16 +9,26 @@ namespace OmniLinkBridgeTest.Mock
|
||||
public byte Par;
|
||||
public ushort Pr2;
|
||||
|
||||
public SendCommandEventArgs()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SendCommandEventArgs(enuUnitCommand cmd, byte par, ushort pr2)
|
||||
{
|
||||
Cmd = cmd;
|
||||
Par = par;
|
||||
Pr2 = pr2;
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
var toCompareWith = other as SendCommandEventArgs;
|
||||
|
||||
if (toCompareWith == null)
|
||||
if (!(other is SendCommandEventArgs toCompareWith))
|
||||
return false;
|
||||
|
||||
return this.Cmd == toCompareWith.Cmd &&
|
||||
this.Par == toCompareWith.Par &&
|
||||
this.Pr2 == toCompareWith.Pr2;
|
||||
return Cmd == toCompareWith.Cmd &&
|
||||
Par == toCompareWith.Par &&
|
||||
Pr2 == toCompareWith.Pr2;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge.Notifications;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using OmniLinkBridge.Notifications;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
|
@ -9,7 +9,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>OmniLinkBridgeTest</RootNamespace>
|
||||
<AssemblyName>OmniLinkBridgeTest</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
|
||||
@ -29,6 +29,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@ -37,6 +38,7 @@
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HAI.Controller, Version=3.11.4.17, Culture=neutral, processorArchitecture=MSIL">
|
||||
@ -57,10 +59,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest.TestAdapter">
|
||||
<Version>2.0.0</Version>
|
||||
<Version>2.2.8</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MSTest.TestFramework">
|
||||
<Version>2.0.0</Version>
|
||||
<Version>2.2.8</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -2,12 +2,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("OmniLinkBridgeTest")]
|
||||
[assembly: AssemblyTitle("OmniLink Bridge Test")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridgeTest")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2019")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using OmniLinkBridge.MQTT.HomeAssistant;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ha = OmniLinkBridge.MQTT.HomeAssistant;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
@ -37,8 +37,8 @@ namespace OmniLinkBridgeTest
|
||||
Settings.LoadSettings(lines.ToArray());
|
||||
Assert.AreEqual("1.1.1.1", Global.controller_address);
|
||||
Assert.AreEqual(4369, Global.controller_port);
|
||||
Assert.AreEqual("00-00-00-00-00-00-00-01", Global.controller_key1);
|
||||
Assert.AreEqual("00-00-00-00-00-00-00-02", Global.controller_key2);
|
||||
Assert.AreEqual("0000000000000001", Global.controller_key1);
|
||||
Assert.AreEqual("0000000000000002", Global.controller_key2);
|
||||
Assert.AreEqual("MyController", Global.controller_name);
|
||||
}
|
||||
|
||||
@ -79,11 +79,14 @@ namespace OmniLinkBridgeTest
|
||||
"verbose_thermostat_timer",
|
||||
"verbose_thermostat",
|
||||
"verbose_unit",
|
||||
"verbose_message"
|
||||
"verbose_message",
|
||||
"verbose_lock"
|
||||
})
|
||||
{
|
||||
List<string> lines = new List<string>(RequiredSettings());
|
||||
lines.Add($"{setting} = yes");
|
||||
List<string> lines = new List<string>(RequiredSettings())
|
||||
{
|
||||
$"{setting} = yes"
|
||||
};
|
||||
Settings.LoadSettings(lines.ToArray());
|
||||
Assert.AreEqual(true, Global.GetValue(setting));
|
||||
}
|
||||
@ -154,8 +157,12 @@ namespace OmniLinkBridgeTest
|
||||
"mqtt_discovery_name_prefix = mynameprefix",
|
||||
"mqtt_discovery_ignore_zones = 1,2-3,4",
|
||||
"mqtt_discovery_ignore_units = 2-5,7",
|
||||
"mqtt_discovery_override_area = id=1",
|
||||
"mqtt_discovery_override_area = id=2;code_arm=true;code_disarm=true;arm_home=false;arm_away=false;arm_night=false;arm_vacation=false",
|
||||
"mqtt_discovery_override_zone = id=5;device_class=garage_door",
|
||||
"mqtt_discovery_override_zone = id=7;device_class=motion",
|
||||
"mqtt_discovery_override_unit = id=1;type=switch",
|
||||
"mqtt_discovery_override_unit = id=395;type=light",
|
||||
});
|
||||
Settings.LoadSettings(lines.ToArray());
|
||||
Assert.AreEqual("myuser", Global.mqtt_username);
|
||||
@ -166,10 +173,29 @@ namespace OmniLinkBridgeTest
|
||||
Assert.IsTrue(Global.mqtt_discovery_ignore_zones.SetEquals(new int[] { 1, 2, 3, 4 }));
|
||||
Assert.IsTrue(Global.mqtt_discovery_ignore_units.SetEquals(new int[] { 2, 3, 4, 5, 7 }));
|
||||
|
||||
Dictionary<int, OmniLinkBridge.MQTT.OverrideArea> override_area = new Dictionary<int, OmniLinkBridge.MQTT.OverrideArea>()
|
||||
{
|
||||
{ 1, new OmniLinkBridge.MQTT.OverrideArea { }},
|
||||
{ 2, new OmniLinkBridge.MQTT.OverrideArea { code_arm = true, code_disarm = true,
|
||||
arm_home = false, arm_away = false, arm_night = false, arm_vacation = false }},
|
||||
};
|
||||
|
||||
Assert.AreEqual(override_area.Count, Global.mqtt_discovery_override_area.Count);
|
||||
foreach (KeyValuePair<int, OmniLinkBridge.MQTT.OverrideArea> pair in override_area)
|
||||
{
|
||||
Global.mqtt_discovery_override_area.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideArea value);
|
||||
Assert.AreEqual(override_area[pair.Key].code_arm, value.code_arm);
|
||||
Assert.AreEqual(override_area[pair.Key].code_disarm, value.code_disarm);
|
||||
Assert.AreEqual(override_area[pair.Key].arm_home, value.arm_home);
|
||||
Assert.AreEqual(override_area[pair.Key].arm_away, value.arm_away);
|
||||
Assert.AreEqual(override_area[pair.Key].arm_night, value.arm_night);
|
||||
Assert.AreEqual(override_area[pair.Key].arm_vacation, value.arm_vacation);
|
||||
}
|
||||
|
||||
Dictionary<int, OmniLinkBridge.MQTT.OverrideZone> override_zone = new Dictionary<int, OmniLinkBridge.MQTT.OverrideZone>()
|
||||
{
|
||||
{ 5, new OmniLinkBridge.MQTT.OverrideZone { device_class = OmniLinkBridge.MQTT.BinarySensor.DeviceClass.garage_door }},
|
||||
{ 7, new OmniLinkBridge.MQTT.OverrideZone { device_class = OmniLinkBridge.MQTT.BinarySensor.DeviceClass.motion }}
|
||||
{ 5, new OmniLinkBridge.MQTT.OverrideZone { device_class = ha.BinarySensor.DeviceClass.garage_door }},
|
||||
{ 7, new OmniLinkBridge.MQTT.OverrideZone { device_class = ha.BinarySensor.DeviceClass.motion }}
|
||||
};
|
||||
|
||||
Assert.AreEqual(override_zone.Count, Global.mqtt_discovery_override_zone.Count);
|
||||
@ -178,6 +204,30 @@ namespace OmniLinkBridgeTest
|
||||
Global.mqtt_discovery_override_zone.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideZone value);
|
||||
Assert.AreEqual(override_zone[pair.Key].device_class, value.device_class);
|
||||
}
|
||||
|
||||
Dictionary<int, OmniLinkBridge.MQTT.OverrideUnit> override_unit = new Dictionary<int, OmniLinkBridge.MQTT.OverrideUnit>()
|
||||
{
|
||||
{ 1, new OmniLinkBridge.MQTT.OverrideUnit { type = OmniLinkBridge.MQTT.UnitType.@switch }},
|
||||
{ 395, new OmniLinkBridge.MQTT.OverrideUnit { type = OmniLinkBridge.MQTT.UnitType.light }}
|
||||
};
|
||||
|
||||
Assert.AreEqual(override_unit.Count, Global.mqtt_discovery_override_unit.Count);
|
||||
foreach (KeyValuePair<int, OmniLinkBridge.MQTT.OverrideUnit> pair in override_unit)
|
||||
{
|
||||
Global.mqtt_discovery_override_unit.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideUnit value);
|
||||
Assert.AreEqual(override_unit[pair.Key].type, value.type);
|
||||
}
|
||||
|
||||
Assert.AreEqual(Global.mqtt_discovery_button_type, typeof(Switch));
|
||||
|
||||
// Test additional settings
|
||||
lines.AddRange(new string[]
|
||||
{
|
||||
"mqtt_discovery_button_type = button"
|
||||
});
|
||||
Settings.LoadSettings(lines.ToArray());
|
||||
|
||||
Assert.AreEqual(Global.mqtt_discovery_button_type, typeof(Button));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -193,8 +243,10 @@ namespace OmniLinkBridgeTest
|
||||
"notify_message"
|
||||
})
|
||||
{
|
||||
List<string> lines = new List<string>(RequiredSettings());
|
||||
lines.Add($"{setting} = yes");
|
||||
List<string> lines = new List<string>(RequiredSettings())
|
||||
{
|
||||
$"{setting} = yes"
|
||||
};
|
||||
Settings.LoadSettings(lines.ToArray());
|
||||
Assert.AreEqual(true, Global.GetValue(setting));
|
||||
}
|
||||
|
131
README.md
131
README.md
@ -1,21 +1,23 @@
|
||||
# OmniLink Bridge
|
||||
Provides MQTT bridge, web service API, time sync, and logging for [HAI/Leviton OmniPro II controllers](https://www.leviton.com/en/products/brands/omni-security-automation). Provides integration with [Samsung SmartThings via web service API](https://github.com/excaliburpartners/SmartThings-OmniPro) and [Home Assistant via MQTT](https://www.home-assistant.io/components/mqtt/).
|
||||
|
||||
Please note that OmniLink Bridge is not in active development. The MQTT and Home Assistant integrations are in maintenance mode. The SmartThings Web API and MySQL logging are deprecated and not feature consistent with MQTT.
|
||||
|
||||
## Download
|
||||
You can use docker to build an image from git or download the [binary here](https://github.com/excaliburpartners/OmniLinkBridge/releases/latest/download/OmniLinkBridge.zip).
|
||||
You can use docker to build an image from git or download the [binary here](https://github.com/excaliburpartners/OmniLinkBridge/releases/latest/download/OmniLinkBridge.zip). You can also install it as a [Home Assistant Add-on](https://github.com/excaliburpartners/hassio-addons).
|
||||
|
||||
## Requirements
|
||||
- [Docker](https://www.docker.com/)
|
||||
- .NET Framework 4.5.2 (or Mono equivalent)
|
||||
- .NET Framework 4.7.2 (or Mono equivalent)
|
||||
|
||||
## Operation
|
||||
OmniLinkBridge is divided into the following modules and configurable settings. Configuration settings can also be set as environment variables by using their name in uppercase. Refer to [OmniLinkBridge.ini](https://github.com/excaliburpartners/OmniLinkBridge/blob/master/OmniLinkBridge/OmniLinkBridge.ini) for specifics.
|
||||
OmniLink Bridge is divided into the following modules and configurable settings. Configuration settings can also be set as environment variables by using their name in uppercase. Refer to [OmniLinkBridge.ini](OmniLinkBridge/OmniLinkBridge.ini) for specifics.
|
||||
|
||||
- OmniLinkII: controller_
|
||||
- Maintains connection to the OmniLink controller
|
||||
- Thermostats
|
||||
- If no status update has been received after 4 minutes a request is issued
|
||||
- A status update containing a temperature of 0 is ignored
|
||||
- A status update containing a temperature of 0 marks the thermostat offline
|
||||
- This can occur when a ZigBee thermostat has lost communication
|
||||
- Time Sync: time_
|
||||
- Controller time is checked and compared to the local computer time disregarding time zones
|
||||
@ -28,7 +30,7 @@ OmniLinkBridge is divided into the following modules and configurable settings.
|
||||
- Provides integration with [Samsung SmartThings](https://github.com/excaliburpartners/SmartThings-OmniPro)
|
||||
- Allows an application to subscribe to receive POST notifications status updates are received from the OmniLinkII module
|
||||
- On failure to POST to callback URL subscription is removed
|
||||
- Recommended for application to send subscribe reqeusts every few minutes
|
||||
- Recommended for application to send subscribe requests every few minutes
|
||||
- Requests to GET endpoints return status from the OmniLinkII module
|
||||
- Requests to POST endpoints send commands to the OmniLinkII module
|
||||
- Logger
|
||||
@ -146,20 +148,54 @@ systemctl start omnilinkbridge.service
|
||||
```
|
||||
|
||||
## MQTT
|
||||
```
|
||||
SUB omnilink/status
|
||||
string online, offline
|
||||
|
||||
SUB omnilink/model
|
||||
string Controller model
|
||||
|
||||
SUB omnilink/version
|
||||
string Controller version
|
||||
```
|
||||
|
||||
### System
|
||||
```
|
||||
SUB omnilink/system/phone/state
|
||||
string secure, trouble
|
||||
|
||||
SUB omnilink/system/ac/state
|
||||
string secure, trouble
|
||||
|
||||
SUB omnilink/system/battery/state
|
||||
string secure, trouble
|
||||
|
||||
SUB omnilink/system/dcm/state
|
||||
string secure, trouble
|
||||
```
|
||||
|
||||
### Areas
|
||||
```
|
||||
SUB omnilink/areaX/name
|
||||
string Area name
|
||||
|
||||
SUB omnilink/areaX/state
|
||||
string triggered, pending, armed_night, armed_night_delay, armed_home, armed_home_instant, armed_away, armed_vacation, disarmed
|
||||
SUB omnilink/areaX/state
|
||||
string triggered, arming, armed_night, armed_night_delay, armed_home, armed_home_instant, armed_away, armed_vacation, disarmed
|
||||
|
||||
SUB omnilink/areaX/basic_state
|
||||
string triggered, pending, armed_night, armed_home, armed_away, disarmed
|
||||
SUB omnilink/areaX/basic_state
|
||||
string triggered, arming, armed_night, armed_home, armed_away, armed_vacation, disarmed
|
||||
|
||||
PUB omnilink/areaX/command
|
||||
SUB omnilink/areaX/json_state
|
||||
string json
|
||||
|
||||
PUB omnilink/areaX/command
|
||||
string arm_home, arm_away, arm_night, disarm, arm_home_instant, arm_night_delay, arm_vacation
|
||||
note Use area 0 for all areas
|
||||
note Optionally the user code number can be specified 'disarm,1'
|
||||
note Optionally the security code can be be specified 'disarm,validate,1234'
|
||||
|
||||
PUB omnilink/areaX/alarm_command
|
||||
string burglary, fire, auxiliary
|
||||
```
|
||||
|
||||
### Zones
|
||||
@ -181,6 +217,8 @@ int Current relative humidity
|
||||
|
||||
PUB omnilink/zoneX/command
|
||||
string bypass, restore
|
||||
note Use zone 0 to restore all zones
|
||||
note Optionally the user code number can be specified 'bypass,1'
|
||||
```
|
||||
|
||||
### Units
|
||||
@ -195,6 +233,14 @@ string OFF, ON
|
||||
SUB omnilink/unitX/brightness_state
|
||||
PUB omnilink/unitX/brightness_command
|
||||
int Level from 0 to 100 percent
|
||||
|
||||
SUB omnilink/unitX/flag_state
|
||||
PUB omnilink/unitX/flag_command
|
||||
int Level from 0 to 255
|
||||
|
||||
SUB omnilink/unitX/scene_state
|
||||
PUB omnilink/unitX/scene_command
|
||||
string A-L
|
||||
```
|
||||
|
||||
### Thermostats
|
||||
@ -202,8 +248,11 @@ int Level from 0 to 100 percent
|
||||
SUB omnilink/thermostatX/name
|
||||
string Thermostat name
|
||||
|
||||
SUB omnilink/thermostatX/status
|
||||
string online, offline
|
||||
|
||||
SUB omnilink/thermostatX/current_operation
|
||||
string idle, cool, heat
|
||||
string idle, cooling, heating
|
||||
|
||||
SUB omnilink/thermostatX/current_temperature
|
||||
int Current temperature in degrees fahrenheit
|
||||
@ -225,15 +274,20 @@ int Setpoint in relative humidity
|
||||
|
||||
SUB omnilink/thermostatX/mode_state
|
||||
PUB omnilink/thermostatX/mode_command
|
||||
string auto, off, cool, heat, e_heat
|
||||
|
||||
SUB omnilink/thermostatX/mode_basic_state
|
||||
string auto, off, cool, heat
|
||||
|
||||
SUB omnilink/thermostatX/fan_mode_state
|
||||
PUB omnilink/thermostatX/fan_mode_command
|
||||
string auto, on, cycle
|
||||
|
||||
SUB omnilink/thermostatX/hold_state
|
||||
SUB omnilink/thermostatX/hold_state
|
||||
string off, on, vacation
|
||||
|
||||
PUB omnilink/thermostatX/hold_command
|
||||
string off, hold
|
||||
string off, on
|
||||
```
|
||||
|
||||
### Buttons
|
||||
@ -260,6 +314,50 @@ PUB omnilink/messageX/command
|
||||
string show, show_no_beep, show_no_beep_or_led, clear
|
||||
```
|
||||
|
||||
### Locks
|
||||
```
|
||||
SUB omnilink/lockX/name
|
||||
string Lock name
|
||||
|
||||
SUB omnilink/lockX/state
|
||||
string locked, unlocked
|
||||
|
||||
PUB omnilink/lockX/command
|
||||
string lock, unlock
|
||||
```
|
||||
|
||||
### Audio Sources
|
||||
```
|
||||
SUB omnilink/sourceXX/name
|
||||
string Audio source name
|
||||
```
|
||||
|
||||
### Audio Zones
|
||||
```
|
||||
SUB omnilink/audioXX/name
|
||||
string Audio zone name
|
||||
|
||||
SUB omnilink/audioXX/state
|
||||
PUB omnilink/audioXX/command
|
||||
string OFF, ON
|
||||
note Use audio 0 to change all audio zones
|
||||
|
||||
SUB omnilink/audioXX/mute_state
|
||||
PUB omnilink/audioXX/mute_command
|
||||
string OFF, ON
|
||||
note Use audio 0 to change all audio zones
|
||||
|
||||
SUB omnilink/audioXX/source_state
|
||||
PUB omnilink/audioXX/source_command
|
||||
string Source name
|
||||
note Refer to omnilink/sourceXX/name
|
||||
|
||||
SUB omnilink/audioXX/volume_state
|
||||
PUB omnilink/audioXX/volume_command
|
||||
int Level from 0 to 100 percent
|
||||
double Level from 0.00 to 1.00 (mqtt_audio_volume_media_player = yes)
|
||||
```
|
||||
|
||||
## Web API
|
||||
To test the web service API you can use your browser to view a page or PowerShell (see below) to change a value.
|
||||
|
||||
@ -324,9 +422,12 @@ POST /PushButton
|
||||
```
|
||||
|
||||
## MySQL
|
||||
The [MySQL ODBC Connector](http://dev.mysql.com/downloads/connector/odbc/) is required for MySQL logging. The docker image comes with the MySQL ODBC connector installed. For Windows and Linux you will need to download and install it.
|
||||
The [MySQL ODBC Connector](http://dev.mysql.com/downloads/connector/odbc/) is required for MySQL logging. The docker image comes with the MySQL ODBC connector installed. For Windows and Linux you will need to download and install it. The Home Assistant Add-on does not support MySQL logging.
|
||||
|
||||
Configure mysql_connection in OmniLinkBridge.ini. For Windows change DRIVER={MySQL} to name of the driver shown in the ODBC Data Source Administrator.
|
||||
```
|
||||
mysql_connection = DRIVER={MySQL};SERVER=localhost;DATABASE=OmniLinkBridge;USER=root;PASSWORD=myPassword;OPTION=3;
|
||||
```
|
||||
```
|
||||
|
||||
## Telemetry
|
||||
OmniLink Bridge collects anonymous telemetry data to help improve the software. You can opt of telemetry by setting a TELEMETRY_OPTOUT environment variable to 1.
|
Loading…
x
Reference in New Issue
Block a user