Compare commits

..

No commits in common. "master" and "1.1.11" have entirely different histories.

70 changed files with 502 additions and 2048 deletions

View File

@ -1,22 +1,14 @@
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 if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
tar zxf mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit.tar.gz && \
RUN 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"; \
else \
mkdir -p /usr/lib/odbc/ && \
touch /etc/odbcinst.ini; \
fi
mysql-connector-odbc-8.0.18-linux-debian9-x86-64bit/bin/myodbc-installer -d -a -n "MySQL" -t "DRIVER=/usr/lib/odbc/libmyodbc8w.so"
COPY . .
RUN nuget restore /build/OmniLinkBridge.sln

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
</configuration>

View File

@ -1,16 +0,0 @@
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));
}
}
}

View File

@ -44,11 +44,9 @@ namespace OmniLinkBridge
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);
log.Information("Started version {Version} on {OperatingSystem} with {Modules}",
Assembly.GetExecutingAssembly().GetName().Version, Environment.OSVersion, modules);
// Startup modules
foreach (IModule module in modules)

View File

@ -1,19 +1,12 @@
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
@ -30,6 +23,18 @@ namespace OmniLinkBridge
{
return (b & (1 << pos)) != 0;
}
public static (string, int) ToCommandCode(this string payload)
{
string[] payloads = payload.Split(',');
int code = 0;
if (payloads.Length > 1)
int.TryParse(payloads[1], out code);
return (payloads[0], code);
}
public static string ToSpaceTitleCase(this string phrase)
{
return Regex.Replace(phrase, "(\\B[A-Z])", " $1");
@ -50,14 +55,5 @@ 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);
}
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Mail;
@ -10,8 +9,6 @@ namespace OmniLinkBridge
{
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;
@ -19,7 +16,6 @@ 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;
@ -35,8 +31,6 @@ 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;
@ -59,12 +53,7 @@ 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;

View File

@ -0,0 +1,9 @@
namespace OmniLinkBridge.MQTT
{
public class Alarm : Device
{
public string command_topic { get; set; }
//public string code { get; set; } = string.Empty;
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
enum AlarmCommands
{

View File

@ -1,10 +0,0 @@
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; }
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
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_night_delay,
arm_vacation
}
}

View File

@ -1,7 +0,0 @@
namespace OmniLinkBridge.MQTT
{
public class Availability
{
public string topic { get; set; } = $"{Global.mqtt_prefix}/status";
}
}

View File

@ -1,15 +1,10 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
public class BinarySensor : Device
{
public BinarySensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
[JsonConverter(typeof(StringEnumConverter))]
public enum DeviceClass
{
@ -32,11 +27,5 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
[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; }
}
}

View File

@ -1,16 +1,9 @@
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
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; }
@ -31,8 +24,8 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
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 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" });
public string hold_state_topic { get; set; }
public string hold_command_topic { get; set; }
public List<string> hold_modes { get; set; } = new List<string>(new string[] { "on", "vacation" });
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
enum CommandTypes
{
@ -7,8 +7,6 @@
unit,
thermostat,
button,
message,
@lock,
audio
message
}
}

View File

@ -0,0 +1,20 @@
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;
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
public class DeviceRegistry
{

View File

@ -1,66 +0,0 @@
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;
}
}
}

View File

@ -1,30 +0,0 @@
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" });
}
}

View File

@ -1,17 +0,0 @@
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; }
}
}

View File

@ -1,44 +0,0 @@
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; }
}
}

View File

@ -1,26 +0,0 @@
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; }
}
}

View File

@ -1,23 +0,0 @@
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; }
}
}

View File

@ -1,16 +0,0 @@
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;
}
}

View File

