mirror of
https://github.com/excaliburpartners/OmniLinkBridge
synced 2024-12-22 02:32:25 +00:00
1.1.13 - Add MQTT area security code support
This commit is contained in:
parent
7c24d9046e
commit
1ce5e3dab9
|
@ -1,6 +1,7 @@
|
|||
FROM mono:latest AS build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y unixodbc
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -24,15 +25,36 @@ namespace OmniLinkBridge
|
|||
return (b & (1 << pos)) != 0;
|
||||
}
|
||||
|
||||
public static (string, int) ToCommandCode(this string payload)
|
||||
public static AreaCommandCode ToCommandCode(this string payload, bool supportValidate = false)
|
||||
{
|
||||
string[] payloads = payload.Split(',');
|
||||
|
||||
int code = 0;
|
||||
if (payloads.Length > 1)
|
||||
int.TryParse(payloads[1], out code);
|
||||
|
||||
return (payloads[0], code);
|
||||
AreaCommandCode ret = new AreaCommandCode()
|
||||
{
|
||||
Command = payloads[0]
|
||||
};
|
||||
|
||||
if (payload.Length == 1)
|
||||
return ret;
|
||||
|
||||
if (payloads.Length == 2)
|
||||
{
|
||||
ret.Success = int.TryParse(payloads[1], out code);
|
||||
}
|
||||
else if (supportValidate && payloads.Length == 3)
|
||||
{
|
||||
if (string.Compare(payloads[1], "validate", true) == 0)
|
||||
{
|
||||
ret.Validate = true;
|
||||
ret.Success = int.TryParse(payloads[2], out code);
|
||||
}
|
||||
else
|
||||
ret.Success = false;
|
||||
}
|
||||
|
||||
ret.Code = code;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToSpaceTitleCase(this string phrase)
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace OmniLinkBridge
|
|||
public static string mqtt_discovery_name_prefix;
|
||||
public static HashSet<int> mqtt_discovery_ignore_zones;
|
||||
public static HashSet<int> mqtt_discovery_ignore_units;
|
||||
public static HashSet<int> mqtt_discovery_area_code_required;
|
||||
public static ConcurrentDictionary<int, MQTT.OverrideZone> mqtt_discovery_override_zone;
|
||||
|
||||
// Notifications
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
namespace OmniLinkBridge.MQTT
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Alarm : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
|
||||
//public string code { get; set; } = string.Empty;
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string command_template { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string code { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
10
OmniLinkBridge/MQTT/AreaCommandCode.cs
Normal file
10
OmniLinkBridge/MQTT/AreaCommandCode.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class AreaCommandCode
|
||||
{
|
||||
public bool Success { get; set; } = true;
|
||||
public string Command { get; set; }
|
||||
public bool Validate { get; set; }
|
||||
public int Code { get; set; }
|
||||
}
|
||||
}
|
|
@ -18,8 +18,16 @@ namespace OmniLinkBridge.MQTT
|
|||
unique_id = $"{Global.mqtt_prefix}area{area.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + area.Name,
|
||||
state_topic = area.ToTopic(Topic.basic_state),
|
||||
command_topic = area.ToTopic(Topic.command)
|
||||
command_topic = area.ToTopic(Topic.command),
|
||||
|
||||
};
|
||||
|
||||
if(Global.mqtt_discovery_area_code_required.Contains(area.Number))
|
||||
{
|
||||
ret.command_template = "{{ action }},validate,{{ code }}";
|
||||
ret.code = "REMOTE_CODE";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -118,7 +126,7 @@ namespace OmniLinkBridge.MQTT
|
|||
name = $"{Global.mqtt_discovery_name_prefix}{area.Name} Auxiliary",
|
||||
device_class = BinarySensor.DeviceClass.problem,
|
||||
state_topic = area.ToTopic(Topic.json_state),
|
||||
value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
value_template = "{% if value_json.burglary_alarm %} ON {%- else -%} OFF {%- endif %}"
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
@ -177,7 +185,7 @@ namespace OmniLinkBridge.MQTT
|
|||
|
||||
public static string ToJsonState(this clsArea area)
|
||||
{
|
||||
AreaState state = new AreaState()
|
||||
AreaState state = new AreaState
|
||||
{
|
||||
arming = area.ExitTimer > 0,
|
||||
burglary_alarm = area.AreaAlarms.IsBitSet(0),
|
||||
|
@ -187,18 +195,17 @@ namespace OmniLinkBridge.MQTT
|
|||
freeze_alarm = area.AreaAlarms.IsBitSet(4),
|
||||
water_alarm = area.AreaAlarms.IsBitSet(5),
|
||||
duress_alarm = area.AreaAlarms.IsBitSet(6),
|
||||
temperature_alarm = area.AreaAlarms.IsBitSet(7)
|
||||
};
|
||||
|
||||
state.mode = area.AreaMode switch
|
||||
{
|
||||
enuSecurityMode.Night => "night",
|
||||
enuSecurityMode.NightDly => "night_delay",
|
||||
enuSecurityMode.Day => "home",
|
||||
enuSecurityMode.DayInst => "home_instant",
|
||||
enuSecurityMode.Away => "away",
|
||||
enuSecurityMode.Vacation => "vacation",
|
||||
_ => "off",
|
||||
temperature_alarm = area.AreaAlarms.IsBitSet(7),
|
||||
mode = area.AreaMode switch
|
||||
{
|
||||
enuSecurityMode.Night => "night",
|
||||
enuSecurityMode.NightDly => "night_delay",
|
||||
enuSecurityMode.Day => "home",
|
||||
enuSecurityMode.DayInst => "home_instant",
|
||||
enuSecurityMode.Away => "away",
|
||||
enuSecurityMode.Vacation => "vacation",
|
||||
_ => "off",
|
||||
}
|
||||
};
|
||||
return JsonConvert.SerializeObject(state);
|
||||
}
|
||||
|
|
|
@ -64,20 +64,63 @@ namespace OmniLinkBridge.MQTT
|
|||
|
||||
private void ProcessAreaReceived(clsArea area, Topic command, string payload)
|
||||
{
|
||||
int code;
|
||||
(payload, code) = payload.ToCommandCode();
|
||||
AreaCommandCode parser = payload.ToCommandCode(supportValidate: true);
|
||||
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out AreaCommands cmd))
|
||||
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out AreaCommands cmd))
|
||||
{
|
||||
if (area.Number == 0)
|
||||
log.Debug("SetArea: 0 implies all areas will be changed");
|
||||
|
||||
log.Debug("SetArea: {id} to {value}", area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "));
|
||||
OmniLink.SendCommand(AreaMapping[cmd], (byte)code, (ushort)area.Number);
|
||||
if (parser.Validate)
|
||||
{
|
||||
string sCode = parser.Code.ToString();
|
||||
|
||||
if(sCode.Length != 4)
|
||||
{
|
||||
log.Warning("SetArea: {id}, Invalid security code: must be 4 digits", area.Number);
|
||||
return;
|
||||
}
|
||||
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgRequestValidateCode(OmniLink.Controller.Connection)
|
||||
{
|
||||
Area = (byte)area.Number,
|
||||
Digit1 = (byte)int.Parse(sCode[0].ToString()),
|
||||
Digit2 = (byte)int.Parse(sCode[1].ToString()),
|
||||
Digit3 = (byte)int.Parse(sCode[2].ToString()),
|
||||
Digit4 = (byte)int.Parse(sCode[3].ToString())
|
||||
}, (M, B, Timeout) =>
|
||||
{
|
||||
if (Timeout || !((B.Length > 3) && (B[0] == 0x21) && (enuOmniLink2MessageType)B[2] == enuOmniLink2MessageType.ValidateCode))
|
||||
return;
|
||||
|
||||
var validateCode = new clsOL2MsgValidateCode(OmniLink.Controller.Connection, B);
|
||||
|
||||
if(validateCode.AuthorityLevel == 0)
|
||||
{
|
||||
log.Warning("SetArea: {id}, Invalid security code: validation failed", area.Number);
|
||||
return;
|
||||
}
|
||||
|
||||
log.Debug("SetArea: {id}, Validated security code, Code Number: {code}, Authority: {authority}",
|
||||
area.Number, validateCode.CodeNumber, validateCode.AuthorityLevel.ToString());
|
||||
|
||||
log.Debug("SetArea: {id} to {value}, Code Number: {code}",
|
||||
area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "), validateCode.CodeNumber);
|
||||
|
||||
OmniLink.SendCommand(AreaMapping[cmd], validateCode.CodeNumber, (ushort)area.Number);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.Debug("SetArea: {id} to {value}, Code Number: {code}",
|
||||
area.Number, cmd.ToString().Replace("arm_", "").Replace("_", " "), parser.Code);
|
||||
|
||||
OmniLink.SendCommand(AreaMapping[cmd], (byte)parser.Code, (ushort)area.Number);
|
||||
}
|
||||
else if (command == Topic.alarm_command && area.Number > 0 && Enum.TryParse(payload, true, out AlarmCommands alarm))
|
||||
else if (command == Topic.alarm_command && area.Number > 0 && Enum.TryParse(parser.Command, true, out AlarmCommands alarm))
|
||||
{
|
||||
log.Debug("SetAreaAlarm: {id} to {value}", area.Number, payload);
|
||||
log.Debug("SetAreaAlarm: {id} to {value}", area.Number, parser.Command);
|
||||
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgActivateKeypadEmg(OmniLink.Controller.Connection)
|
||||
{
|
||||
|
@ -95,17 +138,16 @@ namespace OmniLinkBridge.MQTT
|
|||
|
||||
private void ProcessZoneReceived(clsZone zone, Topic command, string payload)
|
||||
{
|
||||
int code;
|
||||
(payload, code) = payload.ToCommandCode();
|
||||
AreaCommandCode parser = payload.ToCommandCode();
|
||||
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out ZoneCommands cmd) &&
|
||||
if (parser.Success && command == Topic.command && Enum.TryParse(parser.Command, true, out ZoneCommands cmd) &&
|
||||
!(zone.Number == 0 && cmd == ZoneCommands.bypass))
|
||||
{
|
||||
if (zone.Number == 0)
|
||||
log.Debug("SetZone: 0 implies all zones will be restored");
|
||||
|
||||
log.Debug("SetZone: {id} to {value}", zone.Number, payload);
|
||||
OmniLink.SendCommand(ZoneMapping[cmd], (byte)code, (ushort)zone.Number);
|
||||
log.Debug("SetZone: {id} to {value}", zone.Number, parser.Command);
|
||||
OmniLink.SendCommand(ZoneMapping[cmd], (byte)parser.Code, (ushort)zone.Number);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
<Compile Include="Modules\TimeSyncModule.cs" />
|
||||
<Compile Include="MQTT\Alarm.cs" />
|
||||
<Compile Include="MQTT\AlarmCommands.cs" />
|
||||
<Compile Include="MQTT\AreaCommandCode.cs" />
|
||||
<Compile Include="MQTT\AreaCommands.cs" />
|
||||
<Compile Include="MQTT\AreaState.cs" />
|
||||
<Compile Include="MQTT\BinarySensor.cs" />
|
||||
|
@ -174,13 +175,13 @@
|
|||
<Version>4.5.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet.Extensions.ManagedClient">
|
||||
<Version>3.1.1</Version>
|
||||
<Version>3.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>13.0.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog">
|
||||
<Version>2.10.0</Version>
|
||||
<Version>2.12.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Formatting.Compact">
|
||||
<Version>1.1.0</Version>
|
||||
|
@ -189,7 +190,7 @@
|
|||
<Version>1.5.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.Console">
|
||||
<Version>4.0.1</Version>
|
||||
<Version>4.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.Sinks.File">
|
||||
<Version>5.0.0</Version>
|
||||
|
|
|
@ -52,6 +52,7 @@ mqtt_discovery_name_prefix =
|
|||
# Specify a range of numbers like 1,2,3,5-10
|
||||
mqtt_discovery_ignore_zones =
|
||||
mqtt_discovery_ignore_units =
|
||||
mqtt_discovery_area_code_required =
|
||||
# device_class must be battery, door, garage_door, gas, moisture, motion, problem, smoke, or window
|
||||
#mqtt_discovery_override_zone = id=5;device_class=garage_door
|
||||
#mqtt_discovery_override_zone = id=6;device_class=garage_door
|
||||
|
|
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.1.12.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.12.0")]
|
||||
[assembly: AssemblyVersion("1.1.13.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.13.0")]
|
||||
|
|
|
@ -84,6 +84,7 @@ namespace OmniLinkBridge
|
|||
|
||||
Global.mqtt_discovery_ignore_zones = settings.ValidateRange("mqtt_discovery_ignore_zones");
|
||||
Global.mqtt_discovery_ignore_units = settings.ValidateRange("mqtt_discovery_ignore_units");
|
||||
Global.mqtt_discovery_area_code_required = settings.ValidateRange("mqtt_discovery_area_code_required");
|
||||
Global.mqtt_discovery_override_zone = settings.LoadOverrideZone<MQTT.OverrideZone>("mqtt_discovery_override_zone");
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Text;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using OmniLinkBridge.MQTT;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
|
@ -41,18 +42,51 @@ namespace OmniLinkBridgeTest
|
|||
[TestMethod]
|
||||
public void TestToCommandCode()
|
||||
{
|
||||
string payload, command;
|
||||
int code;
|
||||
string payload;
|
||||
AreaCommandCode parser;
|
||||
|
||||
payload = "disarm";
|
||||
(command, code) = payload.ToCommandCode();
|
||||
Assert.AreEqual(command, "disarm");
|
||||
Assert.AreEqual(code, 0);
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,1";
|
||||
(command, code) = payload.ToCommandCode();
|
||||
Assert.AreEqual(command, "disarm");
|
||||
Assert.AreEqual(code, 1);
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 1);
|
||||
|
||||
payload = "disarm,validate,1234";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, true);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, true);
|
||||
Assert.AreEqual(parser.Code, 1234);
|
||||
|
||||
// Falures
|
||||
payload = "disarm,1a";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,validate,";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, true);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
|
||||
payload = "disarm,test,1234";
|
||||
parser = payload.ToCommandCode(supportValidate: true);
|
||||
Assert.AreEqual(parser.Success, false);
|
||||
Assert.AreEqual(parser.Command, "disarm");
|
||||
Assert.AreEqual(parser.Validate, false);
|
||||
Assert.AreEqual(parser.Code, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -65,4 +99,4 @@ namespace OmniLinkBridgeTest
|
|||
CollectionAssert.AreEqual(new List<int>(new int[] { 1, 2, 3, 5, 6 }), range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -166,19 +166,20 @@ string secure, trouble
|
|||
SUB omnilink/areaX/name
|
||||
string Area name
|
||||
|
||||
SUB omnilink/areaX/state
|
||||
SUB omnilink/areaX/state
|
||||
string triggered, arming, armed_night, armed_night_delay, armed_home, armed_home_instant, armed_away, armed_vacation, disarmed
|
||||
|
||||
SUB omnilink/areaX/basic_state
|
||||
SUB omnilink/areaX/basic_state
|
||||
string triggered, arming, armed_night, armed_home, armed_away, armed_vacation, disarmed
|
||||
|
||||
SUB omnilink/areaX/json_state
|
||||
string json
|
||||
|
||||
PUB omnilink/areaX/command
|
||||
PUB omnilink/areaX/command
|
||||
string arm_home, arm_away, arm_night, disarm, arm_home_instant, arm_night_delay, arm_vacation
|
||||
note Use area 0 for all areas
|
||||
note Optionally the user code number can be specified 'disarm,1'
|
||||
note Optionally the security code can be be specified 'disarm,validate,1234'
|
||||
|
||||
PUB omnilink/areaX/alarm_command
|
||||
string burglary, fire, auxiliary
|
||||
|
|
Loading…
Reference in a new issue