- Update MQTT, add console messages and button presses

This commit is contained in:
Ryan Wagoner 2019-12-15 00:25:55 -05:00
parent c3aa88621d
commit 314ea42f64
13 changed files with 230 additions and 30 deletions

View file

@ -6,6 +6,7 @@
zone,
unit,
thermostat,
button
button,
message
}
}

View file

@ -468,5 +468,20 @@ namespace OmniLinkBridge.MQTT
ret.command_topic = button.ToTopic(Topic.command);
return ret;
}
public static string ToTopic(this clsMessage message, Topic topic)
{
return $"{Global.mqtt_prefix}/message{message.Number.ToString()}/{topic.ToString()}";
}
public static string ToState(this clsMessage message)
{
if (message.Status == enuMessageStatus.Displayed)
return "displayed";
else if (message.Status == enuMessageStatus.NotAcked)
return "displayed_not_acknowledged";
else
return "off";
}
}
}

View file

@ -0,0 +1,10 @@
namespace OmniLinkBridge.MQTT
{
enum MessageCommands
{
show,
show_no_beep,
show_no_beep_or_led,
clear
}
}

View file

@ -10,7 +10,7 @@ namespace OmniLinkBridge.MQTT
{
public class MessageProcessor
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
@ -45,9 +45,11 @@ namespace OmniLinkBridge.MQTT
ProcessThermostatReceived(OmniLink.Controller.Thermostats[id], topic, payload);
else if (type == CommandTypes.button && id > 0 && id <= OmniLink.Controller.Buttons.Count)
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);
}
private IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
private static readonly IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
{
{ AreaCommands.disarm, enuUnitCommand.SecurityOff },
{ AreaCommands.arm_home, enuUnitCommand.SecurityDay },
@ -71,7 +73,7 @@ namespace OmniLinkBridge.MQTT
}
}
private IDictionary<ZoneCommands, enuUnitCommand> ZoneMapping = new Dictionary<ZoneCommands, enuUnitCommand>
private static readonly IDictionary<ZoneCommands, enuUnitCommand> ZoneMapping = new Dictionary<ZoneCommands, enuUnitCommand>
{
{ ZoneCommands.restore, enuUnitCommand.Restore },
{ ZoneCommands.bypass, enuUnitCommand.Bypass },
@ -86,7 +88,7 @@ namespace OmniLinkBridge.MQTT
}
}
private IDictionary<UnitCommands, enuUnitCommand> UnitMapping = new Dictionary<UnitCommands, enuUnitCommand>
private static readonly IDictionary<UnitCommands, enuUnitCommand> UnitMapping = new Dictionary<UnitCommands, enuUnitCommand>
{
{ UnitCommands.OFF, enuUnitCommand.Off },
{ UnitCommands.ON, enuUnitCommand.On }
@ -181,5 +183,29 @@ namespace OmniLinkBridge.MQTT
OmniLink.SendCommand(enuUnitCommand.Button, 0, (ushort)button.Number);
}
}
private static readonly IDictionary<MessageCommands, enuUnitCommand> MessageMapping = new Dictionary<MessageCommands, enuUnitCommand>
{
{ MessageCommands.show, enuUnitCommand.ShowMsgWBeep },
{ MessageCommands.show_no_beep, enuUnitCommand.ShowMsgNoBeep },
{ MessageCommands.show_no_beep_or_led, enuUnitCommand.ShowMsgNoBeep },
{ MessageCommands.clear, enuUnitCommand.ClearMsg },
};
private void ProcessMessageReceived(clsMessage message, Topic command, string payload)
{
if (command == Topic.command && Enum.TryParse(payload, true, out MessageCommands cmd))
{
log.Debug("SetMessage: " + message.Number + " to " + cmd.ToString().Replace("_", " "));
byte par = 0;
if (cmd == MessageCommands.show_no_beep)
par = 1;
else if (cmd == MessageCommands.show_no_beep_or_led)
par = 2;
OmniLink.SendCommand(MessageMapping[cmd], par, (ushort)message.Number);
}
}
}
}

View file