@ -1,12 +1,7 @@
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
public class Light : Device
{
public Light(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; }
public string brightness_state_topic { get; set; }

View File

@ -1,9 +1,6 @@
using HAI_Shared;
using Newtonsoft.Json;
using System.Collections.Generic;
using OmniLinkBridge.MQTT.HomeAssistant;
using OmniLinkBridge.MQTT.Parser;
using OmniLinkBridge.Modules;
namespace OmniLinkBridge.MQTT
{
@ -16,38 +13,13 @@ namespace OmniLinkBridge.MQTT
public static Alarm ToConfig(this clsArea area)
{
Alarm ret = new Alarm(MQTTModule.MqttDeviceRegistry)
Alarm ret = new Alarm
{
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),
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;
}
@ -90,9 +62,8 @@ namespace OmniLinkBridge.MQTT
case enuSecurityMode.DayInst:
return "armed_home";
case enuSecurityMode.Away:
return "armed_away";
case enuSecurityMode.Vacation:
return "armed_vacation";
return "armed_away";
case enuSecurityMode.Off:
default:
return "disarmed";
@ -101,7 +72,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigBurglary(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}burglary",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Burglary",
@ -114,7 +85,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigFire(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}fire",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Fire",
@ -127,7 +98,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigGas(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}gas",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Gas",
@ -140,20 +111,20 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigAux(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
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 %}"
value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}"
};
return ret;
}
public static BinarySensor ToConfigFreeze(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}freeze",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Freeze",
@ -166,7 +137,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigWater(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}water",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Water",
@ -179,7 +150,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigDuress(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}duress",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Duress",
@ -192,7 +163,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigTemp(this clsArea area)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}area{area.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Temp",
@ -205,7 +176,7 @@ namespace OmniLinkBridge.MQTT
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),
@ -215,17 +186,18 @@ 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),
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",
}
temperature_alarm = area.AreaAlarms.IsBitSet(7)
};
state.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",
};
return JsonConvert.SerializeObject(state);
}
@ -237,7 +209,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigTemp(this clsZone zone, enuTempFormat format)
{
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
Sensor ret = new Sensor
{
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Temp",
@ -250,7 +222,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigHumidity(this clsZone zone)
{
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
Sensor ret = new Sensor
{
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}humidity",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Humidity",
@ -263,7 +235,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigSensor(this clsZone zone)
{
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
Sensor ret = new Sensor
{
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}",
name = Global.mqtt_discovery_name_prefix + zone.Name
@ -305,7 +277,7 @@ namespace OmniLinkBridge.MQTT
public static Switch ToConfigSwitch(this clsZone zone)
{
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
Switch ret = new Switch
{
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}switch",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Bypass",
@ -320,7 +292,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfig(this clsZone zone)
{
BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
BinarySensor ret = new BinarySensor
{
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}binary",
name = Global.mqtt_discovery_name_prefix + zone.Name
@ -395,7 +367,7 @@ namespace OmniLinkBridge.MQTT
public static Light ToConfig(this clsUnit unit)
{
Light ret = new Light(MQTTModule.MqttDeviceRegistry)
Light ret = new Light
{
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}light",
name = Global.mqtt_discovery_name_prefix + unit.Name,
@ -409,7 +381,7 @@ namespace OmniLinkBridge.MQTT
public static Switch ToConfigSwitch(this clsUnit unit)
{
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
Switch ret = new Switch
{
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}switch",
name = Global.mqtt_discovery_name_prefix + unit.Name,
@ -419,20 +391,6 @@ namespace OmniLinkBridge.MQTT
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;
}
public static string ToState(this clsUnit unit)
{
return unit.Status == 0 || unit.Status == 100 ? UnitCommands.OFF.ToString() : UnitCommands.ON.ToString();
@ -464,7 +422,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigTemp(this clsThermostat thermostat, enuTempFormat format)
{
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
Sensor ret = new Sensor
{
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Temp",
@ -477,7 +435,7 @@ namespace OmniLinkBridge.MQTT
public static Number ToConfigHumidify(this clsThermostat thermostat)
{
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
Number ret = new Number
{
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidify",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidify",
@ -490,7 +448,7 @@ namespace OmniLinkBridge.MQTT
public static Number ToConfigDehumidify(this clsThermostat thermostat)
{
Number ret = new Number(MQTTModule.MqttDeviceRegistry)
Number ret = new Number
{
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}dehumidify",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Dehumidify",
@ -503,7 +461,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigHumidity(this clsThermostat thermostat)
{
Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
Sensor ret = new Sensor
{
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidity",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidity",
@ -516,19 +474,8 @@ namespace OmniLinkBridge.MQTT
public static Climate ToConfig(this clsThermostat thermostat, enuTempFormat format)
{
Climate ret = new Climate(MQTTModule.MqttDeviceRegistry)
Climate ret = new Climate
{
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}",
name = Global.mqtt_discovery_name_prefix + thermostat.Name,
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" }),
@ -536,25 +483,7 @@ namespace OmniLinkBridge.MQTT
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)
@ -563,6 +492,26 @@ namespace OmniLinkBridge.MQTT
ret.max_temp = "35";
}
ret.unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}";
ret.name = Global.mqtt_discovery_name_prefix + thermostat.Name;
ret.action_topic = thermostat.ToTopic(Topic.current_operation);
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_basic_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;
}
@ -597,9 +546,9 @@ namespace OmniLinkBridge.MQTT
return $"{Global.mqtt_prefix}/button{button.Number}/{topic}";
}
public static Switch ToConfigSwitch(this clsButton button)
public static Switch ToConfig(this clsButton button)
{
Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
Switch ret = new Switch
{
unique_id = $"{Global.mqtt_prefix}button{button.Number}",
name = Global.mqtt_discovery_name_prefix + button.Name,
@ -609,18 +558,6 @@ namespace OmniLinkBridge.MQTT
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}/{topic}";
@ -635,135 +572,5 @@ 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;
}
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
enum MessageCommands
{

View File

@ -1,12 +1,10 @@
using HAI_Shared;
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
{
@ -16,18 +14,11 @@ namespace OmniLinkBridge.MQTT
private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
private readonly int[] audioMuteVolumes;
private const int VOLUME_DEFAULT = 10;
private IOmniLinkII OmniLink { get; set; }
private IOmniLinkII OmniLink { get; }
private Dictionary<string, int> AudioSources { get; }
public MessageProcessor(IOmniLinkII omni, Dictionary<string, int> audioSources, int numAudioZones)
public MessageProcessor(IOmniLinkII omni)
{
OmniLink = omni;
AudioSources = audioSources;
audioMuteVolumes = new int[numAudioZones];
}
public void Process(string messageTopic, string payload)
@ -37,8 +28,8 @@ 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;
@ -57,10 +48,6 @@ 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>
@ -69,71 +56,28 @@ 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_night_delay, enuUnitCommand.SecurityNtd },
{ AreaCommands.arm_vacation, enuUnitCommand.SecurityVac },
};
private void ProcessAreaReceived(clsArea area, Topic command, string payload)
{
AreaCommandCode parser = payload.ToCommandCode(supportValidate: true);
int code;
(payload, code) = payload.ToCommandCode();
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out AreaCommands cmd))
if (command == Topic.command && Enum.TryParse(payload, true, out AreaCommands cmd))
{
if (area.Number == 0)
log.Debug("SetArea: 0 implies all areas will be changed");
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);
log.Debug("SetArea: {id} to {value}", area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "));
OmniLink.SendCommand(AreaMapping[cmd], (byte)code, (ushort)area.Number);
}
else if (command == Topic.alarm_command && area.Number > 0 && Enum.TryParse(parser.Command, true, out AlarmCommands alarm))
else if (command == Topic.alarm_command && area.Number > 0 && Enum.TryParse(payload, true, out AlarmCommands alarm))
{
log.Debug("SetAreaAlarm: {id} to {value}", area.Number, parser.Command);
log.Debug("SetAreaAlarm: {id} to {value}", area.Number, payload);
OmniLink.Controller.Connection.Send(new clsOL2MsgActivateKeypadEmg(OmniLink.Controller.Connection)
{
@ -151,16 +95,17 @@ namespace OmniLinkBridge.MQTT
private void ProcessZoneReceived(clsZone zone, Topic command, string payload)
{
AreaCommandCode parser = payload.ToCommandCode();
int code;
(payload, code) = payload.ToCommandCode();
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out ZoneCommands cmd) &&
if (command == Topic.command && Enum.TryParse(payload, true, out ZoneCommands cmd) &&
!(zone.Number == 0 && cmd == ZoneCommands.bypass))
{
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);
log.Debug("SetZone: {id} to {value}", zone.Number, payload);
OmniLink.SendCommand(ZoneMapping[cmd], (byte)code, (ushort)zone.Number);
}
}
@ -180,16 +125,10 @@ namespace OmniLinkBridge.MQTT
OmniLink.SendCommand(UnitMapping[cmd], 0, (ushort)unit.Number);
}
}
else if (unit.Type == enuOL2UnitType.Flag &&
command == Topic.flag_command && int.TryParse(payload, out int flagValue))
{
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))
else if (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
@ -197,10 +136,10 @@ 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))
else if (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);
}
}
@ -306,111 +245,5 @@ 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);
}
}
}
}

View File

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT
{
public class Number : Device
{
public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; }
}
}

View File

@ -1,19 +0,0 @@
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;
}
}

View File

@ -1,7 +0,0 @@
namespace OmniLinkBridge.MQTT
{
public class OverrideUnit
{
public UnitType type { get; set; }
}
}

View File

@ -1,6 +1,4 @@
using OmniLinkBridge.MQTT.HomeAssistant;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class OverrideZone
{

View File

@ -1,8 +0,0 @@
namespace OmniLinkBridge.MQTT.Parser
{
enum LockCommands
{
@lock,
unlock
}
}

View File

@ -1,15 +1,10 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
public class Sensor : Device
{
public Sensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
[JsonConverter(typeof(StringEnumConverter))]
public enum DeviceClass
{
@ -20,6 +15,9 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
[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; }

View File

@ -1,14 +1,9 @@
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT.HomeAssistant
namespace OmniLinkBridge.MQTT
{
public class Switch : Device
{
public Switch(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]

View File

@ -1,9 +1,8 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
public enum Topic
{
name,
status,
state,
command,
alarm_command,
@ -11,8 +10,6 @@
json_state,
brightness_state,
brightness_command,
flag_state,
flag_command,
scene_state,
scene_command,
current_operation,
@ -33,11 +30,5 @@
fan_mode_command,
hold_state,
hold_command,
mute_state,
mute_command,
source_state,
source_command,
volume_state,
volume_command,
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
enum UnitCommands
{

View File

@ -1,9 +0,0 @@
namespace OmniLinkBridge.MQTT
{
public enum UnitType
{
@switch,
light,
number
}
}

View File

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT.Parser
namespace OmniLinkBridge.MQTT
{
enum ZoneCommands
{

View File

@ -2,7 +2,6 @@
using OmniLinkBridge.Notifications;
using OmniLinkBridge.OmniLink;
using Serilog;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Data;
@ -39,8 +38,6 @@ namespace OmniLinkBridge.Modules
omnilink.OnThermostatStatus += Omnilink_OnThermostatStatus;
omnilink.OnUnitStatus += Omnilink_OnUnitStatus;
omnilink.OnMessageStatus += Omnilink_OnMessageStatus;
omnilink.OnLockStatus += Omnilink_OnLockStatus;
omnilink.OnAudioZoneStatus += Omnilink_OnAudioZoneStatus;
omnilink.OnSystemStatus += Omnilink_OnSystemStatus;
}
@ -48,7 +45,6 @@ namespace OmniLinkBridge.Modules
{
if (Global.mysql_logging)
{
log.Warning("MySQL logging is deprecated");
log.Information("Connecting to database");
mysql_conn = new OdbcConnection(Global.mysql_connection);
@ -129,18 +125,15 @@ namespace OmniLinkBridge.Modules
private void Omnilink_OnConnect(object sender, EventArgs e)
{
ushort areaUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Areas.Count; i++)
if (Global.verbose_area)
{
clsArea area = omnilink.Controller.Areas[i];
if (i > 1 && area.DefaultProperties == true)
continue;
areaUsage++;
if (Global.verbose_area)
for (ushort i = 1; i <= omnilink.Controller.Areas.Count; i++)
{
clsArea area = omnilink.Controller.Areas[i];
if (i > 1 && area.DefaultProperties == true)
continue;
string status = area.ModeText();
if (area.ExitTimer > 0)
@ -153,104 +146,21 @@ namespace OmniLinkBridge.Modules
}
}
ushort zoneUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Zones.Count; i++)
if (Global.verbose_zone)
{
clsZone zone = omnilink.Controller.Zones[i];
if (zone.DefaultProperties == true)
continue;
zoneUsage++;
if (Global.verbose_zone)
for (ushort i = 1; i <= omnilink.Controller.Zones.Count; i++)
{
clsZone zone = omnilink.Controller.Zones[i];
if (zone.DefaultProperties == true)
continue;
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)
@ -278,7 +188,7 @@ namespace OmniLinkBridge.Modules
e.Area.AreaDuressAlarmText + "','" + status + "')");
if (Global.verbose_area)
log.Verbose("AreaStatus {id} {name}, Status: {status}, Alarms: {alarms}", e.ID, e.Area.Name, status, e.Area.AreaAlarms);
log.Verbose("AreaStatus {id} {name}, Status: {status}, Alarams: {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());
@ -336,10 +246,6 @@ 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.Verbose("ThermostatStatus {id} {name}, Status: {temp} {status}, " +
@ -385,19 +291,6 @@ namespace OmniLinkBridge.Modules
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(@"

View File

@ -9,13 +9,10 @@ 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;
@ -34,16 +31,11 @@ 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;
@ -55,11 +47,9 @@ 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, AudioSources, omni.Controller.CAP.numAudioZones);
MessageProcessor = new MessageProcessor(omni);
}
public void Startup()
@ -107,7 +97,7 @@ namespace OmniLinkBridge.Modules
MqttClient.ConnectingFailedHandler = new ConnectingFailedHandlerDelegate((e) => log.Error("Error connecting {reason}", e.Exception.Message));
MqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate((e) => log.Debug("Disconnected"));
MqttClient.StartAsync(manoptions).Wait();
MqttClient.StartAsync(manoptions);
MqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate((e) =>
MessageProcessor.Process(e.ApplicationMessage.Topic, Encoding.UTF8.GetString(e.ApplicationMessage.Payload)));
@ -118,7 +108,6 @@ namespace OmniLinkBridge.Modules
Topic.command,
Topic.alarm_command,
Topic.brightness_command,
Topic.flag_command,
Topic.scene_command,
Topic.temperature_heat_command,
Topic.temperature_cool_command,
@ -126,10 +115,7 @@ namespace OmniLinkBridge.Modules
Topic.dehumidify_command,
Topic.mode_command,
Topic.fan_mode_command,
Topic.hold_command,
Topic.mute_command,
Topic.source_command,
Topic.volume_command
Topic.hold_command
};
toSubscribe.ForEach((command) => MqttClient.SubscribeAsync(
@ -140,7 +126,7 @@ namespace OmniLinkBridge.Modules
PublishControllerStatus(OFFLINE);
MqttClient.StopAsync().Wait();
MqttClient.StopAsync();
}
public void Shutdown()
@ -167,7 +153,7 @@ namespace OmniLinkBridge.Modules
private void PublishControllerStatus(string status)
{
log.Information("Publishing controller {status}", status);
PublishAsync($"{Global.mqtt_prefix}/{Topic.status}", status);
PublishAsync($"{Global.mqtt_prefix}/status", status);
}
private void PublishConfig()
@ -179,9 +165,6 @@ namespace OmniLinkBridge.Modules
PublishThermostats();
PublishButtons();
PublishMessages();
PublishLocks();
PublishAudioSources();
PublishAudioZones();
PublishControllerStatus(ONLINE);
PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText());
@ -201,10 +184,10 @@ namespace OmniLinkBridge.Modules
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);
PublishAsync(SystemTroubleTopic("phone"), OmniLink.TroublePhone ? "trouble" : "secure");
PublishAsync(SystemTroubleTopic("ac"), OmniLink.TroubleAC ? "trouble" : "secure");
PublishAsync(SystemTroubleTopic("battery"), OmniLink.TroubleBattery ? "trouble" : "secure");
PublishAsync(SystemTroubleTopic("dcn"), OmniLink.TroubleDCM ? "trouble" : "secure");
}
public string SystemTroubleTopic(string type)
@ -214,14 +197,12 @@ namespace OmniLinkBridge.Modules
public BinarySensor SystemTroubleConfig(string type, string name)
{
return new BinarySensor(MQTTModule.MqttDeviceRegistry)
return new BinarySensor
{
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
device_class = BinarySensor.DeviceClass.problem
};
}
@ -233,8 +214,8 @@ namespace OmniLinkBridge.Modules
{
clsArea area = OmniLink.Controller.Areas[i];
// PC Access doesn't let you customize the area name when configured for one area.
// Ignore default properties for the first area.
// 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.
if (i > 1 && area.DefaultProperties == true)
{
PublishAsync(area.ToTopic(Topic.name), null);
@ -325,7 +306,6 @@ namespace OmniLinkBridge.Modules
for (ushort i = 1; i <= OmniLink.Controller.Units.Count; i++)
{
clsUnit unit = OmniLink.Controller.Units[i];
UnitType unitType = unit.ToUnitType();
if (unit.DefaultProperties == true)
{
@ -339,26 +319,17 @@ namespace OmniLinkBridge.Modules
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
{
foreach(UnitType entry in Enum.GetValues(typeof(UnitType)))
PublishAsync($"{Global.mqtt_discovery_prefix}/{entry}/{Global.mqtt_prefix}/unit{i}/config", null);
string type = i < 385 ? "light" : "switch";
PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/{Global.mqtt_prefix}/unit{i}/config", null);
continue;
}
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",
if (i < 385)
PublishAsync($"{Global.mqtt_discovery_prefix}/light/{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()));
else
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/unit{i}/config",
JsonConvert.SerializeObject(unit.ToConfigSwitch()));
}
}
@ -384,7 +355,6 @@ namespace OmniLinkBridge.Modules
PublishThermostatState(thermostat);
PublishAsync(thermostat.ToTopic(Topic.name), thermostat.Name);
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}/number/{Global.mqtt_prefix}/thermostat{i}humidify/config",
@ -402,9 +372,6 @@ namespace OmniLinkBridge.Modules
{
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++)
{
clsButton button = OmniLink.Controller.Buttons[i];
@ -413,7 +380,6 @@ namespace OmniLinkBridge.Modules
{
PublishAsync(button.ToTopic(Topic.name), 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;
}
@ -421,19 +387,8 @@ namespace OmniLinkBridge.Modules
PublishAsync(button.ToTopic(Topic.state), "OFF");
PublishAsync(button.ToTopic(Topic.name), button.Name);
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()));
}
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config",
JsonConvert.SerializeObject(button.ToConfig()));
}
}
@ -451,103 +406,12 @@ namespace OmniLinkBridge.Modules
continue;
}
PublishMessageStateAsync(message);
PublishMessageState(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)
@ -604,17 +468,8 @@ namespace OmniLinkBridge.Modules
return;
// Ignore events fired by thermostat polling
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);
if (!e.EventTimer)
PublishThermostatState(e.Thermostat);
}
private async void OmniLink_OnButtonStatus(object sender, ButtonStatusEventArgs e)
@ -622,7 +477,7 @@ namespace OmniLinkBridge.Modules
if (!MqttClient.IsConnected)
return;
await PublishButtonStateAsync(e.Button);
await PublishButtonState(e.Button);
}
private void OmniLink_OnMessageStatus(object sender, MessageStatusEventArgs e)
@ -630,23 +485,7 @@ namespace OmniLinkBridge.Modules
if (!MqttClient.IsConnected)
return;
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);
PublishMessageState(e.Message);
}
private void OmniLink_OnSystemStatus(object sender, SystemStatusEventArgs e)
@ -655,13 +494,13 @@ namespace OmniLinkBridge.Modules
return;
if(e.Type == SystemEventType.Phone)
PublishAsync(SystemTroubleTopic("phone"), e.Trouble ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("phone"), e.Trouble ? "trouble" : "secure");
else if (e.Type == SystemEventType.AC)
PublishAsync(SystemTroubleTopic("ac"), e.Trouble ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("ac"), e.Trouble ? "trouble" : "secure");
else if (e.Type == SystemEventType.Button)
PublishAsync(SystemTroubleTopic("battery"), e.Trouble ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("battery"), e.Trouble ? "trouble" : "secure");
else if (e.Type == SystemEventType.DCM)
PublishAsync(SystemTroubleTopic("dcm"), e.Trouble ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("dcm"), e.Trouble ? "trouble" : "secure");
}
private void PublishAreaState(clsArea area)
@ -686,11 +525,7 @@ namespace OmniLinkBridge.Modules
{
PublishAsync(unit.ToTopic(Topic.state), unit.ToState());
if (unit.Type == enuOL2UnitType.Flag)
{
PublishAsync(unit.ToTopic(Topic.flag_state), ((ushort)unit.Status).ToString());
}
else if(unit.Type != enuOL2UnitType.Output)
if (unit.Number < 385)
{
PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString());
PublishAsync(unit.ToTopic(Topic.scene_state), unit.ToSceneState());
@ -712,7 +547,7 @@ namespace OmniLinkBridge.Modules
PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower());
}
private async Task PublishButtonStateAsync(clsButton button)
private async Task PublishButtonState(clsButton button)
{
// Simulate a momentary press
await PublishAsync(button.ToTopic(Topic.state), "ON");
@ -720,23 +555,9 @@ namespace OmniLinkBridge.Modules
await PublishAsync(button.ToTopic(Topic.state), "OFF");
}
private Task PublishMessageStateAsync(clsMessage message)
private void PublishMessageState(clsMessage message)
{
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());
PublishAsync(message.ToTopic(Topic.state), message.ToState());
}
private Task PublishAsync(string topic, string payload)

