mirror of
https://github.com/excaliburpartners/OmniLinkBridge
synced 2024-12-22 10:42:24 +00:00
Implement MQTT thermostat offline status
This commit is contained in:
parent
1ce5e3dab9
commit
a016e1cd64
7
OmniLinkBridge/MQTT/Availability.cs
Normal file
7
OmniLinkBridge/MQTT/Availability.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace OmniLinkBridge.MQTT
|
||||||
|
{
|
||||||
|
public class Availability
|
||||||
|
{
|
||||||
|
public string topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ namespace OmniLinkBridge.MQTT
|
||||||
{
|
{
|
||||||
public class Climate : Device
|
public class Climate : Device
|
||||||
{
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
|
||||||
public string action_topic { get; set; }
|
public string action_topic { get; set; }
|
||||||
public string current_temperature_topic { get; set; }
|
public string current_temperature_topic { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
using OmniLinkBridge.Modules;
|
using OmniLinkBridge.Modules;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OmniLinkBridge.MQTT
|
namespace OmniLinkBridge.MQTT
|
||||||
{
|
{
|
||||||
public class Device
|
public class Device
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public enum AvailabilityMode
|
||||||
|
{
|
||||||
|
all,
|
||||||
|
any,
|
||||||
|
latest
|
||||||
|
}
|
||||||
|
|
||||||
public string unique_id { get; set; }
|
public string unique_id { get; set; }
|
||||||
|
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
|
@ -12,8 +22,15 @@ namespace OmniLinkBridge.MQTT
|
||||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string state_topic { get; set; }
|
public string state_topic { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string availability_topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
public string availability_topic { get; set; } = $"{Global.mqtt_prefix}/status";
|
||||||
|
|
||||||
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public List<Availability> availability { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public AvailabilityMode? availability_mode { get; set; }
|
||||||
|
|
||||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public DeviceRegistry device { get; set; } = MQTTModule.MqttDeviceRegistry;
|
public DeviceRegistry device { get; set; } = MQTTModule.MqttDeviceRegistry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -484,6 +484,17 @@ namespace OmniLinkBridge.MQTT
|
||||||
{
|
{
|
||||||
Climate ret = new Climate
|
Climate ret = new Climate
|
||||||
{
|
{
|
||||||
|
unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}",
|
||||||
|
name = Global.mqtt_discovery_name_prefix + thermostat.Name,
|
||||||
|
|
||||||
|
availability_topic = null,
|
||||||
|
availability_mode = Device.AvailabilityMode.all,
|
||||||
|
availability = new List<Availability>()
|
||||||
|
{
|
||||||
|
new Availability(),
|
||||||
|
new Availability() { topic = thermostat.ToTopic(Topic.status) }
|
||||||
|
},
|
||||||
|
|
||||||
modes = thermostat.Type switch
|
modes = thermostat.Type switch
|
||||||
{
|
{
|
||||||
enuThermostatType.AutoHeatCool => new List<string>(new string[] { "auto", "off", "cool", "heat" }),
|
enuThermostatType.AutoHeatCool => new List<string>(new string[] { "auto", "off", "cool", "heat" }),
|
||||||
|
@ -491,7 +502,25 @@ namespace OmniLinkBridge.MQTT
|
||||||
enuThermostatType.HeatOnly => new List<string>(new string[] { "off", "heat" }),
|
enuThermostatType.HeatOnly => new List<string>(new string[] { "off", "heat" }),
|
||||||
enuThermostatType.CoolOnly => new List<string>(new string[] { "off", "cool" }),
|
enuThermostatType.CoolOnly => new List<string>(new string[] { "off", "cool" }),
|
||||||
_ => new List<string>(new string[] { "off" }),
|
_ => new List<string>(new string[] { "off" }),
|
||||||
}
|
},
|
||||||
|
|
||||||
|
action_topic = thermostat.ToTopic(Topic.current_operation),
|
||||||
|
current_temperature_topic = thermostat.ToTopic(Topic.current_temperature),
|
||||||
|
|
||||||
|
temperature_low_state_topic = thermostat.ToTopic(Topic.temperature_heat_state),
|
||||||
|
temperature_low_command_topic = thermostat.ToTopic(Topic.temperature_heat_command),
|
||||||
|
|
||||||
|
temperature_high_state_topic = thermostat.ToTopic(Topic.temperature_cool_state),
|
||||||
|
temperature_high_command_topic = thermostat.ToTopic(Topic.temperature_cool_command),
|
||||||
|
|
||||||
|
mode_state_topic = thermostat.ToTopic(Topic.mode_basic_state),
|
||||||
|
mode_command_topic = thermostat.ToTopic(Topic.mode_command),
|
||||||
|
|
||||||
|
fan_mode_state_topic = thermostat.ToTopic(Topic.fan_mode_state),
|
||||||
|
fan_mode_command_topic = thermostat.ToTopic(Topic.fan_mode_command),
|
||||||
|
|
||||||
|
preset_mode_state_topic = thermostat.ToTopic(Topic.hold_state),
|
||||||
|
preset_mode_command_topic = thermostat.ToTopic(Topic.hold_command)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (format == enuTempFormat.Celsius)
|
if (format == enuTempFormat.Celsius)
|
||||||
|
@ -500,26 +529,6 @@ namespace OmniLinkBridge.MQTT
|
||||||
ret.max_temp = "35";
|
ret.max_temp = "35";
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.unique_id = $"{Global.mqtt_prefix}thermostat{thermostat.Number}";
|
|
||||||
ret.name = Global.mqtt_discovery_name_prefix + thermostat.Name;
|
|
||||||
|
|
||||||
ret.action_topic = thermostat.ToTopic(Topic.current_operation);
|
|
||||||
ret.current_temperature_topic = thermostat.ToTopic(Topic.current_temperature);
|
|
||||||
|
|
||||||
ret.temperature_low_state_topic = thermostat.ToTopic(Topic.temperature_heat_state);
|
|
||||||
ret.temperature_low_command_topic = thermostat.ToTopic(Topic.temperature_heat_command);
|
|
||||||
|
|
||||||
ret.temperature_high_state_topic = thermostat.ToTopic(Topic.temperature_cool_state);
|
|
||||||
ret.temperature_high_command_topic = thermostat.ToTopic(Topic.temperature_cool_command);
|
|
||||||
|
|
||||||
ret.mode_state_topic = thermostat.ToTopic(Topic.mode_basic_state);
|
|
||||||
ret.mode_command_topic = thermostat.ToTopic(Topic.mode_command);
|
|
||||||
|
|
||||||
ret.fan_mode_state_topic = thermostat.ToTopic(Topic.fan_mode_state);
|
|
||||||
ret.fan_mode_command_topic = thermostat.ToTopic(Topic.fan_mode_command);
|
|
||||||
|
|
||||||
ret.preset_mode_state_topic = thermostat.ToTopic(Topic.hold_state);
|
|
||||||
ret.preset_mode_command_topic = thermostat.ToTopic(Topic.hold_command);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
public enum Topic
|
public enum Topic
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
status,
|
||||||
state,
|
state,
|
||||||
command,
|
command,
|
||||||
alarm_command,
|
alarm_command,
|
||||||
|
|
|
@ -246,6 +246,10 @@ namespace OmniLinkBridge.Modules
|
||||||
humidity + "','" + humidify + "','" + dehumidify + "','" +
|
humidity + "','" + humidify + "','" + dehumidify + "','" +
|
||||||
e.Thermostat.ModeText() + "','" + e.Thermostat.FanModeText() + "','" + e.Thermostat.HoldStatusText() + "')");
|
e.Thermostat.ModeText() + "','" + e.Thermostat.FanModeText() + "','" + e.Thermostat.HoldStatusText() + "')");
|
||||||
|
|
||||||
|
if (e.Offline)
|
||||||
|
log.Warning("Unknown temp for Thermostat {thermostatName}, verify thermostat is online",
|
||||||
|
e.Thermostat.Name);
|
||||||
|
|
||||||
// Ignore events fired by thermostat polling
|
// Ignore events fired by thermostat polling
|
||||||
if (!e.EventTimer && Global.verbose_thermostat)
|
if (!e.EventTimer && Global.verbose_thermostat)
|
||||||
log.Verbose("ThermostatStatus {id} {name}, Status: {temp} {status}, " +
|
log.Verbose("ThermostatStatus {id} {name}, Status: {temp} {status}, " +
|
||||||
|
|
|
@ -153,7 +153,7 @@ namespace OmniLinkBridge.Modules
|
||||||
private void PublishControllerStatus(string status)
|
private void PublishControllerStatus(string status)
|
||||||
{
|
{
|
||||||
log.Information("Publishing controller {status}", status);
|
log.Information("Publishing controller {status}", status);
|
||||||
PublishAsync($"{Global.mqtt_prefix}/status", status);
|
PublishAsync($"{Global.mqtt_prefix}/{Topic.status}", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PublishConfig()
|
private void PublishConfig()
|
||||||
|
@ -357,6 +357,7 @@ namespace OmniLinkBridge.Modules
|
||||||
PublishThermostatState(thermostat);
|
PublishThermostatState(thermostat);
|
||||||
|
|
||||||
PublishAsync(thermostat.ToTopic(Topic.name), thermostat.Name);
|
PublishAsync(thermostat.ToTopic(Topic.name), thermostat.Name);
|
||||||
|
PublishAsync(thermostat.ToTopic(Topic.status), ONLINE);
|
||||||
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i}/config",
|
PublishAsync($"{Global.mqtt_discovery_prefix}/climate/{Global.mqtt_prefix}/thermostat{i}/config",
|
||||||
JsonConvert.SerializeObject(thermostat.ToConfig(OmniLink.Controller.TempFormat)));
|
JsonConvert.SerializeObject(thermostat.ToConfig(OmniLink.Controller.TempFormat)));
|
||||||
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}humidify/config",
|
PublishAsync($"{Global.mqtt_discovery_prefix}/number/{Global.mqtt_prefix}/thermostat{i}humidify/config",
|
||||||
|
@ -470,8 +471,17 @@ namespace OmniLinkBridge.Modules
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ignore events fired by thermostat polling
|
// Ignore events fired by thermostat polling
|
||||||
if (!e.EventTimer)
|
if (e.EventTimer)
|
||||||
PublishThermostatState(e.Thermostat);
|
return;
|
||||||
|
|
||||||
|
if (e.Offline)
|
||||||
|
{
|
||||||
|
PublishAsync(e.Thermostat.ToTopic(Topic.status), OFFLINE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishAsync(e.Thermostat.ToTopic(Topic.status), ONLINE);
|
||||||
|
PublishThermostatState(e.Thermostat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OmniLink_OnButtonStatus(object sender, ButtonStatusEventArgs e)
|
private async void OmniLink_OnButtonStatus(object sender, ButtonStatusEventArgs e)
|
||||||
|
|
|
@ -636,19 +636,13 @@ namespace OmniLinkBridge.Modules
|
||||||
{
|
{
|
||||||
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||||
|
|
||||||
// Don't fire event when invalid temperature of 0 is sometimes received
|
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||||
if (Controller.Thermostats[MSG.ObjectNumber(i)].Temp > 0)
|
|
||||||
{
|
{
|
||||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
ID = MSG.ObjectNumber(i),
|
||||||
{
|
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
|
||||||
ID = MSG.ObjectNumber(i),
|
Offline = Controller.Thermostats[MSG.ObjectNumber(i)].Temp == 0,
|
||||||
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
|
EventTimer = false
|
||||||
EventTimer = false
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (Global.verbose_thermostat_timer)
|
|
||||||
log.Debug("Ignoring unsolicited unknown temp for Thermostat {thermostatName}",
|
|
||||||
Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
|
||||||
|
|
||||||
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
|
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
|
||||||
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
|
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
|
||||||
|
@ -732,19 +726,13 @@ namespace OmniLinkBridge.Modules
|
||||||
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
||||||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
|
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
|
||||||
{
|
{
|
||||||
// Don't fire event when invalid temperature of 0 is sometimes received
|
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||||
if (Controller.Thermostats[tstat.Key].Temp > 0)
|
|
||||||
{
|
{
|
||||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
ID = tstat.Key,
|
||||||
{
|
Thermostat = Controller.Thermostats[tstat.Key],
|
||||||
ID = tstat.Key,
|
Offline = Controller.Thermostats[tstat.Key].Temp == 0,
|
||||||
Thermostat = Controller.Thermostats[tstat.Key],
|
EventTimer = true
|
||||||
EventTimer = true
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (Global.verbose_thermostat_timer)
|
|
||||||
log.Warning("Ignoring unknown temp for Thermostat {thermostatName}",
|
|
||||||
Controller.Thermostats[tstat.Key].Name);
|
|
||||||
}
|
}
|
||||||
else if (Global.verbose_thermostat_timer)
|
else if (Global.verbose_thermostat_timer)
|
||||||
log.Warning("Not logging out of date status for Thermostat {thermostatName}",
|
log.Warning("Not logging out of date status for Thermostat {thermostatName}",
|
||||||
|
|
|
@ -8,6 +8,11 @@ namespace OmniLinkBridge.OmniLink
|
||||||
public ushort ID { get; set; }
|
public ushort ID { get; set; }
|
||||||
public clsThermostat Thermostat { get; set; }
|
public clsThermostat Thermostat { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true when thermostat is offline, indicated by a temperature of 0
|
||||||
|
/// </summary>
|
||||||
|
public bool Offline { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set to true when fired by thermostat polling
|
/// Set to true when fired by thermostat polling
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
<Compile Include="MQTT\AreaCommandCode.cs" />
|
<Compile Include="MQTT\AreaCommandCode.cs" />
|
||||||
<Compile Include="MQTT\AreaCommands.cs" />
|
<Compile Include="MQTT\AreaCommands.cs" />
|
||||||
<Compile Include="MQTT\AreaState.cs" />
|
<Compile Include="MQTT\AreaState.cs" />
|
||||||
|
<Compile Include="MQTT\Availability.cs" />
|
||||||
<Compile Include="MQTT\BinarySensor.cs" />
|
<Compile Include="MQTT\BinarySensor.cs" />
|
||||||
<Compile Include="MQTT\CommandTypes.cs" />
|
<Compile Include="MQTT\CommandTypes.cs" />
|
||||||
<Compile Include="MQTT\Device.cs" />
|
<Compile Include="MQTT\Device.cs" />
|
||||||
|
|
16
README.md
16
README.md
|
@ -15,7 +15,7 @@ OmniLink Bridge is divided into the following modules and configurable settings.
|
||||||
- Maintains connection to the OmniLink controller
|
- Maintains connection to the OmniLink controller
|
||||||
- Thermostats
|
- Thermostats
|
||||||
- If no status update has been received after 4 minutes a request is issued
|
- If no status update has been received after 4 minutes a request is issued
|
||||||
- A status update containing a temperature of 0 is ignored
|
- A status update containing a temperature of 0 marks the thermostat offline
|
||||||
- This can occur when a ZigBee thermostat has lost communication
|
- This can occur when a ZigBee thermostat has lost communication
|
||||||
- Time Sync: time_
|
- Time Sync: time_
|
||||||
- Controller time is checked and compared to the local computer time disregarding time zones
|
- Controller time is checked and compared to the local computer time disregarding time zones
|
||||||
|
@ -146,6 +146,17 @@ systemctl start omnilinkbridge.service
|
||||||
```
|
```
|
||||||
|
|
||||||
## MQTT
|
## MQTT
|
||||||
|
```
|
||||||
|
SUB omnilink/status
|
||||||
|
string online, offline
|
||||||
|
|
||||||
|
SUB omnilink/model
|
||||||
|
string Controller model
|
||||||
|
|
||||||
|
SUB omnilink/version
|
||||||
|
string Controller version
|
||||||
|
```
|
||||||
|
|
||||||
### System
|
### System
|
||||||
```
|
```
|
||||||
SUB omnilink/system/phone/state
|
SUB omnilink/system/phone/state
|
||||||
|
@ -231,6 +242,9 @@ string A-L
|
||||||
SUB omnilink/thermostatX/name
|
SUB omnilink/thermostatX/name
|
||||||
string Thermostat name
|
string Thermostat name
|
||||||
|
|
||||||
|
SUB omnilink/thermostatX/status
|
||||||
|
string online, offline
|
||||||
|
|
||||||
SUB omnilink/thermostatX/current_operation
|
SUB omnilink/thermostatX/current_operation
|
||||||
string idle, cooling, heating
|
string idle, cooling, heating
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue