- Refactor MQTT parser and add unit tests

This commit is contained in:
Ryan Wagoner 2019-12-13 23:14:20 -05:00
parent dbad9ea3ce
commit cb24322bef
26 changed files with 630 additions and 415 deletions

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class Alarm : Device
{

View file

@ -0,0 +1,14 @@
namespace OmniLinkBridge.MQTT
{
enum AreaCommands
{
disarm,
arm_home,
arm_away,
arm_night,
// The below aren't supported by Home Assistant
arm_home_instant,
arm_night_delay,
arm_vacation
}
}

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class AreaState
{

View file

@ -1,10 +1,5 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
{

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT
{

View file

@ -0,0 +1,11 @@
namespace OmniLinkBridge.MQTT
{
enum CommandTypes
{
area,
zone,
unit,
thermostat,
button
}
}

View file

@ -1,10 +1,5 @@
using Newtonsoft.Json;
using OmniLinkBridge.Modules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
{

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class DeviceRegistry
{

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class Light : Device
{

View file

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HAI_Shared;
using HAI_Shared;
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT
@ -376,7 +371,7 @@ namespace OmniLinkBridge.MQTT
public static string ToState(this clsUnit unit)
{
return unit.Status == 0 || unit.Status == 100 ? "OFF" : "ON";
return unit.Status == 0 || unit.Status == 100 ? UnitCommands.OFF.ToString() : UnitCommands.ON.ToString();
}
public static int ToBrightnessState(this clsUnit unit)

View file

@ -0,0 +1,185 @@
using HAI_Shared;
using log4net;
using OmniLinkBridge.OmniLink;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
namespace OmniLinkBridge.MQTT
{
public class MessageProcessor
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
private IOmniLinkII OmniLink { get; set; }
public MessageProcessor(IOmniLinkII omni)
{
OmniLink = omni;
}
public void Process(string messageTopic, string payload)
{
Match match = regexTopic.Match(messageTopic);
if (!match.Success)
return;
if (!Enum.TryParse(match.Groups[1].Value, true, out CommandTypes type)
|| !Enum.TryParse(match.Groups[3].Value, true, out Topic topic)
|| !ushort.TryParse(match.Groups[2].Value, out ushort id))
return;
log.Debug($"Received: Type: {type.ToString()}, Id: {id}, Command: {topic.ToString()}, Value: {payload}");
if (type == CommandTypes.area && id <= OmniLink.Controller.Areas.Count)
ProcessAreaReceived(OmniLink.Controller.Areas[id], topic, payload);
else if (type == CommandTypes.zone && id > 0 && id <= OmniLink.Controller.Zones.Count)
ProcessZoneReceived(OmniLink.Controller.Zones[id], topic, payload);
else if (type == CommandTypes.unit && id > 0 && id <= OmniLink.Controller.Units.Count)
ProcessUnitReceived(OmniLink.Controller.Units[id], topic, payload);
else if (type == CommandTypes.thermostat && id > 0 && id <= OmniLink.Controller.Thermostats.Count)
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);
}
private IDictionary<AreaCommands, enuUnitCommand> AreaMapping = new Dictionary<AreaCommands, enuUnitCommand>
{
{ AreaCommands.disarm, enuUnitCommand.SecurityOff },
{ AreaCommands.arm_home, enuUnitCommand.SecurityDay },
{ AreaCommands.arm_away, enuUnitCommand.SecurityAway },
{ AreaCommands.arm_night, enuUnitCommand.SecurityNight },
// The below aren't supported by Home Assistant
{ AreaCommands.arm_home_instant, enuUnitCommand.SecurityDyi },
{ AreaCommands.arm_night_delay, enuUnitCommand.SecurityNtd },
{ AreaCommands.arm_vacation, enuUnitCommand.SecurityVac },
};
private void ProcessAreaReceived(clsArea area, Topic command, string payload)
{
if (command == Topic.command && Enum.TryParse(payload, true, out AreaCommands cmd))
{
if (area.Number == 0)
log.Debug("SetArea: 0 implies all areas will be changed");
log.Debug("SetArea: " + area.Number + " to " + cmd.ToString().Replace("arm_", "").Replace("_", " "));
OmniLink.SendCommand(AreaMapping[cmd], 0, (ushort)area.Number);
}
}
private IDictionary<ZoneCommands, enuUnitCommand> ZoneMapping = new Dictionary<ZoneCommands, enuUnitCommand>
{
{ ZoneCommands.restore, enuUnitCommand.Restore },
{ ZoneCommands.bypass, enuUnitCommand.Bypass },
};
private void ProcessZoneReceived(clsZone zone, Topic command, string payload)
{
if (command == Topic.command && Enum.TryParse(payload, true, out ZoneCommands cmd))
{
log.Debug("SetZone: " + zone.Number + " to " + payload);
OmniLink.SendCommand(ZoneMapping[cmd], 0, (ushort)zone.Number);
}
}
private IDictionary<UnitCommands, enuUnitCommand> UnitMapping = new Dictionary<UnitCommands, enuUnitCommand>
{
{ UnitCommands.OFF, enuUnitCommand.Off },
{ UnitCommands.ON, enuUnitCommand.On }
};
private void ProcessUnitReceived(clsUnit unit, Topic command, string payload)
{
if (command == Topic.command && Enum.TryParse(payload, true, out UnitCommands cmd))
{
if (string.Compare(unit.ToState(), cmd.ToString()) != 0)
{
log.Debug("SetUnit: " + unit.Number + " to " + cmd.ToString());
OmniLink.SendCommand(UnitMapping[cmd], 0, (ushort)unit.Number);
}
}
else if (command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
{
log.Debug("SetUnit: " + unit.Number + " to " + payload + "%");
OmniLink.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unitValue)[0], (ushort)unit.Number);
// Force status change instead of waiting on controller to update
// Home Assistant sends brightness immediately followed by ON,
// which will cause light to go to 100% brightness
unit.Status = (byte)(100 + unitValue);
}
}
private void ProcessThermostatReceived(clsThermostat thermostat, Topic command, string payload)
{
if (command == Topic.temperature_heat_command && double.TryParse(payload, out double tempLow))
{
string tempUnit = "C";
if (OmniLink.Controller.TempFormat == enuTempFormat.Fahrenheit)
{
tempLow = tempLow.ToCelsius();
tempUnit = "F";
}
int temp = tempLow.ToOmniTemp();
log.Debug("SetThermostatHeatSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
OmniLink.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
}
else if (command == Topic.temperature_cool_command && double.TryParse(payload, out double tempHigh))
{
string tempUnit = "C";
if (OmniLink.Controller.TempFormat == enuTempFormat.Fahrenheit)
{
tempHigh = tempHigh.ToCelsius();
tempUnit = "F";
}
int temp = tempHigh.ToOmniTemp();
log.Debug("SetThermostatCoolSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
OmniLink.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
}
else if (command == Topic.humidify_command && double.TryParse(payload, out double humidify))
{
// Humidity is reported where Fahrenheit temperatures 0-100 correspond to 0-100% relative humidity
int level = humidify.ToCelsius().ToOmniTemp();
log.Debug("SetThermostatHumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
OmniLink.SendCommand(enuUnitCommand.SetHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
}
else if (command == Topic.dehumidify_command && double.TryParse(payload, out double dehumidify))
{
int level = dehumidify.ToCelsius().ToOmniTemp();
log.Debug("SetThermostatDehumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
OmniLink.SendCommand(enuUnitCommand.SetDeHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
}
else if (command == Topic.mode_command && Enum.TryParse(payload, true, out enuThermostatMode mode))
{
log.Debug("SetThermostatMode: " + thermostat.Number + " to " + payload);
OmniLink.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes((int)mode)[0], (ushort)thermostat.Number);
}
else if (command == Topic.fan_mode_command && Enum.TryParse(payload, true, out enuThermostatFanMode fanMode))
{
log.Debug("SetThermostatFanMode: " + thermostat.Number + " to " + payload);
OmniLink.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes((int)fanMode)[0], (ushort)thermostat.Number);
}
else if (command == Topic.hold_command && Enum.TryParse(payload, true, out enuThermostatHoldMode holdMode))
{
log.Debug("SetThermostatHold: " + thermostat.Number + " to " + payload);
OmniLink.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes((int)holdMode)[0], (ushort)thermostat.Number);
}
}
private void ProcessButtonReceived(clsButton button, Topic command, string payload)
{
if (command == Topic.command && Enum.TryParse(payload, true, out UnitCommands cmd) && cmd == UnitCommands.ON)
{
log.Debug("PushButton: " + button.Number);
OmniLink.SendCommand(enuUnitCommand.Button, 0, (ushort)button.Number);
}
}
}
}

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class OverrideZone
{

View file

@ -1,10 +1,5 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
{

View file

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT
{
public class Switch : Device
{

View file

@ -0,0 +1,29 @@
namespace OmniLinkBridge.MQTT
{
public enum Topic
{
state,
command,
basic_state,
json_state,
brightness_state,
brightness_command,
current_operation,
current_temperature,
current_humidity,
temperature_heat_state,
temperature_heat_command,
temperature_cool_state,
temperature_cool_command,
humidify_state,
humidify_command,
dehumidify_state,
dehumidify_command,
mode_state,
mode_command,
fan_mode_state,
fan_mode_command,
hold_state,
hold_command
}
}

View file

@ -1,59 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
{
public class Topic
{
public string Value { get; private set; }
private Topic(string value)
{
Value = value;
}
public override string ToString()
{
return Value;
}
public static Topic state { get { return new Topic("state"); } }
public static Topic command { get { return new Topic("command"); } }
public static Topic basic_state { get { return new Topic("basic_state"); } }
public static Topic json_state { get { return new Topic("json_state"); } }
public static Topic brightness_state { get { return new Topic("brightness_state"); } }
public static Topic brightness_command { get { return new Topic("brightness_command"); } }
public static Topic current_operation { get { return new Topic("current_operation"); } }
public static Topic current_temperature { get { return new Topic("current_temperature"); } }
public static Topic current_humidity { get { return new Topic("current_humidity"); } }
public static Topic temperature_heat_state { get { return new Topic("temperature_heat_state"); } }
public static Topic temperature_heat_command { get { return new Topic("temperature_heat_command"); } }
public static Topic temperature_cool_state { get { return new Topic("temperature_cool_state"); } }
public static Topic temperature_cool_command { get { return new Topic("temperature_cool_command"); } }
public static Topic humidify_state { get { return new Topic("humidify_state"); } }
public static Topic humidify_command { get { return new Topic("humidify_command"); } }
public static Topic dehumidify_state { get { return new Topic("dehumidify_state"); } }
public static Topic dehumidify_command { get { return new Topic("dehumidify_command"); } }
public static Topic mode_state { get { return new Topic("mode_state"); } }
public static Topic mode_command { get { return new Topic("mode_command"); } }
public static Topic fan_mode_state { get { return new Topic("fan_mode_state"); } }
public static Topic fan_mode_command { get { return new Topic("fan_mode_command"); } }
public static Topic hold_state { get { return new Topic("hold_state"); } }
public static Topic hold_command { get { return new Topic("hold_command"); } }
}
}

View file

@ -0,0 +1,8 @@
namespace OmniLinkBridge.MQTT
{
enum UnitCommands
{
OFF,
ON
}
}

View file

@ -0,0 +1,8 @@
namespace OmniLinkBridge.MQTT
{
enum ZoneCommands
{
restore,
bypass,
}
}

View file

@ -8,10 +8,11 @@ using Newtonsoft.Json;
using OmniLinkBridge.MQTT;
using OmniLinkBridge.OmniLink;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace OmniLinkBridge.Modules
{
@ -25,7 +26,7 @@ namespace OmniLinkBridge.Modules
private IManagedMqttClient MqttClient { get; set; }
private bool ControllerConnected { get; set; }
private Regex regexTopic = new Regex(Global.mqtt_prefix + "/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
private MessageProcessor MessageProcessor { get; set; }
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
@ -38,6 +39,8 @@ namespace OmniLinkBridge.Modules
OmniLink.OnZoneStatus += Omnilink_OnZoneStatus;
OmniLink.OnUnitStatus += Omnilink_OnUnitStatus;
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
MessageProcessor = new MessageProcessor(omni);
}
public void Startup()
@ -79,7 +82,7 @@ namespace OmniLinkBridge.Modules
// For the initial connection wait for the controller connected event to publish config
// For subsequent connections publish config immediately
if(ControllerConnected)
if (ControllerConnected)
PublishConfig();
};
MqttClient.ConnectingFailed += (sender, e) => { log.Debug("Error connecting " + e.Exception.Message); };
@ -89,218 +92,35 @@ namespace OmniLinkBridge.Modules
MqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.brightness_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.temperature_heat_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.temperature_cool_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.humidify_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.dehumidify_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.mode_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.fan_mode_command}").Build());
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{Topic.hold_command}").Build());
// Subscribe to notifications for these command topics
List<Topic> toSubscribe = new List<Topic>()
{
Topic.command,
Topic.brightness_command,
Topic.temperature_heat_command,
Topic.temperature_cool_command,
Topic.humidify_command,
Topic.dehumidify_command,
Topic.mode_command,
Topic.fan_mode_command,
Topic.hold_command
};
toSubscribe.ForEach((command) => MqttClient.SubscribeAsync(
new TopicFilterBuilder().WithTopic($"{Global.mqtt_prefix}/+/{command.ToString()}").Build()));
// Wait until shutdown
trigger.WaitOne();
log.Debug("Publishing controller offline");
MqttClient.PublishAsync($"{Global.mqtt_prefix}/status", "offline", MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_prefix}/status", "offline");
MqttClient.StopAsync();
}
private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Match match = regexTopic.Match(e.ApplicationMessage.Topic);
if (!match.Success)
return;
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
log.Debug($"Received: Type: {match.Groups[1].Value}, Id: {match.Groups[2].Value}, Command: {match.Groups[3].Value}, Value: {payload}");
if (match.Groups[1].Value == "area" && ushort.TryParse(match.Groups[2].Value, out ushort areaId) &&
areaId <= OmniLink.Controller.Areas.Count)
{
if(areaId == 0)
log.Debug("SetArea: 0 implies all areas will be changed");
ProcessAreaReceived(OmniLink.Controller.Areas[areaId], match.Groups[3].Value, payload);
}
if (match.Groups[1].Value == "zone" && ushort.TryParse(match.Groups[2].Value, out ushort zoneId) &&
zoneId > 0 && zoneId <= OmniLink.Controller.Zones.Count)
{
ProcessZoneReceived(OmniLink.Controller.Zones[zoneId], match.Groups[3].Value, payload);
}
else if (match.Groups[1].Value == "unit" && ushort.TryParse(match.Groups[2].Value, out ushort unitId) &&
unitId > 0 && unitId <= OmniLink.Controller.Units.Count)
{
ProcessUnitReceived(OmniLink.Controller.Units[unitId], match.Groups[3].Value, payload);
}
else if (match.Groups[1].Value == "thermostat" && ushort.TryParse(match.Groups[2].Value, out ushort thermostatId) &&
thermostatId > 0 && thermostatId <= OmniLink.Controller.Thermostats.Count)
{
ProcessThermostatReceived(OmniLink.Controller.Thermostats[thermostatId], match.Groups[3].Value, payload);
}
else if (match.Groups[1].Value == "button" && ushort.TryParse(match.Groups[2].Value, out ushort buttonId) &&
buttonId > 0 && buttonId <= OmniLink.Controller.Buttons.Count)
{
ProcessButtonReceived(OmniLink.Controller.Buttons[buttonId], match.Groups[3].Value, payload);
}
}
private void ProcessAreaReceived(clsArea area, string command, string payload)
{
if (string.Compare(command, Topic.command.ToString()) == 0)
{
if(string.Compare(payload, "arm_home", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to home");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityDay, 0, (ushort)area.Number);
}
else if (string.Compare(payload, "arm_away", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to away");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityAway, 0, (ushort)area.Number);
}
else if (string.Compare(payload, "arm_night", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to night");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityNight, 0, (ushort)area.Number);
}
else if (string.Compare(payload, "disarm", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to disarm");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityOff, 0, (ushort)area.Number);
}
// The below aren't supported by Home Assistant
else if (string.Compare(payload, "arm_home_instant", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to home instant");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityDyi, 0, (ushort)area.Number);
}
else if (string.Compare(payload, "arm_night_delay", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to night delay");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityNtd, 0, (ushort)area.Number);
}
else if (string.Compare(payload, "arm_vacation", true) == 0)
{
log.Debug("SetArea: " + area.Number + " to vacation");
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityVac, 0, (ushort)area.Number);
}
}
}
private void ProcessZoneReceived(clsZone zone, string command, string payload)
{
if (string.Compare(command, Topic.command.ToString()) == 0)
{
if (string.Compare(payload, "bypass", true) == 0)
{
log.Debug("SetZone: " + zone.Number + " to " + payload);
OmniLink.Controller.SendCommand(enuUnitCommand.Bypass, 0, (ushort)zone.Number);
}
else if (string.Compare(payload, "restore", true) == 0)
{
log.Debug("SetZone: " + zone.Number + " to " + payload);
OmniLink.Controller.SendCommand(enuUnitCommand.Restore, 0, (ushort)zone.Number);
}
}
}
private void ProcessUnitReceived(clsUnit unit, string command, string payload)
{
if (string.Compare(command, Topic.command.ToString()) == 0 && (payload == "ON" || payload == "OFF"))
{
if (unit.ToState() != payload)
{
log.Debug("SetUnit: " + unit.Number + " to " + payload);
if (payload == "ON")
OmniLink.Controller.SendCommand(enuUnitCommand.On, 0, (ushort)unit.Number);
else
OmniLink.Controller.SendCommand(enuUnitCommand.Off, 0, (ushort)unit.Number);
}
}
else if (string.Compare(command, Topic.brightness_command.ToString()) == 0 && Int32.TryParse(payload, out int unitValue))
{
log.Debug("SetUnit: " + unit.Number + " to " + payload + "%");
OmniLink.Controller.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unitValue)[0], (ushort)unit.Number);
// Force status change instead of waiting on controller to update
// Home Assistant sends brightness immediately followed by ON,
// which will cause light to go to 100% brightness
unit.Status = (byte)(100 + unitValue);
}
}
private void ProcessThermostatReceived(clsThermostat thermostat, string command, string payload)
{
if (string.Compare(command, Topic.temperature_heat_command.ToString()) == 0 && double.TryParse(payload, out double tempLow))
{
string tempUnit = "C";
if (OmniLink.Controller.TempFormat == enuTempFormat.Fahrenheit)
{
tempLow = tempLow.ToCelsius();
tempUnit = "F";
}
int temp = tempLow.ToOmniTemp();
log.Debug("SetThermostatHeatSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
OmniLink.Controller.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.temperature_cool_command.ToString()) == 0 && double.TryParse(payload, out double tempHigh))
{
string tempUnit = "C";
if (OmniLink.Controller.TempFormat == enuTempFormat.Fahrenheit)
{
tempHigh = tempHigh.ToCelsius();
tempUnit = "F";
}
int temp = tempHigh.ToOmniTemp();
log.Debug("SetThermostatCoolSetpoint: " + thermostat.Number + " to " + payload + tempUnit + "(" + temp + ")");
OmniLink.Controller.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.humidify_command.ToString()) == 0 && double.TryParse(payload, out double humidify))
{
// Humidity is reported where Fahrenheit temperatures 0-100 correspond to 0-100% relative humidity
int level = humidify.ToCelsius().ToOmniTemp();
log.Debug("SetThermostatHumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
OmniLink.Controller.SendCommand(enuUnitCommand.SetHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.dehumidify_command.ToString()) == 0 && double.TryParse(payload, out double dehumidify))
{
int level = dehumidify.ToCelsius().ToOmniTemp();
log.Debug("SetThermostatDehumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
OmniLink.Controller.SendCommand(enuUnitCommand.SetDeHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.mode_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatMode mode))
{
log.Debug("SetThermostatMode: " + thermostat.Number + " to " + payload);
OmniLink.Controller.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes((int)mode)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.fan_mode_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatFanMode fanMode))
{
log.Debug("SetThermostatFanMode: " + thermostat.Number + " to " + payload);
OmniLink.Controller.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes((int)fanMode)[0], (ushort)thermostat.Number);
}
else if (string.Compare(command, Topic.hold_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatHoldMode holdMode))
{
log.Debug("SetThermostatHold: " + thermostat.Number + " to " + payload);
OmniLink.Controller.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes((int)holdMode)[0], (ushort)thermostat.Number);
}
}
private void ProcessButtonReceived(clsButton button, string command, string payload)
{
if (string.Compare(command, Topic.command.ToString()) == 0 && payload == "ON")
{
log.Debug("PushButton: " + button.Number);
OmniLink.Controller.SendCommand(enuUnitCommand.Button, 0, (ushort)button.Number);
}
MessageProcessor.Process(e.ApplicationMessage.Topic, Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
}
public void Shutdown()
@ -323,7 +143,7 @@ namespace OmniLinkBridge.Modules
if (MqttClient.IsConnected)
{
log.Debug("Publishing controller offline");
MqttClient.PublishAsync($"{Global.mqtt_prefix}/status", "offline", MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_prefix}/status", "offline");
}
}
@ -336,7 +156,9 @@ namespace OmniLinkBridge.Modules
PublishButtons();
log.Debug("Publishing controller online");
MqttClient.PublishAsync($"{Global.mqtt_prefix}/status", "online", MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_prefix}/status", "online");
PublishAsync($"{Global.mqtt_prefix}/model", OmniLink.Controller.GetModelText());
PublishAsync($"{Global.mqtt_prefix}/version", OmniLink.Controller.GetVersionText());
}
private void PublishAreas()
@ -351,38 +173,38 @@ namespace OmniLinkBridge.Modules
// (configured for 1 area). To workaround ignore default properties for the first area.
if (i > 1 && area.DefaultProperties == true)
{
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
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);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config", null);
continue;
}
PublishAreaState(area);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/{Global.mqtt_prefix}/area{i.ToString()}/config",
JsonConvert.SerializeObject(area.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}burglary/config",
JsonConvert.SerializeObject(area.ToConfigBurglary()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config",
JsonConvert.SerializeObject(area.ToConfigFire()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config",
JsonConvert.SerializeObject(area.ToConfigGas()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config",
JsonConvert.SerializeObject(area.ToConfigAux()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config",
JsonConvert.SerializeObject(area.ToConfigFreeze()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config",
JsonConvert.SerializeObject(area.ToConfigWater()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config",
JsonConvert.SerializeObject(area.ToConfigDuress()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config",
JsonConvert.SerializeObject(area.ToConfigTemp()), MqttQualityOfServiceLevel.AtMostOnce, true);
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",
JsonConvert.SerializeObject(area.ToConfigBurglary()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}fire/config",
JsonConvert.SerializeObject(area.ToConfigFire()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}gas/config",
JsonConvert.SerializeObject(area.ToConfigGas()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}aux/config",
JsonConvert.SerializeObject(area.ToConfigAux()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}freeze/config",
JsonConvert.SerializeObject(area.ToConfigFreeze()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}water/config",
JsonConvert.SerializeObject(area.ToConfigWater()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}duress/config",
JsonConvert.SerializeObject(area.ToConfigDuress()));
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/area{i.ToString()}temp/config",
JsonConvert.SerializeObject(area.ToConfigTemp()));
}
}
@ -396,26 +218,26 @@ namespace OmniLinkBridge.Modules
if (zone.DefaultProperties == true || Global.mqtt_discovery_ignore_zones.Contains(zone.Number))
{
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config", null);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config", null);
continue;
}
PublishZoneState(zone);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
JsonConvert.SerializeObject(zone.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}/config",
JsonConvert.SerializeObject(zone.ToConfigSensor()), MqttQualityOfServiceLevel.AtMostOnce, true);
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",
JsonConvert.SerializeObject(zone.ToConfigSensor()));
if (zone.IsTemperatureZone())
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config",
JsonConvert.SerializeObject(zone.ToConfigTemp(OmniLink.Controller.TempFormat)), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}temp/config",
JsonConvert.SerializeObject(zone.ToConfigTemp(OmniLink.Controller.TempFormat)));
else if (zone.IsHumidityZone())
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config",
JsonConvert.SerializeObject(zone.ToConfigHumidity()), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/zone{i.ToString()}humidity/config",
JsonConvert.SerializeObject(zone.ToConfigHumidity()));
}
}
@ -430,18 +252,18 @@ namespace OmniLinkBridge.Modules
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
{
string type = i < 385 ? "light" : "switch";
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/{Global.mqtt_prefix}/unit{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/{Global.mqtt_prefix}/unit{i.ToString()}/config", null);
continue;
}
PublishUnitState(unit);
if(i < 385)
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/light/{Global.mqtt_prefix}/unit{i.ToString()}/config",
JsonConvert.SerializeObject(unit.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/light/{Global.mqtt_prefix}/unit{i.ToString()}/config",
JsonConvert.SerializeObject(unit.ToConfig()));
else
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/unit{i.ToString()}/config",
JsonConvert.SerializeObject(unit.ToConfigSwitch()), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/unit{i.ToString()}/config",
JsonConvert.SerializeObject(unit.ToConfigSwitch()));
}
}
@ -455,20 +277,20 @@ namespace OmniLinkBridge.Modules
if (thermostat.DefaultProperties == true)
{
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
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);
continue;
}
PublishThermostatState(thermostat);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i.ToString()}/config",
JsonConvert.SerializeObject(thermostat.ToConfig(OmniLink.Controller.TempFormat)), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}temp/config",
JsonConvert.SerializeObject(thermostat.ToConfigTemp(OmniLink.Controller.TempFormat)), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config",
JsonConvert.SerializeObject(thermostat.ToConfigHumidity()), MqttQualityOfServiceLevel.AtMostOnce, true);
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",
JsonConvert.SerializeObject(thermostat.ToConfigTemp(OmniLink.Controller.TempFormat)));
PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/{Global.mqtt_prefix}/thermostat{i.ToString()}humidity/config",
JsonConvert.SerializeObject(thermostat.ToConfigHumidity()));
}
}
@ -482,15 +304,15 @@ namespace OmniLinkBridge.Modules
if (button.DefaultProperties == true)
{
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config", null);
continue;
}
// Buttons are always off
MqttClient.PublishAsync(button.ToTopic(Topic.state), "OFF", MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(button.ToTopic(Topic.state), "OFF");
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config",
JsonConvert.SerializeObject(button.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/button{i.ToString()}/config",
JsonConvert.SerializeObject(button.ToConfig()));
}
}
@ -508,7 +330,8 @@ namespace OmniLinkBridge.Modules
clsZone zone = OmniLink.Controller.Zones[i];
if (zone.DefaultProperties == false && zone.Area == e.Area.Number)
OmniLink.Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(OmniLink.Controller.Connection, enuObjectType.Zone, i, i), HandleRequestZoneStatus);
OmniLink.Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(
OmniLink.Controller.Connection, enuObjectType.Zone, i, i), HandleRequestZoneStatus);
}
}
@ -523,7 +346,7 @@ namespace OmniLinkBridge.Modules
{
clsZone zone = OmniLink.Controller.Zones[MSG.ObjectNumber(i)];
zone.CopyExtendedStatus(MSG, i);
MqttClient.PublishAsync(zone.ToTopic(Topic.state), zone.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(zone.ToTopic(Topic.state), zone.ToState());
}
}
@ -556,42 +379,47 @@ namespace OmniLinkBridge.Modules
private void PublishAreaState(clsArea area)
{
MqttClient.PublishAsync(area.ToTopic(Topic.state), area.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(area.ToTopic(Topic.basic_state), area.ToBasicState(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(area.ToTopic(Topic.json_state), area.ToJsonState(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(area.ToTopic(Topic.state), area.ToState());
PublishAsync(area.ToTopic(Topic.basic_state), area.ToBasicState());
PublishAsync(area.ToTopic(Topic.json_state), area.ToJsonState());
}
private void PublishZoneState(clsZone zone)
{
MqttClient.PublishAsync(zone.ToTopic(Topic.state), zone.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(zone.ToTopic(Topic.basic_state), zone.ToBasicState(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(zone.ToTopic(Topic.state), zone.ToState());
PublishAsync(zone.ToTopic(Topic.basic_state), zone.ToBasicState());
if(zone.IsTemperatureZone())
MqttClient.PublishAsync(zone.ToTopic(Topic.current_temperature), zone.TempText(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(zone.ToTopic(Topic.current_temperature), zone.TempText());
else if (zone.IsHumidityZone())
MqttClient.PublishAsync(zone.ToTopic(Topic.current_humidity), zone.TempText(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(zone.ToTopic(Topic.current_humidity), zone.TempText());
}
private void PublishUnitState(clsUnit unit)
{
MqttClient.PublishAsync(unit.ToTopic(Topic.state), unit.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(unit.ToTopic(Topic.state), unit.ToState());
if(unit.Number < 385)
MqttClient.PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString());
}
private void PublishThermostatState(clsThermostat thermostat)
{
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_operation), thermostat.ToOperationState(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_temperature), thermostat.TempText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_humidity), thermostat.HumidityText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.temperature_heat_state), thermostat.HeatSetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.temperature_cool_state), thermostat.CoolSetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.humidify_state), thermostat.HumidifySetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.dehumidify_state), thermostat.DehumidifySetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.mode_state), thermostat.ModeText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.fan_mode_state), thermostat.FanModeText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
MqttClient.PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
PublishAsync(thermostat.ToTopic(Topic.current_operation), thermostat.ToOperationState());
PublishAsync(thermostat.ToTopic(Topic.current_temperature), thermostat.TempText());
PublishAsync(thermostat.ToTopic(Topic.current_humidity), thermostat.HumidityText());
PublishAsync(thermostat.ToTopic(Topic.temperature_heat_state), thermostat.HeatSetpointText());
PublishAsync(thermostat.ToTopic(Topic.temperature_cool_state), thermostat.CoolSetpointText());
PublishAsync(thermostat.ToTopic(Topic.humidify_state), thermostat.HumidifySetpointText());
PublishAsync(thermostat.ToTopic(Topic.dehumidify_state), thermostat.DehumidifySetpointText());
PublishAsync(thermostat.ToTopic(Topic.mode_state), thermostat.ModeText().ToLower());
PublishAsync(thermostat.ToTopic(Topic.fan_mode_state), thermostat.FanModeText().ToLower());
PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower());
}
private Task PublishAsync(string topic, string payload)
{
return MqttClient.PublishAsync(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true);
}
}
}

View file

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace OmniLinkBridge.Modules
{
public class OmniLinkII : IModule
public class OmniLinkII : IModule, IOmniLinkII
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
@ -39,7 +39,7 @@ namespace OmniLinkBridge.Modules
public OmniLinkII(string address, int port, string key1, string key2)
{
Controller = new clsHAC();
Controller.Connection.NetworkAddress = address;
Controller.Connection.NetworkPort = (ushort)port;
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(String.Concat(key1, key2));
@ -73,6 +73,11 @@ namespace OmniLinkBridge.Modules
trigger.Set();
}
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2)
{
return Controller.SendCommand(Cmd, Par, Pr2);
}
#region Connection
private void Connect()
{

View file

@ -0,0 +1,11 @@
using HAI_Shared;
namespace OmniLinkBridge.OmniLink
{
public interface IOmniLinkII
{
clsHAC Controller { get; }
bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2);
}
}

View file

@ -77,22 +77,28 @@
<Compile Include="CoreServer.cs" />
<Compile Include="Modules\TimeSyncModule.cs" />
<Compile Include="MQTT\Alarm.cs" />
<Compile Include="MQTT\AreaCommands.cs" />
<Compile Include="MQTT\AreaState.cs" />
<Compile Include="MQTT\BinarySensor.cs" />
<Compile Include="MQTT\CommandTypes.cs" />
<Compile Include="MQTT\Device.cs" />
<Compile Include="MQTT\Climate.cs" />
<Compile Include="MQTT\DeviceRegistry.cs" />
<Compile Include="MQTT\MessageProcessor.cs" />
<Compile Include="MQTT\OverrideZone.cs" />
<Compile Include="MQTT\Switch.cs" />
<Compile Include="MQTT\Light.cs" />
<Compile Include="MQTT\MappingExtensions.cs" />
<Compile Include="MQTT\Sensor.cs" />
<Compile Include="MQTT\Topics.cs" />
<Compile Include="MQTT\Topic.cs" />
<Compile Include="MQTT\UnitCommands.cs" />
<Compile Include="MQTT\ZoneCommands.cs" />
<Compile Include="Notifications\EmailNotification.cs" />
<Compile Include="Notifications\INotification.cs" />
<Compile Include="Notifications\Notification.cs" />
<Compile Include="Notifications\NotificationPriority.cs" />
<Compile Include="Notifications\PushoverNotification.cs" />
<Compile Include="OmniLink\IOmniLinkII.cs" />
<Compile Include="OmniLink\UnitStatusEventArgs.cs" />
<Compile Include="OmniLink\ThermostatStatusEventArgs.cs" />
<Compile Include="OmniLink\MessageStatusEventArgs.cs" />

View file

@ -0,0 +1,159 @@
using System;
using System.Text;
using System.Collections.Generic;
using HAI_Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge.MQTT;
using OmniLinkBridgeTest.Mock;
namespace OmniLinkBridgeTest
{
[TestClass]
public class MQTTTest
{
MockOmniLinkII omniLink;
MessageProcessor messageProcessor;
[TestInitialize]
public void Initialize()
{
omniLink = new MockOmniLinkII();
messageProcessor = new MessageProcessor(omniLink);
}
[TestMethod]
public void AreaCommandInvalid()
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
// Invalid command
messageProcessor.Process($"omnilink/area1/command", "disarmed");
Assert.IsNull(actual);
// Out of range
messageProcessor.Process($"omnilink/area9/command", "disarm");
Assert.IsNull(actual);
}
[TestMethod]
public void AreaCommand()
{
void check(ushort id, string payload, enuUnitCommand command)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/area{id}/command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = 0,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
// First area standard format
check(1, "disarm", enuUnitCommand.SecurityOff);
check(1, "arm_home", enuUnitCommand.SecurityDay);
check(1, "arm_away", enuUnitCommand.SecurityAway);
check(1, "arm_night", enuUnitCommand.SecurityNight);
check(1, "arm_home_instant", enuUnitCommand.SecurityDyi);
check(1, "arm_night_delay", enuUnitCommand.SecurityNtd);
check(1, "arm_vacation", enuUnitCommand.SecurityVac);
// Last area with case check
check(8, "DISARM", enuUnitCommand.SecurityOff);
}
[TestMethod]
public void ZoneCommand()
{
void check(ushort id, string payload, enuUnitCommand command)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/zone{id}/command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = 0,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(1, "bypass", enuUnitCommand.Bypass);
check(1, "restore", enuUnitCommand.Restore);
check(2, "BYPASS", enuUnitCommand.Bypass);
}
[TestMethod]
public void UnitCommand()
{
void check(ushort id, string payload, enuUnitCommand command)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/unit{id}/command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = 0,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(1, "ON", enuUnitCommand.On);
omniLink.Controller.Units[1].Status = 1;
check(1, "OFF", enuUnitCommand.Off);
check(2, "on", enuUnitCommand.On);
}
[TestMethod]
public void UnitLevelCommand()
{
void check(ushort id, string payload, enuUnitCommand command, int level)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/unit{id}/brightness_command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = (byte)level,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(1, "50", enuUnitCommand.Level, 50);
}
[TestMethod]
public void ButtonCommand()
{
void check(ushort id, string payload, enuUnitCommand command)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/button{id}/command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = 0,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(1, "ON", enuUnitCommand.Button);
check(1, "on", enuUnitCommand.Button);
}
}
}