View File

@ -40,8 +40,6 @@ 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);
@ -53,7 +51,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;
@ -87,7 +85,6 @@ namespace OmniLinkBridge.Modules
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);
}
@ -98,18 +95,16 @@ namespace OmniLinkBridge.Modules
{
retry = DateTime.Now.AddMinutes(1);
log.Debug("Controller: {connectionStatus}", "Connect");
Controller.Connection.Connect(HandleConnectStatus, HandleUnsolicitedPackets);
}
}
private void Disconnect()
{
log.Debug("Controller Status: {connectionStatus}", "Disconnecting");
if (Controller.Connection.ConnectionState != enuOmniLinkConnectionState.Offline)
{
log.Debug("Controller: {connectionStatus}", "Disconnect");
Controller.Connection.Disconnect();
}
}
private void HandleConnectStatus(enuOmniLinkCommStatus CS)
@ -140,9 +135,62 @@ namespace OmniLinkBridge.Modules
log.Warning("Controller Status: {connectionStatus}", status);
break;
default:
case enuOmniLinkCommStatus.NoReply:
case enuOmniLinkCommStatus.UnrecognizedReply:
case enuOmniLinkCommStatus.UnsupportedProtocol:
case enuOmniLinkCommStatus.ClientSessionTerminated:
case enuOmniLinkCommStatus.ControllerSessionTerminated:
case enuOmniLinkCommStatus.CannotStartNewSession:
case enuOmniLinkCommStatus.LoginFailed:
case enuOmniLinkCommStatus.UnableToOpenSocket:
case enuOmniLinkCommStatus.UnableToConnect:
case enuOmniLinkCommStatus.SocketClosed:
case enuOmniLinkCommStatus.UnexpectedError:
case enuOmniLinkCommStatus.UnableToCreateSocket:
case enuOmniLinkCommStatus.PermissionDenied:
case enuOmniLinkCommStatus.BadAddress:
case enuOmniLinkCommStatus.InvalidArgument:
case enuOmniLinkCommStatus.TooManyOpenFiles:
case enuOmniLinkCommStatus.ResourceTemporarilyUnavailable:
case enuOmniLinkCommStatus.SocketOperationOnNonSocket:
case enuOmniLinkCommStatus.DestinationAddressRequired:
case enuOmniLinkCommStatus.MessgeTooLong:
case enuOmniLinkCommStatus.WrongProtocolType:
case enuOmniLinkCommStatus.BadProtocolOption:
case enuOmniLinkCommStatus.ProtocolNotSupported:
case enuOmniLinkCommStatus.SocketTypeNotSupported:
case enuOmniLinkCommStatus.OperationNotSupported:
case enuOmniLinkCommStatus.ProtocolFamilyNotSupported:
case enuOmniLinkCommStatus.AddressFamilyNotSupported:
case enuOmniLinkCommStatus.AddressInUse:
case enuOmniLinkCommStatus.AddressNotAvailable:
case enuOmniLinkCommStatus.NetworkIsDown:
case enuOmniLinkCommStatus.NetworkIsUnreachable:
case enuOmniLinkCommStatus.NetworkReset:
case enuOmniLinkCommStatus.ConnectionAborted:
case enuOmniLinkCommStatus.ConnectionResetByPeer:
case enuOmniLinkCommStatus.NoBufferSpaceAvailable:
case enuOmniLinkCommStatus.NotConnected:
case enuOmniLinkCommStatus.CannotSendAfterShutdown:
case enuOmniLinkCommStatus.ConnectionTimedOut:
case enuOmniLinkCommStatus.ConnectionRefused:
case enuOmniLinkCommStatus.HostIsDown:
case enuOmniLinkCommStatus.HostUnreachable:
case enuOmniLinkCommStatus.TooManyProcesses:
case enuOmniLinkCommStatus.NetworkSubsystemIsUnavailable:
case enuOmniLinkCommStatus.UnsupportedVersion:
case enuOmniLinkCommStatus.NotInitialized:
case enuOmniLinkCommStatus.ShutdownInProgress:
case enuOmniLinkCommStatus.ClassTypeNotFound:
case enuOmniLinkCommStatus.HostNotFound:
case enuOmniLinkCommStatus.HostNotFoundTryAgain:
case enuOmniLinkCommStatus.NonRecoverableError:
case enuOmniLinkCommStatus.NoDataOfRequestedType:
log.Error("Controller Status: {connectionStatus}", status);
break;
default:
break;
}
}
@ -155,7 +203,7 @@ namespace OmniLinkBridge.Modules
}
}
private async void HandleIdentifyController(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
private void HandleIdentifyController(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
{
if (Timeout)
return;
@ -181,7 +229,8 @@ namespace OmniLinkBridge.Modules
log.Information("Controller is {ControllerModel} firmware {ControllerVersion}",
Controller.GetModelText(), Controller.GetVersionText());
await ConnectedAsync();
_ = Connected();
return;
}
@ -190,7 +239,7 @@ namespace OmniLinkBridge.Modules
}
}
private async Task ConnectedAsync()
private async Task Connected()
{
retry = DateTime.MinValue;
@ -201,8 +250,6 @@ namespace OmniLinkBridge.Modules
tstat_timer.Start();
OnConnect?.Invoke(this, new EventArgs());
Program.ShowSendLogsWarning();
}
#endregion
@ -219,9 +266,6 @@ namespace OmniLinkBridge.Modules
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()
@ -233,8 +277,7 @@ namespace OmniLinkBridge.Modules
await Task.Run(() =>
{
if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
log.Error("Timeout occurred waiting system formats");
nameWait.WaitOne(new TimeSpan(0, 0, 10));
});
}
@ -247,8 +290,7 @@ namespace OmniLinkBridge.Modules
await Task.Run(() =>
{
if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
log.Error("Timeout occurred waiting for system troubles");
nameWait.WaitOne(new TimeSpan(0, 0, 10));
});
}
@ -260,8 +302,7 @@ namespace OmniLinkBridge.Modules
await Task.Run(() =>
{
if (!nameWait.WaitOne(new TimeSpan(0, 0, 30)))
log.Error("Timeout occurred waiting for named units {unitType}", type.ToString());
nameWait.WaitOne(new TimeSpan(0, 0, 10));
});
}
@ -327,8 +368,7 @@ 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:
@ -339,8 +379,7 @@ namespace OmniLinkBridge.Modules
else
tstats[MSG.ObjectNumber] = DateTime.MinValue;
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
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;
@ -353,17 +392,6 @@ 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;
}
@ -444,8 +472,6 @@ namespace OmniLinkBridge.Modules
case enuOmniLink2MessageType.CmdExtSecurity:
break;
case enuOmniLink2MessageType.AudioSourceStatus:
// Ignore audio source metadata status updates
handled = true;
break;
case enuOmniLink2MessageType.SystemEvents:
HandleUnsolicitedSystemEvent(B);
@ -662,13 +688,19 @@ namespace OmniLinkBridge.Modules
{
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
// Don't fire event when invalid temperature of 0 is sometimes received
if (Controller.Thermostats[MSG.ObjectNumber(i)].Temp > 0)
{
ID = MSG.ObjectNumber(i),
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
Offline = Controller.Thermostats[MSG.ObjectNumber(i)].Temp == 0,
EventTimer = false
});
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
{
ID = MSG.ObjectNumber(i),
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
EventTimer = false
});
}
else if (Global.verbose_thermostat_timer)
log.Debug("Ignoring unsolicited unknown temp for Thermostat {thermostatName}",
Controller.Thermostats[MSG.ObjectNumber(i)].Name);
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
@ -703,28 +735,6 @@ 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)
{
@ -758,12 +768,9 @@ 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) &&
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
if (RoundToMinute(tstat.Value).AddMinutes(4) <= RoundToMinute(DateTime.Now))
{
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 {thermostatName}",
@ -775,13 +782,19 @@ namespace OmniLinkBridge.Modules
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
{
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
// Don't fire event when invalid temperature of 0 is sometimes received
if (Controller.Thermostats[tstat.Key].Temp > 0)
{
ID = tstat.Key,
Thermostat = Controller.Thermostats[tstat.Key],
Offline = Controller.Thermostats[tstat.Key].Temp == 0,
EventTimer = true
});
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
{
ID = tstat.Key,
Thermostat = Controller.Thermostats[tstat.Key],
EventTimer = true
});
}
else if (Global.verbose_thermostat_timer)
log.Warning("Ignoring unknown temp for Thermostat {thermostatName}",
Controller.Thermostats[tstat.Key].Name);
}
else if (Global.verbose_thermostat_timer)
log.Warning("Not logging out of date status for Thermostat {thermostatName}",

View File

@ -74,15 +74,14 @@ namespace OmniLinkBridge.Modules
// Extract the 2 digit prefix to use when parsing the time
int year = DateTime.Now.Year / 100;
time = new DateTime(MSG.Year + (year * 100), MSG.Month, MSG.Day, MSG.Hour, MSG.Minute, MSG.Second);
time = new DateTime((int)MSG.Year + (year * 100), (int)MSG.Month, (int)MSG.Day, (int)MSG.Hour, (int)MSG.Minute, (int)MSG.Second);
}
catch
{
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;
@ -93,11 +92,10 @@ namespace OmniLinkBridge.Modules
if (adj > Global.time_drift)
{
log.Warning("Controller time {controllerTime} out of sync by {driftSeconds} seconds",
time.ToString("MM/dd/yyyy HH:mm:ss"), adj);
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);
}
}

