1.1.14 - Improve MQTT unit, output and flag capabilities

This commit is contained in:
Ryan Wagoner 2022-11-02 18:09:33 -04:00
parent a016e1cd64
commit 495ce74149
41 changed files with 388 additions and 150 deletions

View file

@ -1,5 +1,4 @@
using OmniLinkBridge.MQTT;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@ -24,39 +23,6 @@ namespace OmniLinkBridge
{
return (b & (1 << pos)) != 0;
}
public static AreaCommandCode ToCommandCode(this string payload, bool supportValidate = false)
{
string[] payloads = payload.Split(',');
int code = 0;
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)
{
return Regex.Replace(phrase, "(\\B[A-Z])", " $1");

View file

@ -55,6 +55,7 @@ namespace OmniLinkBridge
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;
public static ConcurrentDictionary<int, MQTT.OverrideUnit> mqtt_discovery_override_unit;
// Notifications
public static bool notify_area;

View file

@ -0,0 +1,65 @@
using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridge.MQTT
{
public static class Extensions
{
public static AreaCommandCode ToCommandCode(this string payload, bool supportValidate = false)
{
string[] payloads = payload.Split(',');
int code = 0;
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 UnitType ToUnitType(this clsUnit unit)
{
Global.mqtt_discovery_override_unit.TryGetValue(unit.Number, out OverrideUnit override_unit);
if (unit.Type == enuOL2UnitType.Output)
return UnitType.@switch;
if (unit.Type == enuOL2UnitType.Flag)
{
if (override_unit != null && override_unit.type == UnitType.number)
return UnitType.number;
return UnitType.@switch;
}
if (override_unit != null && override_unit.type == UnitType.@switch)
return UnitType.@switch;
return UnitType.light;
}
}
}

View file

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Alarm : Device
{

View file

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class BinarySensor : Device
{

View file

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Climate : Device
{

View file

@ -3,7 +3,7 @@ using Newtonsoft.Json.Converters;
using OmniLinkBridge.Modules;
using System.Collections.Generic;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Device
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class DeviceRegistry
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Light : Device
{

View file

@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Number : Device
{
public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? min { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int? max { get; set; }
}
}

View file

@ -1,7 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Sensor : Device
{

View file

@ -1,6 +1,6 @@
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.HomeAssistant
{
public class Switch : Device
{

View file

@ -1,6 +1,8 @@
using HAI_Shared;
using Newtonsoft.Json;
using System.Collections.Generic;
using OmniLinkBridge.MQTT.HomeAssistant;
using OmniLinkBridge.MQTT.Parser;
namespace OmniLinkBridge.MQTT
{
@ -399,6 +401,20 @@ namespace OmniLinkBridge.MQTT
return ret;
}
public static Number ToConfigNumber(this clsUnit unit)
{
Number ret = new Number
{
unique_id = $"{Global.mqtt_prefix}unit{unit.Number}number",
name = Global.mqtt_discovery_name_prefix + unit.Name,
state_topic = unit.ToTopic(Topic.flag_state),
command_topic = unit.ToTopic(Topic.flag_command),
min = 0,
max = 255
};
return ret;
}
public static string ToState(this clsUnit unit)
{
return unit.Status == 0 || unit.Status == 100 ? UnitCommands.OFF.ToString() : UnitCommands.ON.ToString();

View file

@ -1,4 +1,5 @@
using HAI_Shared;
using OmniLinkBridge.MQTT.Parser;
using OmniLinkBridge.OmniLink;
using Serilog;
using System;
@ -167,10 +168,16 @@ namespace OmniLinkBridge.MQTT
OmniLink.SendCommand(UnitMapping[cmd], 0, (ushort)unit.Number);
}
}
else if (command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
else if (unit.Type == enuOL2UnitType.Flag &&
command == Topic.flag_command && int.TryParse(payload, out int flagValue))
{
log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
OmniLink.SendCommand(enuUnitCommand.Set, BitConverter.GetBytes(flagValue)[0], (ushort)unit.Number);
}
else if (unit.Type != enuOL2UnitType.Output &&
command == Topic.brightness_command && int.TryParse(payload, out int unitValue))
{
log.Debug("SetUnit: {id} to {value}%", unit.Number, payload);
OmniLink.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unitValue)[0], (ushort)unit.Number);
// Force status change instead of waiting on controller to update
@ -178,10 +185,10 @@ namespace OmniLinkBridge.MQTT
// which will cause light to go to 100% brightness
unit.Status = (byte)(100 + unitValue);
}
else if (command == Topic.scene_command && char.TryParse(payload, out char scene))
else if (unit.Type != enuOL2UnitType.Output &&
command == Topic.scene_command && char.TryParse(payload, out char scene))
{
log.Debug("SetUnit: {id} to {value}", unit.Number, payload);
OmniLink.SendCommand(enuUnitCommand.Compose, (byte)(scene - 63), (ushort)unit.Number);
}
}

View file

@ -1,12 +0,0 @@
using Newtonsoft.Json;
namespace OmniLinkBridge.MQTT
{
public class Number : Device
{
public string command_topic { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string icon { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace OmniLinkBridge.MQTT
{
public class OverrideUnit
{
public UnitType type { get; set; }
}
}

View file

@ -1,4 +1,6 @@
namespace OmniLinkBridge.MQTT
using OmniLinkBridge.MQTT.HomeAssistant;
namespace OmniLinkBridge.MQTT
{
public class OverrideZone
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum AlarmCommands
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum AreaCommands
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum CommandTypes
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum MessageCommands
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
public enum Topic
{
@ -11,6 +11,8 @@
json_state,
brightness_state,
brightness_command,
flag_state,
flag_command,
scene_state,
scene_command,
current_operation,

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum UnitCommands
{

View file

@ -1,4 +1,4 @@
namespace OmniLinkBridge.MQTT
namespace OmniLinkBridge.MQTT.Parser
{
enum ZoneCommands
{

View file

@ -0,0 +1,9 @@
namespace OmniLinkBridge.MQTT
{
public enum UnitType
{
@switch,
light,
number
}
}

View file

@ -2,6 +2,7 @@
using OmniLinkBridge.Notifications;
using OmniLinkBridge.OmniLink;
using Serilog;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Data;
@ -125,15 +126,18 @@ namespace OmniLinkBridge.Modules
private void Omnilink_OnConnect(object sender, EventArgs e)
{
if (Global.verbose_area)
ushort areaUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Areas.Count; i++)
{
for (ushort i = 1; i <= omnilink.Controller.Areas.Count; i++)
clsArea area = omnilink.Controller.Areas[i];
if (i > 1 && area.DefaultProperties == true)
continue;
areaUsage++;
if (Global.verbose_area)
{
clsArea area = omnilink.Controller.Areas[i];
if (i > 1 && area.DefaultProperties == true)
continue;
string status = area.ModeText();
if (area.ExitTimer > 0)
@ -146,21 +150,59 @@ namespace OmniLinkBridge.Modules
}
}
if (Global.verbose_zone)
ushort zoneUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Zones.Count; i++)
{
for (ushort i = 1; i <= omnilink.Controller.Zones.Count; i++)
clsZone zone = omnilink.Controller.Zones[i];
if (zone.DefaultProperties == true)
continue;
zoneUsage++;
if (Global.verbose_zone)
{
clsZone zone = omnilink.Controller.Zones[i];
if (zone.DefaultProperties == true)
continue;
if (zone.IsTemperatureZone())
log.Verbose("Initial ZoneStatus {id} {name}, Temp: {temp}", i, zone.Name, zone.TempText());
else
log.Verbose("Initial ZoneStatus {id} {name}, Status: {status}", i, zone.Name, zone.StatusText());
}
}
ushort unitUsage = 0, outputUsage = 0, flagUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Units.Count; i++)
{
clsUnit unit = omnilink.Controller.Units[i];
if (unit.DefaultProperties == true)
continue;
if (unit.Type == enuOL2UnitType.Output)
outputUsage++;
else if (unit.Type == enuOL2UnitType.Flag)
flagUsage++;
else
unitUsage++;
if (Global.verbose_unit)
log.Verbose("Initial UnitStatus {id} {name}, Status: {status}", i, unit.Name, unit.StatusText);
}
ushort thermostatUsage = 0;
for (ushort i = 1; i <= omnilink.Controller.Thermostats.Count; i++)
{
clsThermostat thermostat = omnilink.Controller.Thermostats[i];
if (thermostat.DefaultProperties == true)
continue;
thermostatUsage++;
}
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);
}
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)

View file

@ -9,10 +9,13 @@ using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Protocol;
using Newtonsoft.Json;
using OmniLinkBridge.MQTT;
using OmniLinkBridge.MQTT.HomeAssistant;
using OmniLinkBridge.MQTT.Parser;
using OmniLinkBridge.OmniLink;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
@ -108,6 +111,7 @@ namespace OmniLinkBridge.Modules
Topic.command,
Topic.alarm_command,
Topic.brightness_command,
Topic.flag_command,
Topic.scene_command,
Topic.temperature_heat_command,
Topic.temperature_cool_command,
@ -216,8 +220,8 @@ namespace OmniLinkBridge.Modules
{
clsArea area = OmniLink.Controller.Areas[i];
// PC Access doesn't let you customize the area name for the Omni LTe or Omni IIe
// (configured for 1 area). To workaround ignore default properties for the first area.
// PC Access doesn't let you customize the area name when configured for one area.
// Ignore default properties for the first area.
if (i > 1 && area.DefaultProperties == true)
{
PublishAsync(area.ToTopic(Topic.name), null);
@ -308,6 +312,7 @@ namespace OmniLinkBridge.Modules
for (ushort i = 1; i <= OmniLink.Controller.Units.Count; i++)
{
clsUnit unit = OmniLink.Controller.Units[i];
UnitType unitType = unit.ToUnitType();
if (unit.DefaultProperties == true)
{
@ -321,17 +326,26 @@ namespace OmniLinkBridge.Modules
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
{
string type = i < 385 ? "light" : "switch";
PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/{Global.mqtt_prefix}/unit{i}/config", null);
foreach(UnitType entry in Enum.GetValues(typeof(UnitType)))
PublishAsync($"{Global.mqtt_discovery_prefix}/{entry}/{Global.mqtt_prefix}/unit{i}/config", null);
continue;
}
if (i < 385)
PublishAsync($"{Global.mqtt_discovery_prefix}/light/{Global.mqtt_prefix}/unit{i}/config",
JsonConvert.SerializeObject(unit.ToConfig()));
else
PublishAsync($"{Global.mqtt_discovery_prefix}/switch/{Global.mqtt_prefix}/unit{i}/config",
foreach (UnitType entry in Enum.GetValues(typeof(UnitType)).Cast<UnitType>().Where(x => x != unitType))
PublishAsync($"{Global.mqtt_discovery_prefix}/{entry}/{Global.mqtt_prefix}/unit{i}/config", null);
log.Verbose("Publishing {type} {id} {name} as {unitType}", "units", i, unit.Name, unitType);
if (unitType == UnitType.@switch)
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
JsonConvert.SerializeObject(unit.ToConfigSwitch()));
else if (unitType == UnitType.light)
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
JsonConvert.SerializeObject(unit.ToConfig()));
else if (unitType == UnitType.number)
PublishAsync($"{Global.mqtt_discovery_prefix}/{unitType}/{Global.mqtt_prefix}/unit{i}/config",
JsonConvert.SerializeObject(unit.ToConfigNumber()));
}
}
@ -537,7 +551,11 @@ namespace OmniLinkBridge.Modules
{
PublishAsync(unit.ToTopic(Topic.state), unit.ToState());
if (unit.Number < 385)
if (unit.Type == enuOL2UnitType.Flag)
{
PublishAsync(unit.ToTopic(Topic.flag_state), ((ushort)unit.Status).ToString());
}
else if(unit.Type != enuOL2UnitType.Output)
{
PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString());
PublishAsync(unit.ToTopic(Topic.scene_state), unit.ToSceneState());

View file

@ -51,7 +51,7 @@ namespace OmniLinkBridge.Modules
Controller.Connection.NetworkAddress = address;
Controller.Connection.NetworkPort = (ushort)port;
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(String.Concat(key1, key2));
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(string.Concat(key1, key2));
Controller.PreferredNetworkProtocol = clsHAC.enuPreferredNetworkProtocol.TCP;
Controller.Connection.ConnectionType = enuOmniLinkConnectionType.Network_TCP;
@ -316,7 +316,8 @@ namespace OmniLinkBridge.Modules
Controller.Zones.CopyProperties(MSG);
if (Controller.Zones[MSG.ObjectNumber].IsTemperatureZone() || Controller.Zones[MSG.ObjectNumber].IsHumidityZone())
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus);
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus);
break;
case enuObjectType.Thermostat:
@ -327,7 +328,8 @@ namespace OmniLinkBridge.Modules
else
tstats[MSG.ObjectNumber] = DateTime.MinValue;
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
log.Debug("Added thermostat to watch list {thermostatName}",
Controller.Thermostats[MSG.ObjectNumber].Name);
break;
@ -714,7 +716,8 @@ namespace OmniLinkBridge.Modules
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
{
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus);
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection,
enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus);
if (Global.verbose_thermostat_timer)
log.Debug("Polling status for Thermostat {thermostatName}",

View file

@ -74,14 +74,15 @@ namespace OmniLinkBridge.Modules
// Extract the 2 digit prefix to use when parsing the time
int year = DateTime.Now.Year / 100;
time = new DateTime((int)MSG.Year + (year * 100), (int)MSG.Month, (int)MSG.Day, (int)MSG.Hour, (int)MSG.Minute, (int)MSG.Second);
time = new DateTime(MSG.Year + (year * 100), MSG.Month, MSG.Day, MSG.Hour, MSG.Minute, MSG.Second);
}
catch
{
log.Warning("Controller time could not be parsed");
DateTime now = DateTime.Now;
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection,
(byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
return;
@ -92,10 +93,11 @@ namespace OmniLinkBridge.Modules
if (adj > Global.time_drift)
{
log.Warning("Controller time {controllerTime} out of sync by {driftSeconds} seconds",
time.ToString("MM/dd/yyyy HH:mm:ss"), adj);
time.ToString("MM/dd/yyyy HH:mm:ss"), adj);
DateTime now = DateTime.Now;
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection,
(byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
}
}

View file

@ -82,28 +82,31 @@
<ItemGroup>
<Compile Include="CoreServer.cs" />
<Compile Include="Modules\TimeSyncModule.cs" />
<Compile Include="MQTT\Alarm.cs" />
<Compile Include="MQTT\AlarmCommands.cs" />
<Compile Include="MQTT\HomeAssistant\Alarm.cs" />
<Compile Include="MQTT\OverrideUnit.cs" />
<Compile Include="MQTT\Parser\AlarmCommands.cs" />
<Compile Include="MQTT\AreaCommandCode.cs" />
<Compile Include="MQTT\AreaCommands.cs" />
<Compile Include="MQTT\Parser\AreaCommands.cs" />
<Compile Include="MQTT\AreaState.cs" />
<Compile Include="MQTT\Availability.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\MessageCommands.cs" />
<Compile Include="MQTT\HomeAssistant\BinarySensor.cs" />
<Compile Include="MQTT\Parser\CommandTypes.cs" />
<Compile Include="MQTT\HomeAssistant\Device.cs" />
<Compile Include="MQTT\HomeAssistant\Climate.cs" />
<Compile Include="MQTT\HomeAssistant\DeviceRegistry.cs" />
<Compile Include="MQTT\Extensions.cs" />
<Compile Include="MQTT\Parser\MessageCommands.cs" />
<Compile Include="MQTT\MessageProcessor.cs" />
<Compile Include="MQTT\Number.cs" />
<Compile Include="MQTT\HomeAssistant\Number.cs" />
<Compile Include="MQTT\OverrideZone.cs" />
<Compile Include="MQTT\Switch.cs" />
<Compile Include="MQTT\Light.cs" />
<Compile Include="MQTT\HomeAssistant\Switch.cs" />
<Compile Include="MQTT\HomeAssistant\Light.cs" />
<Compile Include="MQTT\MappingExtensions.cs" />
<Compile Include="MQTT\Sensor.cs" />
<Compile Include="MQTT\Topic.cs" />
<Compile Include="MQTT\UnitCommands.cs" />
<Compile Include="MQTT\ZoneCommands.cs" />
<Compile Include="MQTT\HomeAssistant\Sensor.cs" />
<Compile Include="MQTT\Parser\Topic.cs" />
<Compile Include="MQTT\Parser\UnitCommands.cs" />
<Compile Include="MQTT\Parser\ZoneCommands.cs" />
<Compile Include="MQTT\UnitType.cs" />
<Compile Include="Notifications\EmailNotification.cs" />
<Compile Include="Notifications\INotification.cs" />
<Compile Include="Notifications\Notification.cs" />

View file

@ -4,4 +4,7 @@
<StartArguments>
</StartArguments>
</PropertyGroup>
<PropertyGroup>
<ProjectView>ProjectFiles</ProjectView>
</PropertyGroup>
</Project>

View file

@ -49,13 +49,24 @@ mqtt_prefix = omnilink
mqtt_discovery_prefix = homeassistant
# Prefix for Home Assistant entity names
mqtt_discovery_name_prefix =
# Specify a range of numbers like 1,2,3,5-10
# Skip publishing Home Assistant discovery topics for zones/units
# Specify a range of numbers 1,2,3,5-10
mqtt_discovery_ignore_zones =
mqtt_discovery_ignore_units =
# Require Home Assistant to prompt for user code when arming/disarming area
# Specify a range of numbers 1,2,3,5-10
mqtt_discovery_area_code_required =
# device_class must be battery, door, garage_door, gas, moisture, motion, problem, smoke, or window
# Override the zone Home Assistant binary sensor device_class
# device_class: must be battery, cold, door, garage_door, gas,
# heat, moisture, motion, problem, safety, smoke, or window
#mqtt_discovery_override_zone = id=5;device_class=garage_door
#mqtt_discovery_override_zone = id=6;device_class=garage_door
# Override the unit Home Assistant device type
# type:
# Units (LTe 1-32, IIe 1-64, Pro 1-256) light or switch, defaults to light
# Flags (LTe 41-88, IIe 73-128, Pro 393-511) switch or number, defaults to switch
#mqtt_discovery_override_unit = id=1;type=switch
#mqtt_discovery_override_unit = id=395;type=number
# Notifications (yes/no)
# Always sent for area alarms and critical system events

View file

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.13.0")]
[assembly: AssemblyFileVersion("1.1.13.0")]
[assembly: AssemblyVersion("1.1.14.0")]
[assembly: AssemblyFileVersion("1.1.14.0")]

View file

@ -7,7 +7,7 @@ using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Reflection;
using System.Threading;
using ha = OmniLinkBridge.MQTT.HomeAssistant;
namespace OmniLinkBridge
{
@ -86,6 +86,7 @@ namespace OmniLinkBridge
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");
Global.mqtt_discovery_override_unit = settings.LoadOverrideUnit<MQTT.OverrideUnit>("mqtt_discovery_override_unit");
}
// Notifications
@ -157,7 +158,7 @@ namespace OmniLinkBridge
}
else if (override_zone is MQTT.OverrideZone mqtt_zone)
{
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out MQTT.BinarySensor.DeviceClass attrib_device_class))
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out ha.BinarySensor.DeviceClass attrib_device_class))
throw new Exception("Missing or invalid device_class attribute");
mqtt_zone.device_class = attrib_device_class;
@ -175,6 +176,50 @@ namespace OmniLinkBridge
}
}
private static ConcurrentDictionary<int, T> LoadOverrideUnit<T>(this NameValueCollection settings, string section) where T : new()
{
try
{
ConcurrentDictionary<int, T> ret = new ConcurrentDictionary<int, T>();
string value = settings.CheckEnv(section);
if (string.IsNullOrEmpty(value))
return ret;
string[] ids = value.Split(',');
for (int i = 0; i < ids.Length; i++)
{
Dictionary<string, string> attributes = ids[i].TrimEnd(new char[] { ';' }).Split(';')
.Select(s => s.Split('='))
.ToDictionary(a => a[0].Trim(), a => a[1].Trim(), StringComparer.InvariantCultureIgnoreCase);
if (!attributes.ContainsKey("id") || !int.TryParse(attributes["id"], out int attrib_id))
throw new Exception("Missing or invalid id attribute");
T override_unit = new T();
if (override_unit is MQTT.OverrideUnit mqtt_unit)
{
if (!attributes.ContainsKey("type") || !Enum.TryParse(attributes["type"], out MQTT.UnitType attrib_type))
throw new Exception("Missing or invalid type attribute");
mqtt_unit.type = attrib_type;
}
ret.TryAdd(attrib_id, override_unit);
}
return ret;
}
catch (Exception ex)
{
log.Error(ex, "Invalid override unit specified for {section}", section);
throw;
}
}
private static string ValidateHasValue(this NameValueCollection settings, string section)
{
string value = settings.CheckEnv(section);

View file

@ -1,9 +1,7 @@
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.MQTT;
using System.Collections.Generic;
namespace OmniLinkBridgeTest
{

View file

@ -1,10 +1,9 @@
using System;
using System.Text;
using System.Collections.Generic;
using HAI_Shared;
using HAI_Shared;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.MQTT;
using OmniLinkBridgeTest.Mock;
using System.Collections.Concurrent;
namespace OmniLinkBridgeTest
{
@ -19,6 +18,8 @@ namespace OmniLinkBridgeTest
{
omniLink = new MockOmniLinkII();
messageProcessor = new MessageProcessor(omniLink);
omniLink.Controller.Units[395].Type = enuOL2UnitType.Flag;
}
[TestMethod]
@ -134,6 +135,28 @@ namespace OmniLinkBridgeTest
check(2, "on", enuUnitCommand.On);
}
[TestMethod]
public void UnitFlagCommand()
{
void check(ushort id, string payload, enuUnitCommand command, int value)
{
SendCommandEventArgs actual = null;
omniLink.OnSendCommand += (sender, e) => { actual = e; };
messageProcessor.Process($"omnilink/unit{id}/flag_command", payload);
SendCommandEventArgs expected = new SendCommandEventArgs()
{
Cmd = command,
Par = (byte)value,
Pr2 = id
};
Assert.AreEqual(expected, actual);
}
check(395, "0", enuUnitCommand.Set, 0);
check(395, "1", enuUnitCommand.Set, 1);
check(395, "255", enuUnitCommand.Set, 255);
}
[TestMethod]
public void UnitLevelCommand()
{

View file

@ -1,10 +1,6 @@
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
{

View file

@ -1,9 +1,5 @@
using HAI_Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OmniLinkBridgeTest.Mock
{

View file

@ -1,9 +1,6 @@
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge.Notifications;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using OmniLinkBridge.Notifications;
using System.Net.Mail;
namespace OmniLinkBridgeTest

View file

@ -1,9 +1,8 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniLinkBridge;
using System;
using System.Collections.Generic;
using ha = OmniLinkBridge.MQTT.HomeAssistant;
namespace OmniLinkBridgeTest
{
@ -156,8 +155,11 @@ namespace OmniLinkBridgeTest
"mqtt_discovery_name_prefix = mynameprefix",
"mqtt_discovery_ignore_zones = 1,2-3,4",
"mqtt_discovery_ignore_units = 2-5,7",
"mqtt_discovery_area_code_required = 1",
"mqtt_discovery_override_zone = id=5;device_class=garage_door",
"mqtt_discovery_override_zone = id=7;device_class=motion",
"mqtt_discovery_override_unit = id=1;type=switch",
"mqtt_discovery_override_unit = id=395;type=light",
});
Settings.LoadSettings(lines.ToArray());
Assert.AreEqual("myuser", Global.mqtt_username);
@ -167,11 +169,12 @@ namespace OmniLinkBridgeTest
Assert.AreEqual("mynameprefix ", Global.mqtt_discovery_name_prefix);
Assert.IsTrue(Global.mqtt_discovery_ignore_zones.SetEquals(new int[] { 1, 2, 3, 4 }));
Assert.IsTrue(Global.mqtt_discovery_ignore_units.SetEquals(new int[] { 2, 3, 4, 5, 7 }));
Assert.IsTrue(Global.mqtt_discovery_area_code_required.SetEquals(new int[] { 1 }));
Dictionary<int, OmniLinkBridge.MQTT.OverrideZone> override_zone = new Dictionary<int, OmniLinkBridge.MQTT.OverrideZone>()
{
{ 5, new OmniLinkBridge.MQTT.OverrideZone { device_class = OmniLinkBridge.MQTT.BinarySensor.DeviceClass.garage_door }},
{ 7, new OmniLinkBridge.MQTT.OverrideZone { device_class = OmniLinkBridge.MQTT.BinarySensor.DeviceClass.motion }}
{ 5, new OmniLinkBridge.MQTT.OverrideZone { device_class = ha.BinarySensor.DeviceClass.garage_door }},
{ 7, new OmniLinkBridge.MQTT.OverrideZone { device_class = ha.BinarySensor.DeviceClass.motion }}
};
Assert.AreEqual(override_zone.Count, Global.mqtt_discovery_override_zone.Count);
@ -180,6 +183,19 @@ namespace OmniLinkBridgeTest
Global.mqtt_discovery_override_zone.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideZone value);
Assert.AreEqual(override_zone[pair.Key].device_class, value.device_class);
}
Dictionary<int, OmniLinkBridge.MQTT.OverrideUnit> override_unit = new Dictionary<int, OmniLinkBridge.MQTT.OverrideUnit>()
{
{ 1, new OmniLinkBridge.MQTT.OverrideUnit { type = OmniLinkBridge.MQTT.UnitType.@switch }},
{ 395, new OmniLinkBridge.MQTT.OverrideUnit { type = OmniLinkBridge.MQTT.UnitType.light }}
};
Assert.AreEqual(override_unit.Count, Global.mqtt_discovery_override_unit.Count);
foreach (KeyValuePair<int, OmniLinkBridge.MQTT.OverrideUnit> pair in override_unit)
{
Global.mqtt_discovery_override_unit.TryGetValue(pair.Key, out OmniLinkBridge.MQTT.OverrideUnit value);
Assert.AreEqual(override_unit[pair.Key].type, value.type);
}
}
[TestMethod]

View file

@ -9,7 +9,7 @@ You can use docker to build an image from git or download the [binary here](http
- .NET Framework 4.7.2 (or Mono equivalent)
## Operation
OmniLink Bridge is divided into the following modules and configurable settings. Configuration settings can also be set as environment variables by using their name in uppercase. Refer to [OmniLinkBridge.ini](https://github.com/excaliburpartners/OmniLinkBridge/blob/master/OmniLinkBridge/OmniLinkBridge.ini) for specifics.
OmniLink Bridge is divided into the following modules and configurable settings. Configuration settings can also be set as environment variables by using their name in uppercase. Refer to [OmniLinkBridge.ini](OmniLinkBridge/OmniLinkBridge.ini) for specifics.
- OmniLinkII: controller_
- Maintains connection to the OmniLink controller
@ -232,6 +232,10 @@ SUB omnilink/unitX/brightness_state
PUB omnilink/unitX/brightness_command
int Level from 0 to 100 percent
SUB omnilink/unitX/flag_state
PUB omnilink/unitX/flag_command
int Level from 0 to 255
SUB omnilink/unitX/scene_state
PUB omnilink/unitX/scene_command
string A-L