Compare commits

...

4 Commits

Author SHA1 Message Date
Ryan Wagoner
efa16fb2c3 1.1.19 - Fix MQTT alarm control panel for Home Assistant 2024.6 2024-06-07 21:39:43 -04:00
Ryan Wagoner
f297c2fcaa 1.1.18 - Increase timeout for loading names and add send support logs 2024-05-08 20:25:31 -04:00
Ryan Wagoner
d7eef51adf 1.1.17 - Add MQTT volume scaling for Home Assistant media player 2024-05-05 15:36:00 -04:00
Ryan Wagoner
73f504ca93 1.1.16 - Add MQTT audio support 2024-05-03 21:35:46 -04:00
41 changed files with 1014 additions and 140 deletions

View File

@ -44,9 +44,11 @@ namespace OmniLinkBridge
startTime = DateTime.Now; startTime = DateTime.Now;
Program.ShowSendLogsWarning();
using (LogContext.PushProperty("Telemetry", "Startup")) using (LogContext.PushProperty("Telemetry", "Startup"))
log.Information("Started version {Version} on {OperatingSystem} with {Modules}", log.Information("Started version {Version} in {Environment} on {OperatingSystem} with {Modules}",
Assembly.GetExecutingAssembly().GetName().Version, Environment.OSVersion, modules); Assembly.GetExecutingAssembly().GetName().Version, Program.GetEnvironment(), Environment.OSVersion, modules);
// Startup modules // Startup modules
foreach (IModule module in modules) foreach (IModule module in modules)

View File

@ -9,6 +9,11 @@ namespace OmniLinkBridge
{ {
public static class Extensions 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) public static double ToCelsius(this double f)
{ {
// Convert to celsius // Convert to celsius

View File

@ -10,6 +10,8 @@ namespace OmniLinkBridge
{ {
public static bool DebugSettings { get; set; } public static bool DebugSettings { get; set; }
public static bool UseEnvironment { 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 // HAI / Leviton Omni Controller
public static string controller_address; public static string controller_address;
@ -34,6 +36,7 @@ namespace OmniLinkBridge
public static bool verbose_unit; public static bool verbose_unit;
public static bool verbose_message; public static bool verbose_message;
public static bool verbose_lock; public static bool verbose_lock;
public static bool verbose_audio;
// mySQL Logging // mySQL Logging
public static bool mysql_logging; public static bool mysql_logging;
@ -56,9 +59,12 @@ namespace OmniLinkBridge
public static string mqtt_discovery_name_prefix; public static string mqtt_discovery_name_prefix;
public static HashSet<int> mqtt_discovery_ignore_zones; public static HashSet<int> mqtt_discovery_ignore_zones;
public static HashSet<int> mqtt_discovery_ignore_units; public static HashSet<int> mqtt_discovery_ignore_units;
public static HashSet<int> mqtt_discovery_area_code_required; 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.OverrideZone> mqtt_discovery_override_zone;
public static ConcurrentDictionary<int, MQTT.OverrideUnit> mqtt_discovery_override_unit; 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 // Notifications
public static bool notify_area; public static bool notify_area;

View File

@ -1,9 +1,4 @@
using HAI_Shared; using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT namespace OmniLinkBridge.MQTT
{ {
@ -28,7 +23,13 @@ namespace OmniLinkBridge.MQTT
} }
else if (supportValidate && payloads.Length == 3) else if (supportValidate && payloads.Length == 3)
{ {
if (string.Compare(payloads[1], "validate", true) == 0) // 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.Validate = true;
ret.Success = int.TryParse(payloads[2], out code); ret.Success = int.TryParse(payloads[2], out code);

View File

@ -1,10 +1,15 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using System.Collections.Generic;
namespace OmniLinkBridge.MQTT.HomeAssistant namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Alarm : Device public class Alarm : Device
{ {
public Alarm(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; } public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@ -12,5 +17,14 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string code { get; set; } 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

@ -5,6 +5,11 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class BinarySensor : Device public class BinarySensor : Device
{ {
public BinarySensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public enum DeviceClass public enum DeviceClass
{ {

View File

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

View File

@ -4,6 +4,11 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Climate : Device public class Climate : Device
{ {
public Climate(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string status { get; set; } public string status { get; set; }
public string action_topic { get; set; } public string action_topic { get; set; }

View File

@ -1,12 +1,16 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using OmniLinkBridge.Modules;
using System.Collections.Generic; using System.Collections.Generic;
namespace OmniLinkBridge.MQTT.HomeAssistant namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Device public class Device
{ {
public Device(DeviceRegistry deviceRegistry)
{
device = deviceRegistry;
}
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public enum AvailabilityMode public enum AvailabilityMode
{ {
@ -19,6 +23,9 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
public string name { get; set; } public string name { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string state_topic { get; set; } public string state_topic { get; set; }
@ -32,6 +39,6 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
public AvailabilityMode? availability_mode { get; set; } public AvailabilityMode? availability_mode { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public DeviceRegistry device { get; set; } = MQTTModule.MqttDeviceRegistry; public DeviceRegistry device { get; set; }
} }
} }

View File

@ -2,6 +2,11 @@
{ {
public class Light : Device public class Light : Device
{ {
public Light(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; } public string command_topic { get; set; }
public string brightness_state_topic { get; set; } public string brightness_state_topic { get; set; }

View File

@ -4,6 +4,11 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Lock : Device public class Lock : Device
{ {
public Lock(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; } public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]

View File

@ -4,15 +4,20 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Number : Device public class Number : Device
{ {
public Number(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; } public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; } public int? min { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? min { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? max { get; set; } public int? max { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public double? step { get; set; }
} }
} }

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Select : Device
{
public Select(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; }
public List<string> options { get; set; } = null;
}
}

View File

@ -5,6 +5,11 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Sensor : Device public class Sensor : Device
{ {
public Sensor(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public enum DeviceClass public enum DeviceClass
{ {
@ -15,9 +20,6 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public DeviceClass? device_class { get; set; } public DeviceClass? device_class { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string unit_of_measurement { get; set; } public string unit_of_measurement { get; set; }

View File

@ -4,6 +4,11 @@ namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
public class Switch : Device public class Switch : Device
{ {
public Switch(DeviceRegistry deviceRegistry) : base(deviceRegistry)
{
}
public string command_topic { get; set; } public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]

View File

@ -3,6 +3,7 @@ using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using OmniLinkBridge.MQTT.HomeAssistant; using OmniLinkBridge.MQTT.HomeAssistant;
using OmniLinkBridge.MQTT.Parser; using OmniLinkBridge.MQTT.Parser;
using OmniLinkBridge.Modules;
namespace OmniLinkBridge.MQTT namespace OmniLinkBridge.MQTT
{ {
@ -15,7 +16,7 @@ namespace OmniLinkBridge.MQTT
public static Alarm ToConfig(this clsArea area) public static Alarm ToConfig(this clsArea area)
{ {
Alarm ret = new Alarm Alarm ret = new Alarm(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}", unique_id = $"{Global.mqtt_prefix}area{area.Number}",
name = Global.mqtt_discovery_name_prefix + area.Name, name = Global.mqtt_discovery_name_prefix + area.Name,
@ -24,10 +25,27 @@ namespace OmniLinkBridge.MQTT
}; };
if(Global.mqtt_discovery_area_code_required.Contains(area.Number)) Global.mqtt_discovery_override_area.TryGetValue(area.Number, out OverrideArea override_area);
if (override_area != null)
{ {
ret.command_template = "{{ action }},validate,{{ code }}"; if(override_area.code_arm || override_area.code_disarm)
ret.code = "REMOTE_CODE"; {
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; return ret;
@ -83,7 +101,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigBurglary(this clsArea area) public static BinarySensor ToConfigBurglary(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}burglary", unique_id = $"{Global.mqtt_prefix}area{area.Number}burglary",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Burglary", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Burglary",
@ -96,7 +114,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigFire(this clsArea area) public static BinarySensor ToConfigFire(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}fire", unique_id = $"{Global.mqtt_prefix}area{area.Number}fire",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Fire", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Fire",
@ -109,7 +127,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigGas(this clsArea area) public static BinarySensor ToConfigGas(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}gas", unique_id = $"{Global.mqtt_prefix}area{area.Number}gas",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Gas", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Gas",
@ -122,7 +140,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigAux(this clsArea area) public static BinarySensor ToConfigAux(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}auxiliary", unique_id = $"{Global.mqtt_prefix}area{area.Number}auxiliary",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Auxiliary", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Auxiliary",
@ -135,7 +153,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigFreeze(this clsArea area) public static BinarySensor ToConfigFreeze(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}freeze", unique_id = $"{Global.mqtt_prefix}area{area.Number}freeze",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Freeze", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Freeze",
@ -148,7 +166,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigWater(this clsArea area) public static BinarySensor ToConfigWater(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}water", unique_id = $"{Global.mqtt_prefix}area{area.Number}water",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Water", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Water",
@ -161,7 +179,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigDuress(this clsArea area) public static BinarySensor ToConfigDuress(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}duress", unique_id = $"{Global.mqtt_prefix}area{area.Number}duress",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Duress", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Duress",
@ -174,7 +192,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfigTemp(this clsArea area) public static BinarySensor ToConfigTemp(this clsArea area)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}area{area.Number}temp", unique_id = $"{Global.mqtt_prefix}area{area.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Temp", name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Temp",
@ -219,7 +237,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigTemp(this clsZone zone, enuTempFormat format) public static Sensor ToConfigTemp(this clsZone zone, enuTempFormat format)
{ {
Sensor ret = new Sensor Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}temp", unique_id = $"{Global.mqtt_prefix}zone{zone.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Temp", name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Temp",
@ -232,7 +250,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigHumidity(this clsZone zone) public static Sensor ToConfigHumidity(this clsZone zone)
{ {
Sensor ret = new Sensor Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}humidity", unique_id = $"{Global.mqtt_prefix}zone{zone.Number}humidity",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Humidity", name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Humidity",
@ -245,7 +263,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigSensor(this clsZone zone) public static Sensor ToConfigSensor(this clsZone zone)
{ {
Sensor ret = new Sensor Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}", unique_id = $"{Global.mqtt_prefix}zone{zone.Number}",
name = Global.mqtt_discovery_name_prefix + zone.Name name = Global.mqtt_discovery_name_prefix + zone.Name
@ -287,7 +305,7 @@ namespace OmniLinkBridge.MQTT
public static Switch ToConfigSwitch(this clsZone zone) public static Switch ToConfigSwitch(this clsZone zone)
{ {
Switch ret = new Switch Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}switch", unique_id = $"{Global.mqtt_prefix}zone{zone.Number}switch",
name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Bypass", name = $"{Global.mqtt_discovery_name_prefix}{zone.Name} Bypass",
@ -302,7 +320,7 @@ namespace OmniLinkBridge.MQTT
public static BinarySensor ToConfig(this clsZone zone) public static BinarySensor ToConfig(this clsZone zone)
{ {
BinarySensor ret = new BinarySensor BinarySensor ret = new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}zone{zone.Number}binary", unique_id = $"{Global.mqtt_prefix}zone{zone.Number}binary",
name = Global.mqtt_discovery_name_prefix + zone.Name name = Global.mqtt_discovery_name_prefix + zone.Name
@ -377,7 +395,7 @@ namespace OmniLinkBridge.MQTT
public static Light ToConfig(this clsUnit unit) public static Light ToConfig(this clsUnit unit)
{ {
Light ret = new Light Light ret = new Light(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}light", unique_id = $"{Global.mqtt_prefix}unit{unit.Number}light",
name = Global.mqtt_discovery_name_prefix + unit.Name, name = Global.mqtt_discovery_name_prefix + unit.Name,
@ -391,7 +409,7 @@ namespace OmniLinkBridge.MQTT
public static Switch ToConfigSwitch(this clsUnit unit) public static Switch ToConfigSwitch(this clsUnit unit)
{ {
Switch ret = new Switch Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}switch", unique_id = $"{Global.mqtt_prefix}unit{unit.Number}switch",
name = Global.mqtt_discovery_name_prefix + unit.Name, name = Global.mqtt_discovery_name_prefix + unit.Name,
@ -403,7 +421,7 @@ namespace OmniLinkBridge.MQTT
public static Number ToConfigNumber(this clsUnit unit) public static Number ToConfigNumber(this clsUnit unit)
{ {
Number ret = new Number Number ret = new Number(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}number", unique_id = $"{Global.mqtt_prefix}unit{unit.Number}number",
name = Global.mqtt_discovery_name_prefix + unit.Name, name = Global.mqtt_discovery_name_prefix + unit.Name,
@ -446,7 +464,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigTemp(this clsThermostat thermostat, enuTempFormat format) public static Sensor ToConfigTemp(this clsThermostat thermostat, enuTempFormat format)
{ {
Sensor ret = new Sensor Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}temp", unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}temp",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Temp", name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Temp",
@ -459,7 +477,7 @@ namespace OmniLinkBridge.MQTT
public static Number ToConfigHumidify(this clsThermostat thermostat) public static Number ToConfigHumidify(this clsThermostat thermostat)
{ {
Number ret = new Number Number ret = new Number(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidify", unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidify",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidify", name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidify",
@ -472,7 +490,7 @@ namespace OmniLinkBridge.MQTT
public static Number ToConfigDehumidify(this clsThermostat thermostat) public static Number ToConfigDehumidify(this clsThermostat thermostat)
{ {
Number ret = new Number Number ret = new Number(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}dehumidify", unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}dehumidify",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Dehumidify", name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Dehumidify",
@ -485,7 +503,7 @@ namespace OmniLinkBridge.MQTT
public static Sensor ToConfigHumidity(this clsThermostat thermostat) public static Sensor ToConfigHumidity(this clsThermostat thermostat)
{ {
Sensor ret = new Sensor Sensor ret = new Sensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidity", unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}humidity",
name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidity", name = $"{Global.mqtt_discovery_name_prefix}{thermostat.Name} Humidity",
@ -498,7 +516,7 @@ namespace OmniLinkBridge.MQTT
public static Climate ToConfig(this clsThermostat thermostat, enuTempFormat format) public static Climate ToConfig(this clsThermostat thermostat, enuTempFormat format)
{ {
Climate ret = new Climate Climate ret = new Climate(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}", unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}",
name = Global.mqtt_discovery_name_prefix + thermostat.Name, name = Global.mqtt_discovery_name_prefix + thermostat.Name,
@ -579,9 +597,9 @@ namespace OmniLinkBridge.MQTT
return $"{Global.mqtt_prefix}/button{button.Number}/{topic}"; return $"{Global.mqtt_prefix}/button{button.Number}/{topic}";
} }
public static Switch ToConfig(this clsButton button) public static Switch ToConfigSwitch(this clsButton button)
{ {
Switch ret = new Switch Switch ret = new Switch(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}button{button.Number}", unique_id = $"{Global.mqtt_prefix}button{button.Number}",
name = Global.mqtt_discovery_name_prefix + button.Name, name = Global.mqtt_discovery_name_prefix + button.Name,
@ -591,6 +609,18 @@ namespace OmniLinkBridge.MQTT
return ret; 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) public static string ToTopic(this clsMessage message, Topic topic)
{ {
return $"{Global.mqtt_prefix}/message{message.Number}/{topic}"; return $"{Global.mqtt_prefix}/message{message.Number}/{topic}";
@ -613,7 +643,7 @@ namespace OmniLinkBridge.MQTT
public static Lock ToConfig(this clsAccessControlReader reader) public static Lock ToConfig(this clsAccessControlReader reader)
{ {
Lock ret = new Lock Lock ret = new Lock(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}lock{reader.Number}", unique_id = $"{Global.mqtt_prefix}lock{reader.Number}",
name = Global.mqtt_discovery_name_prefix + reader.Name, name = Global.mqtt_discovery_name_prefix + reader.Name,
@ -635,5 +665,105 @@ namespace OmniLinkBridge.MQTT
else else
return "unlocked"; 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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
namespace OmniLinkBridge.MQTT namespace OmniLinkBridge.MQTT
{ {
@ -15,11 +16,18 @@ namespace OmniLinkBridge.MQTT
private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled); private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
private IOmniLinkII OmniLink { get; set; } private readonly int[] audioMuteVolumes;
private const int VOLUME_DEFAULT = 10;
public MessageProcessor(IOmniLinkII omni) private IOmniLinkII OmniLink { get; }
private Dictionary<string, int> AudioSources { get; }
public MessageProcessor(IOmniLinkII omni, Dictionary<string, int> audioSources, int numAudioZones)
{ {
OmniLink = omni; OmniLink = omni;
AudioSources = audioSources;
audioMuteVolumes = new int[numAudioZones];
} }
public void Process(string messageTopic, string payload) public void Process(string messageTopic, string payload)
@ -29,8 +37,8 @@ namespace OmniLinkBridge.MQTT
if (!match.Success) if (!match.Success)
return; return;
if (!Enum.TryParse(match.Groups[1].Value, true, out CommandTypes type) if (!Enum.TryParse(match.Groups[1].Value, true, out CommandTypes type)
|| !Enum.TryParse(match.Groups[3].Value, true, out Topic topic) || !Enum.TryParse(match.Groups[3].Value, true, out Topic topic)
|| !ushort.TryParse(match.Groups[2].Value, out ushort id)) || !ushort.TryParse(match.Groups[2].Value, out ushort id))
return; return;
@ -51,6 +59,8 @@ namespace OmniLinkBridge.MQTT
ProcessMessageReceived(OmniLink.Controller.Messages[id], topic, payload); ProcessMessageReceived(OmniLink.Controller.Messages[id], topic, payload);
else if (type == CommandTypes.@lock && id <= OmniLink.Controller.AccessControlReaders.Count) else if (type == CommandTypes.@lock && id <= OmniLink.Controller.AccessControlReaders.Count)
ProcessLockReceived(OmniLink.Controller.AccessControlReaders[id], topic, payload); 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> private static readonly IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
@ -78,7 +88,7 @@ namespace OmniLinkBridge.MQTT
{ {
string sCode = parser.Code.ToString(); string sCode = parser.Code.ToString();
if(sCode.Length != 4) if (sCode.Length != 4)
{ {
log.Warning("SetArea: {id}, Invalid security code: must be 4 digits", area.Number); log.Warning("SetArea: {id}, Invalid security code: must be 4 digits", area.Number);
return; return;
@ -98,7 +108,7 @@ namespace OmniLinkBridge.MQTT
var validateCode = new clsOL2MsgValidateCode(OmniLink.Controller.Connection, B); var validateCode = new clsOL2MsgValidateCode(OmniLink.Controller.Connection, B);
if(validateCode.AuthorityLevel == 0) if (validateCode.AuthorityLevel == 0)
{ {
log.Warning("SetArea: {id}, Invalid security code: validation failed", area.Number); log.Warning("SetArea: {id}, Invalid security code: validation failed", area.Number);
return; return;
@ -143,7 +153,7 @@ namespace OmniLinkBridge.MQTT
{ {
AreaCommandCode parser = payload.ToCommandCode(); AreaCommandCode parser = payload.ToCommandCode();
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out ZoneCommands cmd) && if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out ZoneCommands cmd) &&
!(zone.Number == 0 && cmd == ZoneCommands.bypass)) !(zone.Number == 0 && cmd == ZoneCommands.bypass))
{ {
if (zone.Number == 0) if (zone.Number == 0)
@ -176,7 +186,7 @@ namespace OmniLinkBridge.MQTT
log.Debug("SetUnit: {id} to {value}", unit.Number, payload); log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
OmniLink.SendCommand(enuUnitCommand.Set, BitConverter.GetBytes(flagValue)[0], (ushort)unit.Number); OmniLink.SendCommand(enuUnitCommand.Set, BitConverter.GetBytes(flagValue)[0], (ushort)unit.Number);
} }
else if (unit.Type != enuOL2UnitType.Output && else if (unit.Type != enuOL2UnitType.Output &&
command == Topic.brightness_command && int.TryParse(payload, out int unitValue)) command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
{ {
log.Debug("SetUnit: {id} to {value}%", unit.Number, payload); log.Debug("SetUnit: {id} to {value}%", unit.Number, payload);
@ -187,7 +197,7 @@ namespace OmniLinkBridge.MQTT
// which will cause light to go to 100% brightness // which will cause light to go to 100% brightness
unit.Status = (byte)(100 + unitValue); unit.Status = (byte)(100 + unitValue);
} }
else if (unit.Type != enuOL2UnitType.Output && else if (unit.Type != enuOL2UnitType.Output &&
command == Topic.scene_command && char.TryParse(payload, out char scene)) command == Topic.scene_command && char.TryParse(payload, out char scene))
{ {
log.Debug("SetUnit: {id} to {value}", unit.Number, payload); log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
@ -315,5 +325,92 @@ namespace OmniLinkBridge.MQTT
OmniLink.SendCommand(LockMapping[cmd], 0, (ushort)reader.Number); 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,19 @@
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT
{
public class OverrideArea
{
public bool code_arm { get; set; }
public bool code_disarm { get; set; }
public bool arm_home { get; set; } = true;
public bool arm_away { get; set; } = true;
public bool arm_night { get; set; } = true;
public bool arm_vacation { get; set; } = true;
}
}

View File

@ -8,6 +8,7 @@
thermostat, thermostat,
button, button,
message, message,
@lock @lock,
audio
} }
} }

View File

@ -33,5 +33,11 @@
fan_mode_command, fan_mode_command,
hold_state, hold_state,
hold_command, hold_command,
mute_state,
mute_command,
source_state,
source_command,
volume_state,
volume_command,
} }
} }

View File

@ -40,6 +40,7 @@ namespace OmniLinkBridge.Modules
omnilink.OnUnitStatus += Omnilink_OnUnitStatus; omnilink.OnUnitStatus += Omnilink_OnUnitStatus;
omnilink.OnMessageStatus += Omnilink_OnMessageStatus; omnilink.OnMessageStatus += Omnilink_OnMessageStatus;
omnilink.OnLockStatus += Omnilink_OnLockStatus; omnilink.OnLockStatus += Omnilink_OnLockStatus;
omnilink.OnAudioZoneStatus += Omnilink_OnAudioZoneStatus;
omnilink.OnSystemStatus += Omnilink_OnSystemStatus; omnilink.OnSystemStatus += Omnilink_OnSystemStatus;
} }
@ -224,6 +225,9 @@ namespace OmniLinkBridge.Modules
continue; continue;
audioSourceUsage++; audioSourceUsage++;
if (Global.verbose_audio)
log.Verbose("Initial AudioSource {id} {name}", i, audioSource.rawName);
} }
ushort audioZoneUsage = 0; ushort audioZoneUsage = 0;
@ -235,6 +239,10 @@ namespace OmniLinkBridge.Modules
continue; continue;
audioZoneUsage++; 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")) using (LogContext.PushProperty("Telemetry", "ControllerUsage"))
@ -270,7 +278,7 @@ namespace OmniLinkBridge.Modules
e.Area.AreaDuressAlarmText + "','" + status + "')"); e.Area.AreaDuressAlarmText + "','" + status + "')");
if (Global.verbose_area) if (Global.verbose_area)
log.Verbose("AreaStatus {id} {name}, Status: {status}, Alarams: {alarms}", e.ID, e.Area.Name, status, e.Area.AreaAlarms); log.Verbose("AreaStatus {id} {name}, Status: {status}, Alarms: {alarms}", e.ID, e.Area.Name, status, e.Area.AreaAlarms);
if (Global.notify_area && e.Area.LastMode != e.Area.AreaMode) if (Global.notify_area && e.Area.LastMode != e.Area.AreaMode)
Notification.Notify("Security", e.Area.Name + " " + e.Area.ModeText()); Notification.Notify("Security", e.Area.Name + " " + e.Area.ModeText());
@ -383,6 +391,13 @@ namespace OmniLinkBridge.Modules
log.Verbose("LockStatus {id} {name}, Status: {status}", e.ID, e.Reader.Name, e.Reader.LockStatusText()); 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) private void Omnilink_OnSystemStatus(object sender, SystemStatusEventArgs e)
{ {
DBQueue(@" DBQueue(@"

View File

@ -34,11 +34,16 @@ namespace OmniLinkBridge.Modules
private bool ControllerConnected { get; set; } private bool ControllerConnected { get; set; }
private MessageProcessor MessageProcessor { 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 readonly AutoResetEvent trigger = new AutoResetEvent(false);
private const string ONLINE = "online"; private const string ONLINE = "online";
private const string OFFLINE = "offline"; private const string OFFLINE = "offline";
private const string SECURE = "secure";
private const string TROUBLE = "trouble";
public MQTTModule(OmniLinkII omni) public MQTTModule(OmniLinkII omni)
{ {
OmniLink = omni; OmniLink = omni;
@ -51,9 +56,10 @@ namespace OmniLinkBridge.Modules
OmniLink.OnButtonStatus += OmniLink_OnButtonStatus; OmniLink.OnButtonStatus += OmniLink_OnButtonStatus;
OmniLink.OnMessageStatus += OmniLink_OnMessageStatus; OmniLink.OnMessageStatus += OmniLink_OnMessageStatus;
OmniLink.OnLockStatus += OmniLink_OnLockStatus; OmniLink.OnLockStatus += OmniLink_OnLockStatus;
OmniLink.OnAudioZoneStatus += OmniLink_OnAudioZoneStatus;
OmniLink.OnSystemStatus += OmniLink_OnSystemStatus; OmniLink.OnSystemStatus += OmniLink_OnSystemStatus;
MessageProcessor = new MessageProcessor(omni); MessageProcessor = new MessageProcessor(omni, AudioSources, omni.Controller.CAP.numAudioZones);
} }
public void Startup() public void Startup()
@ -120,7 +126,10 @@ namespace OmniLinkBridge.Modules
Topic.dehumidify_command, Topic.dehumidify_command,
Topic.mode_command, Topic.mode_command,
Topic.fan_mode_command, Topic.fan_mode_command,
Topic.hold_command Topic.hold_command,
Topic.mute_command,
Topic.source_command,
Topic.volume_command
}; };
toSubscribe.ForEach((command) => MqttClient.SubscribeAsync( toSubscribe.ForEach((command) => MqttClient.SubscribeAsync(
@ -171,6 +180,8 @@ namespace OmniLinkBridge.Modules
PublishButtons(); PublishButtons();
PublishMessages(); PublishMessages();
PublishLocks(); PublishLocks();
PublishAudioSources();
PublishAudioZones();
PublishControllerStatus(ONLINE); PublishControllerStatus(ONLINE);
PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText()); PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText());
@ -190,10 +201,10 @@ namespace OmniLinkBridge.Modules
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_dcm/config", PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/system_dcm/config",
JsonConvert.SerializeObject(SystemTroubleConfig("dcm", "DCM"))); JsonConvert.SerializeObject(SystemTroubleConfig("dcm", "DCM")));
PublishAsync(SystemTroubleTopic("phone"), OmniLink.TroublePhone ? "trouble" : "secure"); PublishAsync(SystemTroubleTopic("phone"), OmniLink.TroublePhone ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("ac"), OmniLink.TroubleAC ? "trouble" : "secure"); PublishAsync(SystemTroubleTopic("ac"), OmniLink.TroubleAC ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("battery"), OmniLink.TroubleBattery ? "trouble" : "secure"); PublishAsync(SystemTroubleTopic("battery"), OmniLink.TroubleBattery ? TROUBLE : SECURE);
PublishAsync(SystemTroubleTopic("dcm"), OmniLink.TroubleDCM ? "trouble" : "secure"); PublishAsync(SystemTroubleTopic("dcm"), OmniLink.TroubleDCM ? TROUBLE : SECURE);
} }
public string SystemTroubleTopic(string type) public string SystemTroubleTopic(string type)
@ -203,14 +214,14 @@ namespace OmniLinkBridge.Modules
public BinarySensor SystemTroubleConfig(string type, string name) public BinarySensor SystemTroubleConfig(string type, string name)
{ {
return new BinarySensor return new BinarySensor(MQTTModule.MqttDeviceRegistry)
{ {
unique_id = $"{Global.mqtt_prefix}system{type}", unique_id = $"{Global.mqtt_prefix}system{type}",
name = $"{Global.mqtt_discovery_name_prefix}System {name}", name = $"{Global.mqtt_discovery_name_prefix}System {name}",
state_topic = SystemTroubleTopic(type), state_topic = SystemTroubleTopic(type),
device_class = BinarySensor.DeviceClass.problem, device_class = BinarySensor.DeviceClass.problem,
payload_off = "secure", payload_off = SECURE,
payload_on = "trouble" payload_on = TROUBLE
}; };
} }
@ -391,6 +402,9 @@ namespace OmniLinkBridge.Modules
{ {
log.Debug("Publishing {type}", "buttons"); log.Debug("Publishing {type}", "buttons");
if (Global.mqtt_discovery_button_type == typeof(Switch))
log.Information("See {setting} for new option when publishing {type}", "mqtt_discovery_button_type", "buttons");
for (ushort i = 1; i <= OmniLink.Controller.Buttons.Count; i++) for (ushort i = 1; i <= OmniLink.Controller.Buttons.Count; i++)
{ {
clsButton button = OmniLink.Controller.Buttons[i]; clsButton button = OmniLink.Controller.Buttons[i];
@ -399,6 +413,7 @@ namespace OmniLinkBridge.Modules
{ {
PublishAsync(button.ToTopic(Topic.name), null); PublishAsync(button.ToTopic(Topic.name), null);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config", null); PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config", null);
continue; continue;
} }
@ -406,8 +421,19 @@ namespace OmniLinkBridge.Modules
PublishAsync(button.ToTopic(Topic.state), "OFF"); PublishAsync(button.ToTopic(Topic.state), "OFF");
PublishAsync(button.ToTopic(Topic.name), button.Name); PublishAsync(button.ToTopic(Topic.name), button.Name);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config",
JsonConvert.SerializeObject(button.ToConfig())); if (Global.mqtt_discovery_button_type == typeof(Switch))
{
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config",
JsonConvert.SerializeObject(button.ToConfigSwitch()));
}
else if (Global.mqtt_discovery_button_type == typeof(Button))
{
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/button/{Global.mqtt_prefix}/button{i}/config",
JsonConvert.SerializeObject(button.ToConfigButton()));
}
} }
} }
@ -454,6 +480,74 @@ namespace OmniLinkBridge.Modules
} }
} }
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) private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
{ {
if (!MqttClient.IsConnected) if (!MqttClient.IsConnected)
@ -547,19 +641,27 @@ namespace OmniLinkBridge.Modules
PublishLockStateAsync(e.Reader); PublishLockStateAsync(e.Reader);
} }
private void OmniLink_OnAudioZoneStatus(object sender, AudioZoneStatusEventArgs e)
{
if (!MqttClient.IsConnected)
return;
PublishAudioZoneStateAsync(e.AudioZone);
}
private void OmniLink_OnSystemStatus(object sender, SystemStatusEventArgs e) private void OmniLink_OnSystemStatus(object sender, SystemStatusEventArgs e)
{ {
if (!MqttClient.IsConnected) if (!MqttClient.IsConnected)
return; return;
if(e.Type == SystemEventType.Phone) 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) 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) 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) 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) private void PublishAreaState(clsArea area)
@ -628,6 +730,15 @@ namespace OmniLinkBridge.Modules
return PublishAsync(reader.ToTopic(Topic.state), reader.ToState()); return PublishAsync(reader.ToTopic(Topic.state), reader.ToState());
} }
private void PublishAudioZoneStateAsync(clsAudioZone audioZone)
{
PublishAsync(audioZone.ToTopic(Topic.state), audioZone.ToState());
PublishAsync(audioZone.ToTopic(Topic.mute_state), audioZone.ToMuteState());
PublishAsync(audioZone.ToTopic(Topic.source_state),
OmniLink.Controller.AudioSources[audioZone.ToSourceState()].rawName);
PublishAsync(audioZone.ToTopic(Topic.volume_state), audioZone.ToVolumeState().ToString());
}
private Task PublishAsync(string topic, string payload) private Task PublishAsync(string topic, string payload)
{ {
return MqttClient.PublishAsync(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true); return MqttClient.PublishAsync(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true);

View File

@ -41,6 +41,7 @@ namespace OmniLinkBridge.Modules
public event EventHandler<ButtonStatusEventArgs> OnButtonStatus; public event EventHandler<ButtonStatusEventArgs> OnButtonStatus;
public event EventHandler<MessageStatusEventArgs> OnMessageStatus; public event EventHandler<MessageStatusEventArgs> OnMessageStatus;
public event EventHandler<LockStatusEventArgs> OnLockStatus; public event EventHandler<LockStatusEventArgs> OnLockStatus;
public event EventHandler<AudioZoneStatusEventArgs> OnAudioZoneStatus;
public event EventHandler<SystemStatusEventArgs> OnSystemStatus; public event EventHandler<SystemStatusEventArgs> OnSystemStatus;
private readonly AutoResetEvent trigger = new AutoResetEvent(false); private readonly AutoResetEvent trigger = new AutoResetEvent(false);
@ -86,6 +87,7 @@ namespace OmniLinkBridge.Modules
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2) 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); return Controller.SendCommand(Cmd, Par, Pr2);
} }
@ -199,6 +201,8 @@ namespace OmniLinkBridge.Modules
tstat_timer.Start(); tstat_timer.Start();
OnConnect?.Invoke(this, new EventArgs()); OnConnect?.Invoke(this, new EventArgs());
Program.ShowSendLogsWarning();
} }
#endregion #endregion
@ -216,6 +220,8 @@ namespace OmniLinkBridge.Modules
await GetNamed(enuObjectType.Message); await GetNamed(enuObjectType.Message);
await GetNamed(enuObjectType.Button); await GetNamed(enuObjectType.Button);
await GetNamed(enuObjectType.AccessControlReader); await GetNamed(enuObjectType.AccessControlReader);
await GetNamed(enuObjectType.AudioSource);
await GetNamed(enuObjectType.AudioZone);
} }
private async Task GetSystemFormats() private async Task GetSystemFormats()
@ -227,7 +233,8 @@ namespace OmniLinkBridge.Modules
await Task.Run(() => await Task.Run(() =>
{ {
nameWait.WaitOne(new TimeSpan(0, 0, 10)); if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
log.Error("Timeout occurred waiting system formats");
}); });
} }
@ -240,7 +247,8 @@ namespace OmniLinkBridge.Modules
await Task.Run(() => await Task.Run(() =>
{ {
nameWait.WaitOne(new TimeSpan(0, 0, 10)); if(!nameWait.WaitOne(new TimeSpan(0, 0, 10)))
log.Error("Timeout occurred waiting for system troubles");
}); });
} }
@ -252,7 +260,8 @@ namespace OmniLinkBridge.Modules
await Task.Run(() => await Task.Run(() =>
{ {
nameWait.WaitOne(new TimeSpan(0, 0, 10)); if (!nameWait.WaitOne(new TimeSpan(0, 0, 30)))
log.Error("Timeout occurred waiting for named units {unitType}", type.ToString());
}); });
} }
@ -347,6 +356,14 @@ namespace OmniLinkBridge.Modules
case enuObjectType.AccessControlReader: case enuObjectType.AccessControlReader:
Controller.AccessControlReaders.CopyProperties(MSG); Controller.AccessControlReaders.CopyProperties(MSG);
break; 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: default:
break; break;
} }
@ -427,6 +444,8 @@ namespace OmniLinkBridge.Modules
case enuOmniLink2MessageType.CmdExtSecurity: case enuOmniLink2MessageType.CmdExtSecurity:
break; break;
case enuOmniLink2MessageType.AudioSourceStatus: case enuOmniLink2MessageType.AudioSourceStatus:
// Ignore audio source metadata status updates
handled = true;
break; break;
case enuOmniLink2MessageType.SystemEvents: case enuOmniLink2MessageType.SystemEvents:
HandleUnsolicitedSystemEvent(B); HandleUnsolicitedSystemEvent(B);
@ -695,6 +714,17 @@ namespace OmniLinkBridge.Modules
}); });
} }
break; 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: default:
if (Global.verbose_unhandled) if (Global.verbose_unhandled)
{ {

View File

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

View File

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

View File

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

View File

@ -83,8 +83,11 @@
<Compile Include="ControllerEnricher.cs" /> <Compile Include="ControllerEnricher.cs" />
<Compile Include="CoreServer.cs" /> <Compile Include="CoreServer.cs" />
<Compile Include="Modules\TimeSyncModule.cs" /> <Compile Include="Modules\TimeSyncModule.cs" />
<Compile Include="MQTT\HomeAssistant\Button.cs" />
<Compile Include="MQTT\HomeAssistant\Alarm.cs" /> <Compile Include="MQTT\HomeAssistant\Alarm.cs" />
<Compile Include="MQTT\HomeAssistant\Lock.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\OverrideUnit.cs" />
<Compile Include="MQTT\Parser\AlarmCommands.cs" /> <Compile Include="MQTT\Parser\AlarmCommands.cs" />
<Compile Include="MQTT\AreaCommandCode.cs" /> <Compile Include="MQTT\AreaCommandCode.cs" />
@ -117,6 +120,7 @@
<Compile Include="Notifications\PushoverNotification.cs" /> <Compile Include="Notifications\PushoverNotification.cs" />
<Compile Include="OmniLink\ButtonStatusEventArgs.cs" /> <Compile Include="OmniLink\ButtonStatusEventArgs.cs" />
<Compile Include="OmniLink\IOmniLinkII.cs" /> <Compile Include="OmniLink\IOmniLinkII.cs" />
<Compile Include="OmniLink\AudioZoneStatusEventArgs.cs" />
<Compile Include="OmniLink\SystemEventType.cs" /> <Compile Include="OmniLink\SystemEventType.cs" />
<Compile Include="OmniLink\LockStatusEventArgs.cs" /> <Compile Include="OmniLink\LockStatusEventArgs.cs" />
<Compile Include="OmniLink\UnitStatusEventArgs.cs" /> <Compile Include="OmniLink\UnitStatusEventArgs.cs" />

View File

@ -23,6 +23,7 @@ verbose_thermostat = yes
verbose_unit = yes verbose_unit = yes
verbose_message = yes verbose_message = yes
verbose_lock = yes verbose_lock = yes
verbose_audio = yes
# mySQL Logging (yes/no) # mySQL Logging (yes/no)
mysql_logging = no mysql_logging = no
@ -54,14 +55,24 @@ mqtt_discovery_name_prefix =
# Specify a range of numbers 1,2,3,5-10 # Specify a range of numbers 1,2,3,5-10
mqtt_discovery_ignore_zones = mqtt_discovery_ignore_zones =
mqtt_discovery_ignore_units = mqtt_discovery_ignore_units =
# Require Home Assistant to prompt for user code when arming/disarming area
# Specify a range of numbers 1,2,3,5-10 # Override the area Home Assistant alarm control panel
mqtt_discovery_area_code_required = # 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 # Override the zone Home Assistant binary sensor device_class
# device_class: must be battery, cold, door, garage_door, gas, # device_class: must be battery, cold, door, garage_door, gas,
# heat, moisture, motion, problem, safety, smoke, or window # heat, moisture, motion, problem, safety, smoke, or window
#mqtt_discovery_override_zone = id=5;device_class=garage_door #mqtt_discovery_override_zone = id=5;device_class=garage_door
#mqtt_discovery_override_zone = id=6;device_class=garage_door #mqtt_discovery_override_zone = id=6;device_class=garage_door
# Override the unit Home Assistant device type # Override the unit Home Assistant device type
# type: # type:
# Units (LTe 1-32, IIe 1-64, Pro 1-256) light or switch, defaults to light # Units (LTe 1-32, IIe 1-64, Pro 1-256) light or switch, defaults to light
@ -69,6 +80,15 @@ mqtt_discovery_area_code_required =
#mqtt_discovery_override_unit = id=1;type=switch #mqtt_discovery_override_unit = id=1;type=switch
#mqtt_discovery_override_unit = id=395;type=number #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) # Notifications (yes/no)
# Always sent for area alarms and critical system events # Always sent for area alarms and critical system events
# Optionally enable for area status changes and console messages # Optionally enable for area status changes and console messages

View File

@ -4,7 +4,9 @@ using Serilog.Events;
using Serilog.Filters; using Serilog.Filters;
using Serilog.Formatting.Compact; using Serilog.Formatting.Compact;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.ServiceProcess; using System.ServiceProcess;
@ -12,11 +14,11 @@ using System.Threading.Tasks;
namespace OmniLinkBridge namespace OmniLinkBridge
{ {
class Program internal class Program
{ {
static CoreServer server; private static CoreServer server;
static int Main(string[] args) private static int Main(string[] args)
{ {
bool interactive = false; bool interactive = false;
@ -55,6 +57,10 @@ namespace OmniLinkBridge
case "-ll": case "-ll":
Enum.TryParse(args[++i], out log_level); Enum.TryParse(args[++i], out log_level);
break; break;
case "-ld":
Global.DebugSettings = true;
Global.SendLogs = true;
break;
case "-s": case "-s":
Global.webapi_subscriptions_file = args[++i]; Global.webapi_subscriptions_file = args[++i];
break; break;
@ -64,6 +70,12 @@ namespace OmniLinkBridge
} }
} }
if (string.Compare(Environment.GetEnvironmentVariable("SEND_LOGS"), "1") == 0)
{
Global.DebugSettings = true;
Global.SendLogs = true;
}
config_file = GetFullPath(config_file); config_file = GetFullPath(config_file);
Global.webapi_subscriptions_file = GetFullPath(Global.webapi_subscriptions_file ?? "WebSubscriptions.json"); Global.webapi_subscriptions_file = GetFullPath(Global.webapi_subscriptions_file ?? "WebSubscriptions.json");
@ -76,7 +88,7 @@ namespace OmniLinkBridge
var log_config = new LoggerConfiguration() var log_config = new LoggerConfiguration()
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Enrich.WithProperty("Application", "OmniLinkBridge") .Enrich.WithProperty("Application", "OmniLinkBridge")
.Enrich.WithProperty("Session", Guid.NewGuid()) .Enrich.WithProperty("Session", Global.SessionID)
.Enrich.With<ControllerEnricher>() .Enrich.With<ControllerEnricher>()
.Enrich.FromLogContext(); .Enrich.FromLogContext();
@ -92,7 +104,10 @@ namespace OmniLinkBridge
rollingInterval: RollingInterval.Day, retainedFileCountLimit: 15)); rollingInterval: RollingInterval.Day, retainedFileCountLimit: 15));
} }
if (UseTelemetry()) if (Global.SendLogs)
log_config = log_config.WriteTo.Logger(lc => lc
.WriteTo.Http("https://telemetry.excalibur-partners.com"));
else if (UseTelemetry())
log_config = log_config.WriteTo.Logger(lc => lc log_config = log_config.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(Matching.WithProperty("Telemetry")) .Filter.ByIncludingOnly(Matching.WithProperty("Telemetry"))
.WriteTo.Http("https://telemetry.excalibur-partners.com")); .WriteTo.Http("https://telemetry.excalibur-partners.com"));
@ -155,7 +170,7 @@ namespace OmniLinkBridge
return 0; return 0;
} }
static string GetFullPath(string file) private static string GetFullPath(string file)
{ {
if (Path.IsPathRooted(file)) if (Path.IsPathRooted(file))
return file; return file;
@ -169,21 +184,38 @@ namespace OmniLinkBridge
args.Cancel = true; args.Cancel = true;
} }
static bool IsRunningOnMono() private static bool IsRunningOnMono()
{ {
return Type.GetType("Mono.Runtime") != null; return Type.GetType("Mono.Runtime") != null;
} }
static bool UseTelemetry() public static string GetEnvironment()
{
if (Environment.GetEnvironmentVariable("HASSIO_TOKEN") != null)
return "Home Assistant";
else if (IsRunningOnMono())
return Process.GetProcesses().Any(w => w.Id == 2) ? "Mono" : "Docker";
else
return "Native";
}
private static bool UseTelemetry()
{ {
return string.Compare(Environment.GetEnvironmentVariable("TELEMETRY_OPTOUT"), "1") != 0; return string.Compare(Environment.GetEnvironmentVariable("TELEMETRY_OPTOUT"), "1") != 0;
} }
static void ShowHelp() public static void ShowSendLogsWarning()
{
if (Global.SendLogs)
Log.Warning("SENDING LOGS TO DEVELOPER Controller: {ControllerID}, Session: {Session}",
Global.controller_id, Global.SessionID);
}
private static void ShowHelp()
{ {
Console.WriteLine( Console.WriteLine(
AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-e] [-d] [-j] [-s subscriptions_file]\n" + 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] [-i]\n" + "\t[-lf log_file|disable] [-lj [-ll verbose|debug|information|warning|error] [-ld] [-i]\n" +
"\t-c Specifies the configuration file. Default is OmniLinkBridge.ini\n" + "\t-c Specifies the configuration file. Default is OmniLinkBridge.ini\n" +
"\t-e Check environment variables for configuration settings\n" + "\t-e Check environment variables for configuration settings\n" +
"\t-d Show debug ouput for configuration loading\n" + "\t-d Show debug ouput for configuration loading\n" +
@ -191,8 +223,14 @@ namespace OmniLinkBridge
"\t-lf Specifies the rolling log file. Retention is 15 days. Default is log.txt.\n" + "\t-lf Specifies the rolling log file. Retention is 15 days. Default is log.txt.\n" +
"\t-lj Write logs as CLEF (compact log event format) JSON.\n" + "\t-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-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"); "\t-i Run in interactive mode");
Console.WriteLine(
"\nVersion: " + Assembly.GetExecutingAssembly().GetName().Version +
"\nEnvironment: " + GetEnvironment());
Console.WriteLine( Console.WriteLine(
"\nOmniLink Bridge collects anonymous telemetry data to help improve the software.\n" + "\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."); "You can opt of telemetry by setting a TELEMETRY_OPTOUT environment variable to 1.");

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.15.0")] [assembly: AssemblyVersion("1.1.19.0")]
[assembly: AssemblyFileVersion("1.1.15.0")] [assembly: AssemblyFileVersion("1.1.19.0")]

View File

@ -1,3 +1,4 @@
using OmniLinkBridge.MQTT.HomeAssistant;
using Serilog; using Serilog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -54,10 +55,11 @@ namespace OmniLinkBridge
Global.verbose_unit = settings.ValidateBool("verbose_unit"); Global.verbose_unit = settings.ValidateBool("verbose_unit");
Global.verbose_message = settings.ValidateBool("verbose_message"); Global.verbose_message = settings.ValidateBool("verbose_message");
Global.verbose_lock = settings.ValidateBool("verbose_lock"); Global.verbose_lock = settings.ValidateBool("verbose_lock");
Global.verbose_audio = settings.ValidateBool("verbose_audio");
// mySQL Logging // mySQL Logging
Global.mysql_logging = settings.ValidateBool("mysql_logging"); Global.mysql_logging = settings.ValidateBool("mysql_logging");
Global.mysql_connection = settings.CheckEnv("mysql_connection"); Global.mysql_connection = settings.CheckEnv("mysql_connection", true);
// Web Service // Web Service
Global.webapi_enabled = settings.ValidateBool("webapi_enabled"); Global.webapi_enabled = settings.ValidateBool("webapi_enabled");
@ -75,8 +77,8 @@ namespace OmniLinkBridge
{ {
Global.mqtt_server = settings.CheckEnv("mqtt_server"); Global.mqtt_server = settings.CheckEnv("mqtt_server");
Global.mqtt_port = settings.ValidatePort("mqtt_port"); Global.mqtt_port = settings.ValidatePort("mqtt_port");
Global.mqtt_username = settings.CheckEnv("mqtt_username"); Global.mqtt_username = settings.CheckEnv("mqtt_username", true);
Global.mqtt_password = settings.CheckEnv("mqtt_password"); Global.mqtt_password = settings.CheckEnv("mqtt_password", true);
Global.mqtt_prefix = settings.CheckEnv("mqtt_prefix") ?? "omnilink"; Global.mqtt_prefix = settings.CheckEnv("mqtt_prefix") ?? "omnilink";
Global.mqtt_discovery_prefix = settings.CheckEnv("mqtt_discovery_prefix") ?? "homeassistant"; Global.mqtt_discovery_prefix = settings.CheckEnv("mqtt_discovery_prefix") ?? "homeassistant";
Global.mqtt_discovery_name_prefix = settings.CheckEnv("mqtt_discovery_name_prefix") ?? string.Empty; Global.mqtt_discovery_name_prefix = settings.CheckEnv("mqtt_discovery_name_prefix") ?? string.Empty;
@ -86,9 +88,12 @@ namespace OmniLinkBridge
Global.mqtt_discovery_ignore_zones = settings.ValidateRange("mqtt_discovery_ignore_zones"); 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_ignore_units = settings.ValidateRange("mqtt_discovery_ignore_units");
Global.mqtt_discovery_area_code_required = settings.ValidateRange("mqtt_discovery_area_code_required"); 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_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_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 // Notifications
@ -102,31 +107,113 @@ namespace OmniLinkBridge
{ {
Global.mail_tls = settings.ValidateBool("mail_tls"); Global.mail_tls = settings.ValidateBool("mail_tls");
Global.mail_port = settings.ValidatePort("mail_port"); Global.mail_port = settings.ValidatePort("mail_port");
Global.mail_username = settings.CheckEnv("mail_username"); Global.mail_username = settings.CheckEnv("mail_username", true);
Global.mail_password = settings.CheckEnv("mail_password"); Global.mail_password = settings.CheckEnv("mail_password", true);
Global.mail_from = settings.ValidateMailFrom("mail_from"); Global.mail_from = settings.ValidateMailFrom("mail_from");
Global.mail_to = settings.ValidateMailTo("mail_to"); Global.mail_to = settings.ValidateMailTo("mail_to");
} }
// Prowl Notifications // Prowl Notifications
Global.prowl_key = settings.ValidateMultipleStrings("prowl_key"); Global.prowl_key = settings.ValidateMultipleStrings("prowl_key", true);
// Pushover Notifications // Pushover Notifications
Global.pushover_token = settings.CheckEnv("pushover_token"); Global.pushover_token = settings.CheckEnv("pushover_token", true);
Global.pushover_user = settings.ValidateMultipleStrings("pushover_user"); Global.pushover_user = settings.ValidateMultipleStrings("pushover_user", true);
} }
private static string CheckEnv(this NameValueCollection settings, string name) private static string CheckEnv(this NameValueCollection settings, string name, bool sensitive = false)
{ {
string env = Global.UseEnvironment ? Environment.GetEnvironmentVariable(name.ToUpper()) : null; string env = Global.UseEnvironment ? Environment.GetEnvironmentVariable(name.ToUpper()) : null;
string value = !string.IsNullOrEmpty(env) ? env : settings[name]; string value = !string.IsNullOrEmpty(env) ? env : settings[name];
if (Global.DebugSettings) if (Global.DebugSettings)
log.Debug((!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(5) + $"{name}: {value}"); log.Debug("{ConfigType} {ConfigName}: {ConfigValue}",
(!string.IsNullOrEmpty(env) ? "ENV" : "CONF").PadRight(4), name,
sensitive && value != null ? value.Truncate(3) + "***MASKED***" : value);
return 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() private static ConcurrentDictionary<int, T> LoadOverrideZone<T>(this NameValueCollection settings, string section) where T : new()
{ {
try try
@ -237,7 +324,7 @@ namespace OmniLinkBridge
private static string ValidateEncryptionKey(this NameValueCollection settings, string section) private static string ValidateEncryptionKey(this NameValueCollection settings, string section)
{ {
string value = settings.CheckEnv(section).Replace("-",""); string value = settings.CheckEnv(section, true).Replace("-","");
if (string.IsNullOrEmpty(value) || value.Length != 16) if (string.IsNullOrEmpty(value) || value.Length != 16)
{ {
@ -334,14 +421,14 @@ namespace OmniLinkBridge
} }
} }
private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section) private static string[] ValidateMultipleStrings(this NameValueCollection settings, string section, bool sensitive = false)
{ {
try try
{ {
if (settings.CheckEnv(section) == null) if (settings.CheckEnv(section, true) == null)
return new string[] { }; return new string[] { };
return settings.CheckEnv(section).Split(','); return settings.CheckEnv(section, sensitive).Split(',');
} }
catch catch
{ {
@ -367,6 +454,21 @@ 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) private static NameValueCollection LoadCollection(string[] lines)
{ {
NameValueCollection settings = new NameValueCollection(); NameValueCollection settings = new NameValueCollection();

View File

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

View File

@ -1,9 +1,4 @@
using HAI_Shared; using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.WebAPI namespace OmniLinkBridge.WebAPI
{ {

View File

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

View File

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

View File

@ -64,6 +64,14 @@ namespace OmniLinkBridgeTest
Assert.AreEqual(parser.Validate, true); Assert.AreEqual(parser.Validate, true);
Assert.AreEqual(parser.Code, 1234); 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 // Falures
payload = "disarm,1a"; payload = "disarm,1a";
parser = payload.ToCommandCode(supportValidate: true); parser = payload.ToCommandCode(supportValidate: true);

View File

@ -1,9 +1,13 @@
using HAI_Shared; using HAI_Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge; using OmniLinkBridge;
using OmniLinkBridge.Modules;
using OmniLinkBridge.MQTT; using OmniLinkBridge.MQTT;
using OmniLinkBridgeTest.Mock; using OmniLinkBridgeTest.Mock;
using Serilog;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
namespace OmniLinkBridgeTest namespace OmniLinkBridgeTest
{ {
@ -16,8 +20,24 @@ namespace OmniLinkBridgeTest
[TestInitialize] [TestInitialize]
public void Initialize() 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(); omniLink = new MockOmniLinkII();
messageProcessor = new MessageProcessor(omniLink); messageProcessor = new MessageProcessor(omniLink, audioSources, 8);
omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag; omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag;
} }
@ -313,6 +333,113 @@ namespace OmniLinkBridgeTest
// Check case insensitivity // Check case insensitivity
check(2, "LOCK", enuUnitCommand.Lock); 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,11 +1,15 @@
using HAI_Shared; using HAI_Shared;
using OmniLinkBridge.OmniLink; using OmniLinkBridge.OmniLink;
using Serilog;
using System; using System;
using System.Reflection;
namespace OmniLinkBridgeTest.Mock namespace OmniLinkBridgeTest.Mock
{ {
class MockOmniLinkII : IOmniLinkII class MockOmniLinkII : IOmniLinkII
{ {
private static readonly ILogger log = Log.Logger.ForContext(MethodBase.GetCurrentMethod().DeclaringType);
public clsHAC Controller { get; private set; } public clsHAC Controller { get; private set; }
public event EventHandler<SendCommandEventArgs> OnSendCommand; public event EventHandler<SendCommandEventArgs> OnSendCommand;
@ -21,6 +25,7 @@ namespace OmniLinkBridgeTest.Mock
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2) 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 }); OnSendCommand?.Invoke(null, new SendCommandEventArgs() { Cmd = Cmd, Par = Par, Pr2 = Pr2 });
return true; return true;
} }

View File

@ -9,6 +9,18 @@ namespace OmniLinkBridgeTest.Mock
public byte Par; public byte Par;
public ushort Pr2; 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) public override bool Equals(object other)
{ {
if (!(other is SendCommandEventArgs toCompareWith)) if (!(other is SendCommandEventArgs toCompareWith))

View File

@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge; using OmniLinkBridge;
using OmniLinkBridge.MQTT.HomeAssistant;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ha = OmniLinkBridge.MQTT.HomeAssistant; using ha = OmniLinkBridge.MQTT.HomeAssistant;
@ -36,8 +37,8 @@ namespace OmniLinkBridgeTest
Settings.LoadSettings(lines.ToArray()); Settings.LoadSettings(lines.ToArray());
Assert.AreEqual("1.1.1.1", Global.controller_address); Assert.AreEqual("1.1.1.1", Global.controller_address);
Assert.AreEqual(4369, Global.controller_port); Assert.AreEqual(4369, Global.controller_port);
Assert.AreEqual("00-00-00-00-00-00-00-01", Global.controller_key1); Assert.AreEqual("0000000000000001", Global.controller_key1);
Assert.AreEqual("00-00-00-00-00-00-00-02", Global.controller_key2); Assert.AreEqual("0000000000000002", Global.controller_key2);
Assert.AreEqual("MyController", Global.controller_name); Assert.AreEqual("MyController", Global.controller_name);
} }
@ -156,7 +157,8 @@ namespace OmniLinkBridgeTest
"mqtt_discovery_name_prefix = mynameprefix", "mqtt_discovery_name_prefix = mynameprefix",
"mqtt_discovery_ignore_zones = 1,2-3,4", "mqtt_discovery_ignore_zones = 1,2-3,4",
"mqtt_discovery_ignore_units = 2-5,7", "mqtt_discovery_ignore_units = 2-5,7",
"mqtt_discovery_area_code_required = 1", "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=5;device_class=garage_door",
"mqtt_discovery_override_zone = id=7;device_class=motion", "mqtt_discovery_override_zone = id=7;device_class=motion",
"mqtt_discovery_override_unit = id=1;type=switch", "mqtt_discovery_override_unit = id=1;type=switch",
@ -170,7 +172,25 @@ namespace OmniLinkBridgeTest
Assert.AreEqual("mynameprefix ", Global.mqtt_discovery_name_prefix); Assert.AreEqual("mynameprefix ", Global.mqtt_discovery_name_prefix);
Assert.IsTrue(Global.mqtt_discovery_ignore_zones.SetEquals(new int[] { 1, 2, 3, 4 })); 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 })); Assert.IsTrue(Global.mqtt_discovery_ignore_units.SetEquals(new int[] { 2, 3, 4, 5, 7 }));
Assert.IsTrue(Global.mqtt_discovery_area_code_required.SetEquals(new int[] { 1 }));
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>() Dictionary<int, OmniLinkBridge.MQTT.OverrideZone> override_zone = new Dictionary<int, OmniLinkBridge.MQTT.OverrideZone>()
{ {
@ -197,6 +217,17 @@ namespace OmniLinkBridgeTest
Global.mqtt_discovery_override_unit.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideUnit value); Global.mqtt_discovery_override_unit.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideUnit value);
Assert.AreEqual(override_unit[pair.Key].type, value.type); 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] [TestMethod]

View File

@ -326,6 +326,38 @@ PUB omnilink/lockX/command
string lock, unlock 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 ## 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. To test the web service API you can use your browser to view a page or PowerShell (see below) to change a value.