View File

@ -33,8 +33,6 @@ namespace OmniLinkBridge
public void Startup()
{
log.Warning("WebAPI is deprecated");
WebNotification.RestoreSubscriptions();
Uri uri = new Uri("http://0.0.0.0:" + Global.webapi_port + "/");

View File

@ -26,21 +26,23 @@ namespace OmniLinkBridge.Notifications
};
mail.To.Add(address);
using SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port);
smtp.EnableSsl = Global.mail_tls;
if (!string.IsNullOrEmpty(Global.mail_username))
using (SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port))
{
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
}
smtp.EnableSsl = Global.mail_tls;
if (!string.IsNullOrEmpty(Global.mail_username))
{
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
}
try
{
smtp.Send(mail);
}
catch (Exception ex)
{
log.Error(ex, "An error occurred sending email notification");
try
{
smtp.Send(mail);
}
catch (Exception ex)
{
log.Error(ex, "An error occurred sending email notification");
}
}
}
}

View File

@ -25,10 +25,12 @@ 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;
}
}
}

View File

@ -24,9 +24,11 @@ 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;
}
}
}

View File

@ -1,11 +0,0 @@
using HAI_Shared;
using System;
namespace OmniLinkBridge.OmniLink
{
public class AudioZoneStatusEventArgs : EventArgs
{
public ushort ID { get; set; }
public clsAudioZone AudioZone { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using HAI_Shared;
using System;
namespace OmniLinkBridge.OmniLink
{
public class LockStatusEventArgs : EventArgs
{
public ushort ID { get; set; }
public clsAccessControlReader Reader { get; set; }
}
}

View File

@ -1,4 +1,10 @@
namespace OmniLinkBridge.OmniLink
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.OmniLink
{
public enum SystemEventType
{

View File

@ -1,4 +1,5 @@
using System;
using HAI_Shared;
using System;
namespace OmniLinkBridge.OmniLink
{

View File

@ -8,11 +8,6 @@ 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>

View File

@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OmniLinkBridge</RootNamespace>
<AssemblyName>OmniLinkBridge</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
@ -80,39 +80,28 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ControllerEnricher.cs" />
<Compile Include="CoreServer.cs" />
<Compile Include="Modules\TimeSyncModule.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\Alarm.cs" />
<Compile Include="MQTT\AlarmCommands.cs" />
<Compile Include="MQTT\AreaCommands.cs" />
<Compile Include="MQTT\AreaState.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\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\MessageProcessor.cs" />
<Compile Include="MQTT\HomeAssistant\Number.cs" />
<Compile Include="MQTT\Number.cs" />
<Compile Include="MQTT\OverrideZone.cs" />
<Compile Include="MQTT\HomeAssistant\Switch.cs" />
<Compile Include="MQTT\HomeAssistant\Light.cs" />
<Compile Include="MQTT\Switch.cs" />
<Compile Include="MQTT\Light.cs" />
<Compile Include="MQTT\MappingExtensions.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="MQTT\Sensor.cs" />
<Compile Include="MQTT\Topic.cs" />
<Compile Include="MQTT\UnitCommands.cs" />
<Compile Include="MQTT\ZoneCommands.cs" />
<Compile Include="Notifications\EmailNotification.cs" />
<Compile Include="Notifications\INotification.cs" />
<Compile Include="Notifications\Notification.cs" />
@ -120,9 +109,7 @@
<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" />
@ -187,22 +174,22 @@
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="MQTTnet.Extensions.ManagedClient">
<Version>3.1.2</Version>
<Version>3.0.17</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.3</Version>
<Version>13.0.1</Version>
</PackageReference>
<PackageReference Include="Serilog">
<Version>3.1.1</Version>
<Version>2.10.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Formatting.Compact">
<Version>2.0.0</Version>
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Async">
<Version>1.5.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Console">
<Version>5.0.1</Version>
<Version>4.0.0</Version>
</PackageReference>
<PackageReference Include="Serilog.Sinks.File">
<Version>5.0.0</Version>

View File

@ -4,7 +4,4 @@
<StartArguments>
</StartArguments>
</PropertyGroup>
<PropertyGroup>
<ProjectView>ProjectFiles</ProjectView>
</PropertyGroup>
</Project>

View File

@ -22,8 +22,6 @@ 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
@ -51,44 +49,13 @@ mqtt_prefix = omnilink
mqtt_discovery_prefix = homeassistant
# Prefix for Home Assistant entity names
mqtt_discovery_name_prefix =
# Skip publishing Home Assistant discovery topics for zones/units
# Specify a range of numbers 1,2,3,5-10
# Specify a range of numbers like 1,2,3,5-10
mqtt_discovery_ignore_zones =
mqtt_discovery_ignore_units =
# 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
# device_class must be battery, door, garage_door, gas, moisture, motion, problem, 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

View File

@ -4,9 +4,7 @@ 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;
@ -14,11 +12,11 @@ using System.Threading.Tasks;
namespace OmniLinkBridge
{
internal class Program
class Program
{
private static CoreServer server;
static CoreServer server;
private static int Main(string[] args)
static int Main(string[] args)
{
bool interactive = false;
@ -57,10 +55,6 @@ namespace OmniLinkBridge
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];
break;
@ -70,12 +64,6 @@ namespace OmniLinkBridge
}
}
if (string.Compare(Environment.GetEnvironmentVariable("SEND_LOGS"), "1") == 0)
{
Global.DebugSettings = true;
Global.SendLogs = true;
}
config_file = GetFullPath(config_file);
Global.webapi_subscriptions_file = GetFullPath(Global.webapi_subscriptions_file ?? "WebSubscriptions.json");
@ -88,8 +76,8 @@ namespace OmniLinkBridge
var log_config = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("Application", "OmniLinkBridge")
.Enrich.WithProperty("Session", Global.SessionID)
.Enrich.With<ControllerEnricher>()
.Enrich.WithProperty("Session", Guid.NewGuid())
.Enrich.WithProperty("User", (Environment.UserName + Environment.MachineName).GetHashCode())
.Enrich.FromLogContext();
if (log_file != null)
@ -104,10 +92,7 @@ namespace OmniLinkBridge
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())
if (UseTelemetry())
log_config = log_config.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(Matching.WithProperty("Telemetry"))
.WriteTo.Http("https://telemetry.excalibur-partners.com"));
@ -170,7 +155,7 @@ namespace OmniLinkBridge
return 0;
}
private static string GetFullPath(string file)
static string GetFullPath(string file)
{
if (Path.IsPathRooted(file))
return file;
@ -184,38 +169,21 @@ namespace OmniLinkBridge
args.Cancel = true;
}
private static bool IsRunningOnMono()
static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
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()
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()
static void ShowHelp()
{
Console.WriteLine(
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[-lf log_file|disable] [-lj [-ll verbose|debug|information|warning|error] [-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" +
@ -223,14 +191,8 @@ namespace OmniLinkBridge
"\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.");

View File

@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
[assembly: AssemblyProduct("OmniLinkBridge")]
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2021")]
[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.19.0")]
[assembly: AssemblyFileVersion("1.1.19.0")]
[assembly: AssemblyVersion("1.1.11.0")]
[assembly: AssemblyFileVersion("1.1.11.0")]

View File

@ -1,4 +1,3 @@
using OmniLinkBridge.MQTT.HomeAssistant;
using Serilog;
using System;
using System.Collections.Concurrent;
@ -8,7 +7,7 @@ using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Reflection;
using ha = OmniLinkBridge.MQTT.HomeAssistant;
using System.Threading;
namespace OmniLinkBridge
{
@ -31,10 +30,9 @@ namespace OmniLinkBridge
// HAI / Leviton Omni Controller
Global.controller_address = settings.ValidateHasValue("controller_address");
Global.controller_port = settings.ValidatePort("controller_port");
Global.controller_key1 = settings.ValidateEncryptionKey("controller_key1");
Global.controller_key2 = settings.ValidateEncryptionKey("controller_key2");
Global.controller_key1 = settings.ValidateHasValue("controller_key1");
Global.controller_key2 = settings.ValidateHasValue("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");
@ -54,12 +52,10 @@ 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", true);
Global.mysql_connection = settings.CheckEnv("mysql_connection");
// Web Service
Global.webapi_enabled = settings.ValidateBool("webapi_enabled");
@ -77,8 +73,8 @@ namespace OmniLinkBridge
{
Global.mqtt_server = settings.CheckEnv("mqtt_server");
Global.mqtt_port = settings.ValidatePort("mqtt_port");
Global.mqtt_username = settings.CheckEnv("mqtt_username", true);
Global.mqtt_password = settings.CheckEnv("mqtt_password", true);
Global.mqtt_username = settings.CheckEnv("mqtt_username");
Global.mqtt_password = settings.CheckEnv("mqtt_password");
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;
@ -88,12 +84,7 @@ 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
@ -107,113 +98,31 @@ namespace OmniLinkBridge
{
Global.mail_tls = settings.ValidateBool("mail_tls");
Global.mail_port = settings.ValidatePort("mail_port");
Global.mail_username = settings.CheckEnv("mail_username", true);
Global.mail_password = settings.CheckEnv("mail_password", true);
Global.mail_username = settings.CheckEnv("mail_username");
Global.mail_password = settings.CheckEnv("mail_password");
Global.mail_from = settings.ValidateMailFrom("mail_from");
Global.mail_to = settings.ValidateMailTo("mail_to");
}
// Prowl Notifications
Global.prowl_key = settings.ValidateMultipleStrings("prowl_key", true);
Global.prowl_key = settings.ValidateMultipleStrings("prowl_key");
// Pushover Notifications
Global.pushover_token = settings.CheckEnv("pushover_token", true);
Global.pushover_user = settings.ValidateMultipleStrings("pushover_user", true);
Global.pushover_token = settings.CheckEnv("pushover_token");
Global.pushover_user = settings.ValidateMultipleStrings("pushover_user");
}
private static string CheckEnv(this NameValueCollection settings, string name, bool sensitive = false)
private static string CheckEnv(this NameValueCollection settings, string name)
{
string env = Global.UseEnvironment ? Environment.GetEnvironmentVariable(name.ToUpper()) : null;
string value = !string.IsNullOrEmpty(env) ? env : settings[name];
if (Global.DebugSettings)
log.Debug("{ConfigType} {ConfigName}: {ConfigValue}",
(!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(4), name,
sensitive && value != null ? value.Truncate(3) + "***MASKED***" : value);
log.Debug((!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(5) + $"{name}: {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
@ -247,7 +156,7 @@ namespace OmniLinkBridge
}
else if (override_zone is MQTT.OverrideZone mqtt_zone)
{
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out ha.BinarySensor.DeviceClass attrib_device_class))
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out MQTT.BinarySensor.DeviceClass attrib_device_class))
throw new Exception("Missing or invalid device_class attribute");
mqtt_zone.device_class = attrib_device_class;
@ -265,50 +174,6 @@ namespace OmniLinkBridge
}
}
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;
}
}
private static string ValidateHasValue(this NameValueCollection settings, string section)
{
string value = settings.CheckEnv(section);
@ -322,19 +187,6 @@ namespace OmniLinkBridge
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();
}
return value;
}
private static int ValidateInt(this NameValueCollection settings, string section)
{
try
@ -421,14 +273,14 @@ namespace OmniLinkBridge
}
}
private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section, bool sensitive = false)
private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section)
{
try
{
if (settings.CheckEnv(section, true) == null)
if (settings.CheckEnv(section) == null)
return new string[] { };
return settings.CheckEnv(section, sensitive).Split(',');
return settings.CheckEnv(section).Split(',');
}
catch
{
@ -454,21 +306,6 @@ namespace OmniLinkBridge
}
}
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();

