1.1.19 - Fix MQTT alarm control panel for Home Assistant 2024.6

This commit is contained in:
Ryan Wagoner 2024-06-06 20:20:25 -04:00
parent f297c2fcaa
commit efa16fb2c3
11 changed files with 184 additions and 18 deletions

View file

@ -59,7 +59,7 @@ 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 Type mqtt_discovery_button_type;

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,4 +1,5 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT.HomeAssistant namespace OmniLinkBridge.MQTT.HomeAssistant
{ {
@ -16,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

@ -25,11 +25,28 @@ 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)
{
if(override_area.code_arm || override_area.code_disarm)
{ {
ret.command_template = "{{ action }},validate,{{ code }}"; ret.command_template = "{{ action }},validate,{{ code }}";
ret.code = "REMOTE_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;
} }

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

@ -87,6 +87,7 @@
<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\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" />

View file

@ -55,20 +55,31 @@ 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
# Flags (LTe 41-88, IIe 73-128, Pro 393-511) switch or number, defaults to switch # 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=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 # Publish buttons as this Home Assistant device type
# must be button (recommended) or switch (default, previous behavior) # must be button (recommended) or switch (default, previous behavior)
mqtt_discovery_button_type = switch mqtt_discovery_button_type = switch

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.18.0")] [assembly: AssemblyVersion("1.1.19.0")]
[assembly: AssemblyFileVersion("1.1.18.0")] [assembly: AssemblyFileVersion("1.1.19.0")]

View file

@ -88,7 +88,7 @@ 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_discovery_button_type = settings.ValidateType("mqtt_discovery_button_type", typeof(Switch), typeof(Button));
@ -134,6 +134,86 @@ namespace OmniLinkBridge
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

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

@ -157,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",
@ -171,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>()
{ {