mirror of
https://github.com/excaliburpartners/OmniLinkBridge
synced 2024-12-22 10:42:24 +00:00
Add MQTT lock support
This commit is contained in:
parent
495ce74149
commit
84b52c8f30
|
@ -31,6 +31,7 @@ namespace OmniLinkBridge
|
|||
public static bool verbose_thermostat;
|
||||
public static bool verbose_unit;
|
||||
public static bool verbose_message;
|
||||
public static bool verbose_lock;
|
||||
|
||||
// mySQL Logging
|
||||
public static bool mysql_logging;
|
||||
|
|
21
OmniLinkBridge/MQTT/HomeAssistant/Lock.cs
Normal file
21
OmniLinkBridge/MQTT/HomeAssistant/Lock.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace OmniLinkBridge.MQTT.HomeAssistant
|
||||
{
|
||||
public class Lock : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_lock { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string payload_unlock { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_locked { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string state_unlocked { get; set; }
|
||||
}
|
||||
}
|
|
@ -605,5 +605,35 @@ namespace OmniLinkBridge.MQTT
|
|||
else
|
||||
return "off";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsAccessControlReader reader, Topic topic)
|
||||
{
|
||||
return $"{Global.mqtt_prefix}/lock{reader.Number}/{topic}";
|
||||
}
|
||||
|
||||
public static Lock ToConfig(this clsAccessControlReader reader)
|
||||
{
|
||||
Lock ret = new Lock
|
||||
{
|
||||
unique_id = $"{Global.mqtt_prefix}lock{reader.Number}",
|
||||
name = Global.mqtt_discovery_name_prefix + reader.Name,
|
||||
state_topic = reader.ToTopic(Topic.state),
|
||||
command_topic = reader.ToTopic(Topic.command),
|
||||
payload_lock = "lock",
|
||||
payload_unlock = "unlock",
|
||||
state_locked = "locked",
|
||||
state_unlocked = "unlocked"
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsAccessControlReader reader)
|
||||
{
|
||||
if (reader.LockStatus == 0)
|
||||
return "locked";
|
||||
else
|
||||
return "unlocked";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ namespace OmniLinkBridge.MQTT
|
|||
ProcessButtonReceived(OmniLink.Controller.Buttons[id], topic, payload);
|
||||
else if (type == CommandTypes.message && id > 0 && id <= OmniLink.Controller.Messages.Count)
|
||||
ProcessMessageReceived(OmniLink.Controller.Messages[id], topic, payload);
|
||||
else if (type == CommandTypes.@lock && id <= OmniLink.Controller.AccessControlReaders.Count)
|
||||
ProcessLockReceived(OmniLink.Controller.AccessControlReaders[id], topic, payload);
|
||||
}
|
||||
|
||||
private static readonly IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
|
||||
|
@ -294,5 +296,24 @@ namespace OmniLinkBridge.MQTT
|
|||
OmniLink.SendCommand(MessageMapping[cmd], par, (ushort)message.Number);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly IDictionary<LockCommands, enuUnitCommand> LockMapping = new Dictionary<LockCommands, enuUnitCommand>
|
||||
{
|
||||
{ LockCommands.@lock, enuUnitCommand.Lock },
|
||||
{ LockCommands.unlock, enuUnitCommand.Unlock },
|
||||
};
|
||||
|
||||
private void ProcessLockReceived(clsAccessControlReader reader, Topic command, string payload)
|
||||
{
|
||||
if (command == Topic.command && Enum.TryParse(payload, true, out LockCommands cmd))
|
||||
{
|
||||
if (reader.Number == 0)
|
||||
log.Debug("SetLock: 0 implies all locks will be changed");
|
||||
|
||||
log.Debug("SetLock: {id} to {value}", reader.Number, payload);
|
||||
|
||||
OmniLink.SendCommand(LockMapping[cmd], 0, (ushort)reader.Number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
unit,
|
||||
thermostat,
|
||||
button,
|
||||
message
|
||||
message,
|
||||
@lock
|
||||
}
|
||||
}
|
||||
|
|
8
OmniLinkBridge/MQTT/Parser/LockCommands.cs
Normal file
8
OmniLinkBridge/MQTT/Parser/LockCommands.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace OmniLinkBridge.MQTT.Parser
|
||||
{
|
||||
enum LockCommands
|
||||
{
|
||||
@lock,
|
||||
unlock
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ namespace OmniLinkBridge.Modules
|
|||
omnilink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
omnilink.OnUnitStatus += Omnilink_OnUnitStatus;
|
||||
omnilink.OnMessageStatus += Omnilink_OnMessageStatus;
|
||||
omnilink.OnLockStatus += Omnilink_OnLockStatus;
|
||||
omnilink.OnSystemStatus += Omnilink_OnSystemStatus;
|
||||
}
|
||||
|
||||
|
@ -199,10 +200,24 @@ namespace OmniLinkBridge.Modules
|
|||
thermostatUsage++;
|
||||
}
|
||||
|
||||
ushort lockUsage = 0;
|
||||
for (ushort i = 1; i <= omnilink.Controller.AccessControlReaders.Count; i++)
|
||||
{
|
||||
clsAccessControlReader reader = omnilink.Controller.AccessControlReaders[i];
|
||||
|
||||
if (reader.DefaultProperties == true)
|
||||
continue;
|
||||
|
||||
lockUsage++;
|
||||
|
||||
if(Global.verbose_lock)
|
||||
log.Verbose("Initial LockStatus {id} {name}, Status: {status}", i, reader.Name, reader.LockStatusText());
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Telemetry", "ControllerUsage"))
|
||||
log.Debug("Controller has {AreaUsage} areas, {ZoneUsage} zones, {UnitUsage} units, " +
|
||||
"{OutputUsage} outputs, {FlagUsage} flags, {ThermostatUsage} thermostats",
|
||||
areaUsage, zoneUsage, unitUsage, outputUsage, flagUsage, thermostatUsage);
|
||||
"{OutputUsage} outputs, {FlagUsage} flags, {ThermostatUsage} thermostats, {LockUsage} locks",
|
||||
areaUsage, zoneUsage, unitUsage, outputUsage, flagUsage, thermostatUsage, lockUsage);
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
|
@ -337,6 +352,12 @@ namespace OmniLinkBridge.Modules
|
|||
Notification.Notify("Message", e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnLockStatus(object sender, LockStatusEventArgs e)
|
||||
{
|
||||
if (Global.verbose_lock)
|
||||
log.Verbose("LockStatus {id} {name}, Status: {status}", e.ID, e.Reader.Name, e.Reader.LockStatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnSystemStatus(object sender, SystemStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
|
|
|
@ -50,6 +50,7 @@ namespace OmniLinkBridge.Modules
|
|||
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
OmniLink.OnButtonStatus += OmniLink_OnButtonStatus;
|
||||
OmniLink.OnMessageStatus += OmniLink_OnMessageStatus;
|
||||
OmniLink.OnLockStatus += OmniLink_OnLockStatus;
|
||||
OmniLink.OnSystemStatus += OmniLink_OnSystemStatus;
|
||||
|
||||
MessageProcessor = new MessageProcessor(omni);
|
||||
|
@ -169,6 +170,7 @@ namespace OmniLinkBridge.Modules
|
|||
PublishThermostats();
|
||||
PublishButtons();
|
||||
PublishMessages();
|
||||
PublishLocks();
|
||||
|
||||
PublishControllerStatus(ONLINE);
|
||||
PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText());
|
||||
|
@ -429,6 +431,29 @@ namespace OmniLinkBridge.Modules
|
|||
}
|
||||
}
|
||||
|
||||
private void PublishLocks()
|
||||
{
|
||||
log.Debug("Publishing {type}", "locks");
|
||||
|
||||
for (ushort i = 1; i <= OmniLink.Controller.AccessControlReaders.Count; i++)
|
||||
{
|
||||
clsAccessControlReader reader = OmniLink.Controller.AccessControlReaders[i];
|
||||
|
||||
if (reader.DefaultProperties == true)
|
||||
{
|
||||
PublishAsync(reader.ToTopic(Topic.name), null);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/lock/{Global.mqtt_prefix}/lock{i}/config", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishLockStateAsync(reader);
|
||||
|
||||
PublishAsync(reader.ToTopic(Topic.name), reader.Name);
|
||||
PublishAsync($"{Global.mqtt_discovery_prefix}/lock/{Global.mqtt_prefix}/lock{i}/config",
|
||||
JsonConvert.SerializeObject(reader.ToConfig()));
|
||||
}
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
|
@ -514,6 +539,14 @@ namespace OmniLinkBridge.Modules
|
|||
PublishMessageStateAsync(e.Message);
|
||||
}
|
||||
|
||||
private void OmniLink_OnLockStatus(object sender, LockStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
return;
|
||||
|
||||
PublishLockStateAsync(e.Reader);
|
||||
}
|
||||
|
||||
private void OmniLink_OnSystemStatus(object sender, SystemStatusEventArgs e)
|
||||
{
|
||||
if (!MqttClient.IsConnected)
|
||||
|
@ -590,6 +623,11 @@ namespace OmniLinkBridge.Modules
|
|||
return PublishAsync(message.ToTopic(Topic.state), message.ToState());
|
||||
}
|
||||
|
||||
private Task PublishLockStateAsync(clsAccessControlReader reader)
|
||||
{
|
||||
return PublishAsync(reader.ToTopic(Topic.state), reader.ToState());
|
||||
}
|
||||
|
||||
private Task PublishAsync(string topic, string payload)
|
||||
{
|
||||
return MqttClient.PublishAsync(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace OmniLinkBridge.Modules
|
|||
public event EventHandler<UnitStatusEventArgs> OnUnitStatus;
|
||||
public event EventHandler<ButtonStatusEventArgs> OnButtonStatus;
|
||||
public event EventHandler<MessageStatusEventArgs> OnMessageStatus;
|
||||
public event EventHandler<LockStatusEventArgs> OnLockStatus;
|
||||
public event EventHandler<SystemStatusEventArgs> OnSystemStatus;
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
@ -214,6 +215,7 @@ namespace OmniLinkBridge.Modules
|
|||
await GetNamed(enuObjectType.Unit);
|
||||
await GetNamed(enuObjectType.Message);
|
||||
await GetNamed(enuObjectType.Button);
|
||||
await GetNamed(enuObjectType.AccessControlReader);
|
||||
}
|
||||
|
||||
private async Task GetSystemFormats()
|
||||
|
@ -342,6 +344,9 @@ namespace OmniLinkBridge.Modules
|
|||
case enuObjectType.Button:
|
||||
Controller.Buttons.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.AccessControlReader:
|
||||
Controller.AccessControlReaders.CopyProperties(MSG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -679,6 +684,17 @@ namespace OmniLinkBridge.Modules
|
|||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.AccessControlLock:
|
||||
for (byte i = 0; i < MSG.AccessControlLockCount(); i++)
|
||||
{
|
||||
Controller.AccessControlReaders[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnLockStatus?.Invoke(this, new LockStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Reader = Controller.AccessControlReaders[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (Global.verbose_unhandled)
|
||||
{
|
||||
|
|
11
OmniLinkBridge/OmniLink/LockStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/LockStatusEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class LockStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsAccessControlReader Reader { get; set; }
|
||||
}
|
||||
}
|
|
@ -83,6 +83,7 @@
|
|||
<Compile Include="CoreServer.cs" />
|
||||
<Compile Include="Modules\TimeSyncModule.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Alarm.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Lock.cs" />
|
||||
<Compile Include="MQTT\OverrideUnit.cs" />
|
||||
<Compile Include="MQTT\Parser\AlarmCommands.cs" />
|
||||
<Compile Include="MQTT\AreaCommandCode.cs" />
|
||||
|
@ -95,6 +96,7 @@
|
|||
<Compile Include="MQTT\HomeAssistant\Climate.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\DeviceRegistry.cs" />
|
||||
<Compile Include="MQTT\Extensions.cs" />
|
||||
<Compile Include="MQTT\Parser\LockCommands.cs" />
|
||||
<Compile Include="MQTT\Parser\MessageCommands.cs" />
|
||||
<Compile Include="MQTT\MessageProcessor.cs" />
|
||||
<Compile Include="MQTT\HomeAssistant\Number.cs" />
|
||||
|
@ -115,6 +117,7 @@
|
|||
<Compile Include="OmniLink\ButtonStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\IOmniLinkII.cs" />
|
||||
<Compile Include="OmniLink\SystemEventType.cs" />
|
||||
<Compile Include="OmniLink\LockStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\UnitStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\ThermostatStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\MessageStatusEventArgs.cs" />
|
||||
|
|
|
@ -22,6 +22,7 @@ verbose_thermostat_timer = yes
|
|||
verbose_thermostat = yes
|
||||
verbose_unit = yes
|
||||
verbose_message = yes
|
||||
verbose_lock = yes
|
||||
|
||||
# mySQL Logging (yes/no)
|
||||
mysql_logging = no
|
||||
|
|
|
@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridge")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2022")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ namespace OmniLinkBridge
|
|||
Global.verbose_thermostat = settings.ValidateBool("verbose_thermostat");
|
||||
Global.verbose_unit = settings.ValidateBool("verbose_unit");
|
||||
Global.verbose_message = settings.ValidateBool("verbose_message");
|
||||
Global.verbose_lock = settings.ValidateBool("verbose_lock");
|
||||
|
||||
// mySQL Logging
|
||||
Global.mysql_logging = settings.ValidateBool("mysql_logging");
|
||||
|
|
|
@ -286,6 +286,33 @@ namespace OmniLinkBridgeTest
|
|||
|
||||
check(2, "SHOW", enuUnitCommand.ShowMsgWBeep, 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LockCommand()
|
||||
{
|
||||
void check(ushort id, string payload, enuUnitCommand command)
|
||||
{
|
||||
SendCommandEventArgs actual = null;
|
||||
omniLink.OnSendCommand += (sender, e) => { actual = e; };
|
||||
messageProcessor.Process($"omnilink/lock{id}/command", payload);
|
||||
SendCommandEventArgs expected = new SendCommandEventArgs()
|
||||
{
|
||||
Cmd = command,
|
||||
Par = 0,
|
||||
Pr2 = id
|
||||
};
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
check(1, "lock", enuUnitCommand.Lock);
|
||||
check(1, "unlock", enuUnitCommand.Unlock);
|
||||
|
||||
// Check all locks
|
||||
check(0, "lock", enuUnitCommand.Lock);
|
||||
|
||||
// Check case insensitivity
|
||||
check(2, "LOCK", enuUnitCommand.Lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridgeTest")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2022")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2024")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
|
|
@ -78,7 +78,8 @@ namespace OmniLinkBridgeTest
|
|||
"verbose_thermostat_timer",
|
||||
"verbose_thermostat",
|
||||
"verbose_unit",
|
||||
"verbose_message"
|
||||
"verbose_message",
|
||||
"verbose_lock"
|
||||
})
|
||||
{
|
||||
List<string> lines = new List<string>(RequiredSettings())
|
||||
|
|
12
README.md
12
README.md
|
@ -312,6 +312,18 @@ PUB omnilink/messageX/command
|
|||
string show, show_no_beep, show_no_beep_or_led, clear
|
||||
```
|
||||
|
||||
### Locks
|
||||
```
|
||||
SUB omnilink/lockX/name
|
||||
string Lock name
|
||||
|
||||
SUB omnilink/lockX/state
|
||||
string locked, unlocked
|
||||
|
||||
PUB omnilink/lockX/command
|
||||
string lock, unlock
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
Loading…
Reference in a new issue