View File

@ -1,4 +1,10 @@
namespace OmniLinkBridge.WebAPI
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.WebAPI
{
public enum DeviceType
{

View File

@ -1,4 +1,9 @@
using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.WebAPI
{
@ -8,15 +13,14 @@ namespace OmniLinkBridge.WebAPI
public static AreaContract ToContract(this clsArea area)
{
AreaContract ret = new AreaContract
{
id = (ushort)area.Number,
name = area.Name,
burglary = area.AreaBurglaryAlarmText,
co = area.AreaGasAlarmText,
fire = area.AreaFireAlarmText,
water = area.AreaWaterAlarmText
};
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;
if (area.ExitTimer > 0)
{
@ -33,13 +37,12 @@ namespace OmniLinkBridge.WebAPI
public static ZoneContract ToContract(this clsZone zone)
{
ZoneContract ret = new ZoneContract
{
id = (ushort)zone.Number,
zonetype = zone.ZoneType,
name = zone.Name,
status = zone.StatusText()
};
ZoneContract ret = new ZoneContract();
ret.id = (ushort)zone.Number;
ret.zonetype = zone.ZoneType;
ret.name = zone.Name;
ret.status = zone.StatusText();
if (zone.IsTemperatureZone())
ret.temp = zone.TempText();
@ -51,11 +54,10 @@ namespace OmniLinkBridge.WebAPI
public static UnitContract ToContract(this clsUnit unit)
{
UnitContract ret = new UnitContract
{
id = (ushort)unit.Number,
name = unit.Name
};
UnitContract ret = new UnitContract();
ret.id = (ushort)unit.Number;
ret.name = unit.Name;
if (unit.Status > 100)
ret.level = (ushort)(unit.Status - 100);
@ -69,16 +71,17 @@ namespace OmniLinkBridge.WebAPI
public static ThermostatContract ToContract(this clsThermostat unit)
{
ThermostatContract ret = new ThermostatContract
{
id = (ushort)unit.Number,
name = unit.Name
};
ThermostatContract ret = new ThermostatContract();
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.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);
ret.temp = temp;
ret.humidity = humidity;

View File

@ -1,4 +1,5 @@
using HAI_Shared;
using OmniLinkBridge.WebAPI;
using Serilog;
using System;
using System.Collections.Generic;

View File

@ -1,4 +1,10 @@
namespace OmniLinkBridge.WebAPI
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.WebAPI
{
public class OverrideZone
{

View File

@ -1,7 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.MQTT;
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
namespace OmniLinkBridgeTest
{
@ -40,59 +41,18 @@ namespace OmniLinkBridgeTest
[TestMethod]
public void TestToCommandCode()
{
string payload;
AreaCommandCode parser;
string payload, command;
int code;
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);
(command, code) = payload.ToCommandCode();
Assert.AreEqual(command, "disarm");
Assert.AreEqual(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);
(command, code) = payload.ToCommandCode();
Assert.AreEqual(command, "disarm");
Assert.AreEqual(code, 1);
}
[TestMethod]
@ -105,4 +65,4 @@ namespace OmniLinkBridgeTest
CollectionAssert.AreEqual(new List<int>(new int[] { 1, 2, 3, 5, 6 }), range);
}
}
}
}

View File

@ -1,13 +1,10 @@
using HAI_Shared;
using System;
using System.Text;
using System.Collections.Generic;
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
{
@ -20,26 +17,8 @@ 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, audioSources, 8);
omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag;
messageProcessor = new MessageProcessor(omniLink);
}
[TestMethod]
@ -155,28 +134,6 @@ 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()
{
@ -306,140 +263,6 @@ 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);
}
}
}

View File

@ -1,15 +1,15 @@
using HAI_Shared;
using OmniLinkBridge.OmniLink;
using Serilog;
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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;
@ -25,7 +25,6 @@ namespace OmniLinkBridgeTest.Mock
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;
}

