Skip to content

Commit

Permalink
1.1.13 - Add MQTT area security code support
Browse files Browse the repository at this point in the history
  • Loading branch information
rwagoner committed Oct 21, 2022
1 parent 7c24d90 commit 1ce5e3d
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 52 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 28 additions & 6 deletions OmniLinkBridge/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using OmniLinkBridge.MQTT;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
Expand All @@ -24,15 +25,36 @@ public static bool IsBitSet(this byte b, int pos)
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)
Expand Down
1 change: 1 addition & 0 deletions OmniLinkBridge/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public abstract class Global
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
Expand Down
11 changes: 9 additions & 2 deletions OmniLinkBridge/MQTT/Alarm.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions OmniLinkBridge/MQTT/AreaCommandCode.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
37 changes: 22 additions & 15 deletions OmniLinkBridge/MQTT/MappingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ public static Alarm ToConfig(this clsArea area)
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;
}

Expand Down Expand Up @@ -118,7 +126,7 @@ public static BinarySensor ToConfigAux(this clsArea area)
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;
}
Expand Down Expand Up @@ -177,7 +185,7 @@ public static BinarySensor ToConfigTemp(this clsArea area)

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),
Expand All @@ -187,18 +195,17 @@ public static string ToJsonState(this clsArea area)
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);
}
Expand Down
66 changes: 54 additions & 12 deletions OmniLinkBridge/MQTT/MessageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,63 @@ public void Process(string messageTopic, string payload)

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)
{
Expand All @@ -95,17 +138,16 @@ private void ProcessAreaReceived(clsArea area, Topic command, string payload)

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);
}
}

Expand Down
7 changes: 4 additions & 3 deletions OmniLinkBridge/OmniLinkBridge.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
1 change: 1 addition & 0 deletions OmniLinkBridge/OmniLinkBridge.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions OmniLinkBridge/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// 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")]
1 change: 1 addition & 0 deletions OmniLinkBridge/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public static void LoadSettings(NameValueCollection settings)

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");
}

Expand Down
52 changes: 43 additions & 9 deletions OmniLinkBridgeTest/ExtensionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.MQTT;

namespace OmniLinkBridgeTest
{
Expand Down Expand Up @@ -41,18 +42,51 @@ public void TestIsBitSet()
[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]
Expand All @@ -65,4 +99,4 @@ public void TestParseRange()
CollectionAssert.AreEqual(new List<int>(new int[] { 1, 2, 3, 5, 6 }), range);
}
}
}
}
Loading

0 comments on commit 1ce5e3d

Please sign in to comment.