View file

@ -0,0 +1,30 @@
using HAI_Shared;
using OmniLinkBridge.OmniLink;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridgeTest.Mock
{
class MockOmniLinkII : IOmniLinkII
{
public clsHAC Controller { get; private set; }
public event EventHandler<SendCommandEventArgs> OnSendCommand;
public MockOmniLinkII()
{
Controller = new clsHAC();
Controller.Model = enuModel.OMNI_PRO_II;
Controller.TempFormat = enuTempFormat.Fahrenheit;
}
public bool SendCommand(enuUnitCommand Cmd, byte Par, ushort Pr2)
{
OnSendCommand?.Invoke(null, new SendCommandEventArgs() { Cmd = Cmd, Par = Par, Pr2 = Pr2 });
return true;
}
}
}

View file

@ -0,0 +1,33 @@
using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridgeTest.Mock
{
public class SendCommandEventArgs : EventArgs
{
public enuUnitCommand Cmd;
public byte Par;
public ushort Pr2;
public override bool Equals(object other)
{
var toCompareWith = other as SendCommandEventArgs;
if (toCompareWith == null)
return false;
return this.Cmd == toCompareWith.Cmd &&
this.Par == toCompareWith.Par &&
this.Pr2 == toCompareWith.Pr2;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View file

@ -39,12 +39,19 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="HAI.Controller, Version=3.11.4.17, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\OmniLinkBridge\HAI.Controller.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyTestHarness.cs" />
<Compile Include="ExtensionTest.cs" />
<Compile Include="Mock\MockOmniLinkII.cs" />
<Compile Include="Mock\SendCommandEventArgs.cs" />
<Compile Include="MQTTTest.cs" />
<Compile Include="NotificationTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>