View File

@ -1,5 +1,9 @@
using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridgeTest.Mock
{
@ -9,18 +13,6 @@ 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)
{
if (!(other is SendCommandEventArgs toCompareWith))

View File

@ -1,6 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge.Notifications;
using OmniLinkBridge;
using System.Net.Mail;
namespace OmniLinkBridgeTest

View File

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OmniLinkBridgeTest</RootNamespace>
<AssemblyName>OmniLinkBridgeTest</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
@ -59,10 +59,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter">
<Version>2.2.8</Version>
<Version>2.2.7</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>2.2.8</Version>
<Version>2.2.7</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
[assembly: AssemblyProduct("OmniLinkBridgeTest")]
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@ -1,9 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.MQTT.HomeAssistant;
using System;
using System;
using System.Text;
using System.Collections.Generic;
using ha = OmniLinkBridge.MQTT.HomeAssistant;
using System.Collections.Concurrent;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
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("0000000000000001", Global.controller_key1);
Assert.AreEqual("0000000000000002", Global.controller_key2);
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("MyController", Global.controller_name);
}
@ -79,8 +79,7 @@ namespace OmniLinkBridgeTest
"verbose_thermostat_timer",
"verbose_thermostat",
"verbose_unit",
"verbose_message",
"verbose_lock"
"verbose_message"
})
{
List<string> lines = new List<string>(RequiredSettings())
@ -157,12 +156,8 @@ 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);
@ -173,29 +168,10 @@ 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 = ha.BinarySensor.DeviceClass.garage_door }},
{ 7, new OmniLinkBridge.MQTT.OverrideZone { device_class = ha.BinarySensor.DeviceClass.motion }}
{ 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 }}
};
Assert.AreEqual(override_zone.Count, Global.mqtt_discovery_override_zone.Count);
@ -204,30 +180,6 @@ 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]