@ -2,6 +2,7 @@
{
public enum Topic
{
name,
state,
command,
basic_state,

View file

@ -276,7 +276,7 @@ namespace OmniLinkBridge.Modules
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID + "','" + e.Message.Name + "','" + e.Message.StatusText() + "')");
if (Global.verbose_message)
log.Debug("MessageStatus " + e.Message.Name + ", " + e.Message.StatusText());
log.Debug("MessageStatus " + e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());
if (Global.notify_message)
Notification.Notify("Message", e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());

View file

@ -2,6 +2,10 @@
using log4net;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Protocol;
using Newtonsoft.Json;
@ -38,6 +42,8 @@ namespace OmniLinkBridge.Modules
OmniLink.OnZoneStatus += Omnilink_OnZoneStatus;
OmniLink.OnUnitStatus += Omnilink_OnUnitStatus;
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
OmniLink.OnButtonStatus += OmniLink_OnButtonStatus;
OmniLink.OnMessageStatus += OmniLink_OnMessageStatus;
MessageProcessor = new MessageProcessor(omni);
}
@ -66,7 +72,7 @@ namespace OmniLinkBridge.Modules
.Build();
MqttClient = new MqttFactory().CreateManagedMqttClient();
MqttClient.Connected += (sender, e) =>
MqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate((e) =>
{
log.Debug("Connected");
@ -83,13 +89,14 @@ namespace OmniLinkBridge.Modules
// For subsequent connections publish config immediately
if (ControllerConnected)
PublishConfig();
};
MqttClient.ConnectingFailed += (sender, e) => { log.Debug("Error connecting " + e.Exception.Message); };
MqttClient.Disconnected += (sender, e) => { log.Debug("Disconnected"); };
});
MqttClient.ConnectingFailedHandler = new ConnectingFailedHandlerDelegate((e) => log.Debug("Error connecting " + e.Exception.Message));
MqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate((e) => log.Debug("Disconnected"));
MqttClient.StartAsync(manoptions);
MqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
MqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate((e) =>
MessageProcessor.Process(e.ApplicationMessage.Topic, Encoding.UTF8.GetString(e.ApplicationMessage.Payload)));
// Subscribe to notifications for these command topics
List<Topic> toSubscribe = new List<Topic>()
@ -117,11 +124,6 @@ namespace OmniLinkBridge.Modules
MqttClient.StopAsync();
}
private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
{
MessageProcessor.Process(e.ApplicationMessage.Topic, Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
}
public void Shutdown()
{
trigger.Set();
@ -153,6 +155,7 @@ namespace OmniLinkBridge.Modules
PublishUnits();
PublishThermostats();
PublishButtons();
PublishMessages();
log.Debug("Publishing controller online");
PublishAsync($"{Global.mqtt_prefix}/status", "online");
@ -172,6 +175,7 @@ namespace OmniLinkBridge.Modules
// (configured for 1 area). To workaround ignore default properties for the first area.
if (i > 1 && area.DefaultProperties == true)
{
PublishAsync(area.ToTopic(Topic.name), null);
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config", null);
@ -186,6 +190,7 @@ namespace OmniLinkBridge.Modules
PublishAreaState(area);
PublishAsync(area.ToTopic(Topic.name), area.Name);
PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config",
JsonConvert.SerializeObject(area.ToConfig()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config",
@ -215,6 +220,16 @@ namespace OmniLinkBridge.Modules
{
clsZone zone = OmniLink.Controller.Zones[i];
if (zone.DefaultProperties == true)
{
PublishAsync(zone.ToTopic(Topic.name), null);
}
else
{
PublishZoneState(zone);
PublishAsync(zone.ToTopic(Topic.name), zone.Name);
}
if (zone.DefaultProperties == true || Global.mqtt_discovery_ignore_zones.Contains(zone.Number))
{
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null);
@ -224,8 +239,6 @@ namespace OmniLinkBridge.Modules
continue;
}
PublishZoneState(zone);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
JsonConvert.SerializeObject(zone.ToConfig()));
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
@ -248,6 +261,16 @@ namespace OmniLinkBridge.Modules
{
clsUnit unit = OmniLink.Controller.Units[i];
if (unit.DefaultProperties == true)
{
PublishAsync(unit.ToTopic(Topic.name), null);
}
else
{
PublishUnitState(unit);
PublishAsync(unit.ToTopic(Topic.name), unit.Name);
}
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
{
string type = i < 385 ? "light" : "switch";
@ -255,8 +278,6 @@ namespace OmniLinkBridge.Modules
continue;
}
PublishUnitState(unit);
if (i < 385)
PublishAsync($"{Global.mqtt_discovery_prefix}/light/{Global.mqtt_prefix}/unit{i.ToString()}/config",
JsonConvert.SerializeObject(unit.ToConfig()));
@ -276,6 +297,7 @@ namespace OmniLinkBridge.Modules
if (thermostat.DefaultProperties == true)
{
PublishAsync(thermostat.ToTopic(Topic.name), null);
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config", null);
@ -284,6 +306,7 @@ namespace OmniLinkBridge.Modules
PublishThermostatState(thermostat);
PublishAsync(thermostat.ToTopic(Topic.name), thermostat.Name);
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config",
JsonConvert.SerializeObject(thermostat.ToConfig(OmniLink.Controller.TempFormat)));
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config",
@ -303,18 +326,40 @@ namespace OmniLinkBridge.Modules
if (button.DefaultProperties == true)
{
PublishAsync(button.ToTopic(Topic.name), null);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config", null);
continue;
}
// Buttons are always off
// Buttons are off unless momentarily pressed
PublishAsync(button.ToTopic(Topic.state), "OFF");
PublishAsync(button.ToTopic(Topic.name), button.Name);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config",
JsonConvert.SerializeObject(button.ToConfig()));
}
}
private void PublishMessages()
{
log.Debug("Publishing messages");
for (ushort i = 1; i <= OmniLink.Controller.Messages.Count; i++)
{
clsMessage message = OmniLink.Controller.Messages[i];
if (message.DefaultProperties == true)
{
PublishAsync(message.ToTopic(Topic.name), null);
continue;
}
PublishMessageState(message);
PublishAsync(message.ToTopic(Topic.name), message.Name);
}
}
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
{
if (!MqttClient.IsConnected)
@ -375,6 +420,22 @@ namespace OmniLinkBridge.Modules
PublishThermostatState(e.Thermostat);
}
private async void OmniLink_OnButtonStatus(object sender, ButtonStatusEventArgs e)
{
if (!MqttClient.IsConnected)
return;
await PublishButtonState(e.Button);
}
private void OmniLink_OnMessageStatus(object sender, MessageStatusEventArgs e)
{
if (!MqttClient.IsConnected)
return;
PublishMessageState(e.Message);
}
private void PublishAreaState(clsArea area)
{
PublishAsync(area.ToTopic(Topic.state), area.ToState());
@ -415,6 +476,19 @@ namespace OmniLinkBridge.Modules
PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower());
}
private async Task PublishButtonState(clsButton button)
{
// Simulate a momentary press
await PublishAsync(button.ToTopic(Topic.state), "ON");
await Task.Delay(1000);
await PublishAsync(button.ToTopic(Topic.state), "OFF");
}
private void PublishMessageState(clsMessage message)
{
PublishAsync(message.ToTopic(Topic.state), message.ToState());
}
private Task PublishAsync(string topic, string payload)
{
return MqttClient.PublishAsync(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true);

View file

@ -30,6 +30,7 @@ namespace OmniLinkBridge.Modules
public event EventHandler<ZoneStatusEventArgs> OnZoneStatus;
public event EventHandler<ThermostatStatusEventArgs> OnThermostatStatus;
public event EventHandler<UnitStatusEventArgs> OnUnitStatus;
public event EventHandler<ButtonStatusEventArgs> OnButtonStatus;
public event EventHandler<MessageStatusEventArgs> OnMessageStatus;
public event EventHandler<SystemStatusEventArgs> OnSystemStatus;
@ -567,6 +568,12 @@ namespace OmniLinkBridge.Modules
eventargs.Value = ((int)MSG.SystemEvent).ToString() + " " + Controller.Buttons[MSG.SystemEvent].Name;
OnSystemStatus?.Invoke(this, eventargs);
OnButtonStatus?.Invoke(this, new ButtonStatusEventArgs()
{
ID = MSG.SystemEvent,
Button = Controller.Buttons[MSG.SystemEvent]
});
}
else if (MSG.SystemEvent >= 768 && MSG.SystemEvent <= 771)
{

View file

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

View file

@ -84,6 +84,7 @@
<Compile Include="MQTT\Device.cs" />
<Compile Include="MQTT\Climate.cs" />
<Compile Include="MQTT\DeviceRegistry.cs" />
<Compile Include="MQTT\MessageCommands.cs" />
<Compile Include="MQTT\MessageProcessor.cs" />
<Compile Include="MQTT\OverrideZone.cs" />
<Compile Include="MQTT\Switch.cs" />
@ -98,6 +99,7 @@
<Compile Include="Notifications\Notification.cs" />
<Compile Include="Notifications\NotificationPriority.cs" />
<Compile Include="Notifications\PushoverNotification.cs" />
<Compile Include="OmniLink\ButtonStatusEventArgs.cs" />
<Compile Include="OmniLink\IOmniLinkII.cs" />
<Compile Include="OmniLink\UnitStatusEventArgs.cs" />
<Compile Include="OmniLink\ThermostatStatusEventArgs.cs" />
@ -166,10 +168,10 @@
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="MQTTnet.Extensions.ManagedClient">
<Version>2.8.4</Version>
<Version>3.0.8</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>11.0.2</Version>
<Version>12.0.3</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View file

@ -153,6 +153,31 @@ namespace OmniLinkBridgeTest
check(1, "ON", enuUnitCommand.Button);
check(1, "on", enuUnitCommand.Button);
}
[TestMethod]
public void MessageCommand()
{
void check(ushort id, string payload, enuUnitCommand command, byte par)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/message{id}/command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = par,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(1, "show", enuUnitCommand.ShowMsgWBeep, 0);
check(1, "show_no_beep", enuUnitCommand.ShowMsgNoBeep, 1);
check(1, "show_no_beep_or_led", enuUnitCommand.ShowMsgNoBeep, 2);
check(1, "clear", enuUnitCommand.ClearMsg, 0);
check(2, "SHOW", enuUnitCommand.ShowMsgWBeep, 0);
}
}
}

View file

@ -57,10 +57,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter">
<Version>1.3.2</Version>
<Version>2.0.0</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>1.3.2</Version>
<Version>2.0.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View file

@ -132,6 +132,9 @@ systemctl start omnilinkbridge.service
### Areas
```
SUB omnilink/areaX/name
string Area name
SUB omnilink/areaX/state
string triggered, pending, armed_night, armed_night_delay, armed_home, armed_home_instant, armed_away, armed_vacation, disarmed
@ -144,6 +147,9 @@ string(insensitive) arm_home, arm_away, arm_night, disarm, arm_home_instant, arm
### Zones
```
SUB omnilink/zoneX/name
string Zone name
SUB omnilink/zoneX/state
string secure, not_ready, trouble, armed, tripped, bypassed
@ -162,6 +168,9 @@ string(insensitive) bypass, restore
### Units
```
SUB omnilink/unitX/name
string Unit name
SUB omnilink/unitX/state
PUB omnilink/unitX/command
string OFF, ON
@ -173,6 +182,9 @@ int Level from 0 to 100 percent
### Thermostats
```
SUB omnilink/thermostatX/name
string Thermostat name
SUB omnilink/thermostatX/current_operation
string idle, cool, heat
@ -209,13 +221,29 @@ string off, hold
### Buttons
```
SUB omnilink/buttonX/name
string Button name
SUB omnilink/buttonX/state
string OFF
string OFF, ON
PUB omnilink/buttonX/command
string ON
```
### Messages
```
SUB omnilink/messageX/name
string Message name
SUB omnilink/messageX/state
string off, displayed, displayed_not_acknowledged
PUB omnilink/messageX/command
string show, show_no_beep, show_no_beep_or_led, clear
```
## 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.