View File

@ -1,23 +1,21 @@
# 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 also install it as a [Home Assistant Add-on](https://github.com/excaliburpartners/hassio-addons).
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).
## Requirements
- [Docker](https://www.docker.com/)
- .NET Framework 4.7.2 (or Mono equivalent)
- .NET Framework 4.5.2 (or Mono equivalent)
## Operation
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.
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](https://github.com/excaliburpartners/OmniLinkBridge/blob/master/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 marks the thermostat offline
- A status update containing a temperature of 0 is ignored
- 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
@ -30,7 +28,7 @@ OmniLink Bridge 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 requests every few minutes
- Recommended for application to send subscribe reqeusts every few minutes
- Requests to GET endpoints return status from the OmniLinkII module
- Requests to POST endpoints send commands to the OmniLinkII module
- Logger
@ -148,17 +146,6 @@ 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
@ -179,20 +166,19 @@ string secure, trouble
SUB omnilink/areaX/name
string Area name
SUB omnilink/areaX/state
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, arming, armed_night, armed_home, armed_away, armed_vacation, disarmed
SUB omnilink/areaX/basic_state
string triggered, arming, armed_night, armed_home, armed_away, disarmed
SUB omnilink/areaX/json_state
string json
PUB omnilink/areaX/command
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
@ -234,10 +220,6 @@ 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
@ -248,9 +230,6 @@ string A-L
SUB omnilink/thermostatX/name
string Thermostat name
SUB omnilink/thermostatX/status
string online, offline
SUB omnilink/thermostatX/current_operation
string idle, cooling, heating
@ -314,50 +293,6 @@ 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.
@ -422,7 +357,7 @@ 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 Home Assistant Add-on does not support MySQL logging.
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.
Configure mysql_connection in OmniLinkBridge.ini. For Windows change DRIVER={MySQL} to name of the driver shown in the ODBC Data Source Administrator.
```