Skip to content

Commit

Permalink
#12 - Added ability to override ID and Name generation for Outlets, a…
Browse files Browse the repository at this point in the history
…nd measurements. Added ability to also completely disable certain Outlets and Measurements.
  • Loading branch information
XtremeOwnageDotCom committed Aug 29, 2024
1 parent aa502ff commit 52b4f6b
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 78 deletions.
8 changes: 7 additions & 1 deletion rPDU2MQTT/Classes/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ namespace rPDU2MQTT.Classes;

public class Config
{
public Config(IOptionsSnapshot<MQTTConfig> MQTT, IOptionsSnapshot<PduConfig> PDU, IOptionsSnapshot<HomeAssistantConfig> HASS, IOptionsSnapshot<Overrides> Overrides)
public Config(IOptionsSnapshot<MQTTConfig> MQTT, IOptionsSnapshot<PduConfig> PDU, IOptionsSnapshot<HomeAssistantConfig> HASS, IOptionsSnapshot<Overrides> Overrides, IOptionsSnapshot<OutletOverrides> outlets, IOptionsSnapshot<MeasurementOverrides> measurements)
{
this.MQTT = MQTT.Value;
this.PDU = PDU.Value;
this.HASS = HASS.Value;
this.Overrides = Overrides.Value;
this.Outlets = outlets.Value;
this.Measurements = measurements.Value;
}

public MQTTConfig MQTT { get; }
public PduConfig PDU { get; }
public HomeAssistantConfig HASS { get; }

public Overrides Overrides { get; }

public OutletOverrides Outlets { get; }

public MeasurementOverrides Measurements { get; }
}
15 changes: 10 additions & 5 deletions rPDU2MQTT/Classes/PDU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ private void processDevices(Dictionary<string, Device> devices)
{
foreach (var (key, device) in devices)
{
// Remove any disabled outlets.
config.Outlets.RemoveDisabledRecords(device.Outlets);

// Propagate down the parent, and identifier.
device.Entity.SetParentAndIdentifier(BaseEntity.FromDevice(device, MqttPath.Entity));
device.Outlets.SetParentAndIdentifier(BaseEntity.FromDevice(device, MqttPath.Outlets));
Expand All @@ -72,15 +75,17 @@ private void processChildDevice<T>(Dictionary<string, T> entities) where T : Nam
if (entity is Outlet o)
{
int k = int.TryParse(key, out int s) ? s + 1 : 0; //Note- the plus one, is so the number aligns with what is seen on the GUI.
entity.Entity_Name = o.GetOverrideOrDefault(k, config.Overrides.OutletID, FormatName: true);
entity.Entity_DisplayName = o.GetOverrideOrDefault(k, config.Overrides.OutletName, FormatName: false);
entity.ApplyOverrides(k.ToString(), config.Outlets);
}
else
{
entity.Entity_Name = (entity.Label ?? entity.Name).FormatName();
entity.Entity_DisplayName = (entity.Label ?? entity.Name);
}

// Remove any disabled measurements.
config.Measurements.RemoveDisabledRecords(entity.Measurements, o => o.Type);

// All measurements will be stored into a sub-key.
entity.Measurements.SetParentAndIdentifier(BaseEntity.FromDevice(entity, MqttPath.Measurements));

Expand All @@ -95,10 +100,10 @@ private void processMeasurements<T>(Dictionary<string, T> measurements) where T
{
// We want to override the default key here- to give a nice, readable key.
entity.Record_Key = entity.Type;
entity.ApplyOverrides(entity.Type, config.Measurements, DefaultName: entity.Type, DefaultDisplayName: entity.Type);

var suffix = entity.GetOverrideOrDefault(entity.Type, config.Overrides.MeasurementID, entity.Type, true);
entity.Entity_Name = entity.GetEntityName(suffix);
entity.Entity_DisplayName = entity.GetOverrideOrDefault(entity.Type, config.Overrides.MeasurementName, entity.Type, false);
//SInce- ApplyNameOverridesReturnIsValid already set the EntityName, we are just going to append a suffix to it.
entity.Entity_Name = entity.GetEntityName(entity.Entity_Name);
}
}
}
79 changes: 62 additions & 17 deletions rPDU2MQTT/Extensions/EntityWithName_Overrides.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
using rPDU2MQTT.Helpers;
using rPDU2MQTT.Interfaces;
using rPDU2MQTT.Models.Config;
using rPDU2MQTT.Models.PDU.basePDU;
using rPDU2MQTT.Models.PDU.DummyDevices;

namespace rPDU2MQTT.Extensions;

public static class EntityWithName_Overrides
{
/// <summary>
/// This method will both apply any overides specified for entity_id, and name. And, it will return if this entity is enabled or not.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="Key"></typeparam>
/// <param name="entity"></param>
/// <param name="key"></param>
/// <param name="Overrides"></param>
/// <returns></returns>
public static void ApplyOverrides<TEntity>(this TEntity entity, string key, TypeOverride Overrides, string? DefaultName = null, string? DefaultDisplayName = null)
where TEntity : NamedEntity
{
entity.Entity_Name = entity.GetOverrideOrDefault(key, Overrides.ID, FormatAsName: true, DefaultValue: DefaultName);
entity.Entity_DisplayName = entity.GetOverrideOrDefault(key, Overrides.Name, FormatAsName: false, DefaultValue: DefaultDisplayName);
entity.Entity_Enabled = tryGetValue(Overrides.Enabled, key, out bool enabled, DefaultValue: true) ? enabled : true;
}

/// <summary>
/// This calculates the name of an entity, based on a collection of overrides.
/// </summary>
Expand All @@ -16,21 +35,23 @@ public static class EntityWithName_Overrides
/// <param name="entity"></param>
/// <param name="Key"></param>
/// <param name="Overrides"></param>
public static string GetOverrideOrDefault<T, Key>(this T entity, Key? key, Dictionary<Key, string> Overrides, string Default = null, bool FormatName = false)
where Key : notnull
public static string GetOverrideOrDefault<T>(this T entity, string? key, Dictionary<string, string> Overrides, string? DefaultValue = null, bool FormatAsName = false)
where T : NamedEntity
{
string formatIfNeeded(string input) => FormatName switch
string formatIfNeeded(string input) => FormatAsName switch
{
true => input.FormatName(),
false => input
};

if (tryGetValue(Overrides, key, out string val))
return formatIfNeeded(val);
if (string.IsNullOrEmpty(key))
throw new NullReferenceException("Key is null");

if (Default is not null)
return formatIfNeeded(Default);
if (Overrides.TryGetValue(key, out string overrideValue))
return formatIfNeeded(overrideValue);

if (!string.IsNullOrEmpty(DefaultValue))
return formatIfNeeded(DefaultValue);

if (entity is EntityWithNameAndLabel entityWithNameAndLabel)
return formatIfNeeded(entityWithNameAndLabel.Label ?? entityWithNameAndLabel.Name);
Expand All @@ -42,44 +63,68 @@ public static string GetOverrideOrDefault<T, Key>(this T entity, Key? key, Dicti
/// <summary>
/// Multi-type lookup. Does case-insensitive compare for strings.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="dictionary"></param>
/// <param name="key"></param>
/// <param name="Result"></param>
/// <returns></returns>
private static bool tryGetValue<T>(this Dictionary<T, string> dictionary, T? key, out string Result)
where T : notnull
private static bool tryGetValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey? key, out TValue Result, TValue DefaultValue)
where TKey : notnull
{
if (key is null)
{
Result = string.Empty;
Result = DefaultValue;
return false;
}
if (key is string sKey && dictionary is Dictionary<string, string> stringDictionary)
return tryGetStringValue(stringDictionary, sKey, out Result);
if (key is string sKey && dictionary is Dictionary<string, TValue> stringDictionary)
return caseInsensitiveLookup<TValue>(stringDictionary, sKey, out Result, DefaultValue);

if (dictionary.ContainsKey(key))
{
Result = dictionary[key];
return true;
}

Result = string.Empty;
Result = DefaultValue;
return false;
}


private static bool tryGetStringValue(this Dictionary<string, string> Dictionary, string Key, out string Result)
private static bool caseInsensitiveLookup<TValue>(this Dictionary<string, TValue> Dictionary, string Key, out TValue Result, TValue DefaultValue)
{
var match = Dictionary.Keys.FirstOrDefault(o => string.Equals(o, Key, StringComparison.OrdinalIgnoreCase));
if (match is null)
{
Result = string.Empty;
Result = DefaultValue;
return false;
}

Result = Dictionary[match];
return !string.IsNullOrWhiteSpace(Result);

if (Result is null)
return false;
if (Result is string s)
return !string.IsNullOrWhiteSpace(s);
return Result is not null;
}

/// <summary>
/// Sets all properties of <see cref="IMQTTKey"/>.
/// </summary>
/// <remarks>
/// <see cref="IMQTTKey.Record_Key"/> is set to key from device.
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <param name="Items"></param>
/// <param name="Parent"></param>
public static void SetParentAndIdentifier<T>(this Dictionary<string, T> Items, IMQTTKey Parent) where T : BaseEntity
{
foreach (var (key, item) in Items)
{
item.Record_Parent = Parent;
item.Record_Key = key;
item.Entity_Identifier = Parent.CreateChildIdentifier(key);
}
}

}
18 changes: 0 additions & 18 deletions rPDU2MQTT/Extensions/IMQTTKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,4 @@ string getObjectID(IMQTTKey? cur)
return result.FormatName();
}

/// <summary>
/// Sets all properties of <see cref="IMQTTKey"/>.
/// </summary>
/// <remarks>
/// <see cref="IMQTTKey.Record_Key"/> is set to key from device.
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <param name="Items"></param>
/// <param name="Parent"></param>
public static void SetParentAndIdentifier<T>(this Dictionary<string, T> Items, IMQTTKey Parent) where T : BaseEntity
{
foreach (var (key, item) in Items)
{
item.Record_Parent = Parent;
item.Record_Key = key;
item.Entity_Identifier = Parent.CreateChildIdentifier(key);
}
}
}
38 changes: 12 additions & 26 deletions rPDU2MQTT/Models/Config/Overrides.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace rPDU2MQTT.Models.Config;
using System.Text.Json.Serialization;

namespace rPDU2MQTT.Models.Config;

public class Overrides
{
Expand All @@ -11,30 +13,14 @@ public class Overrides
/// Allows overriding the generated entity name for the PDU.
/// </summary>
public string? PduName { get; set; } = null;
}

/// <summary>
/// Allows overriding the generated "name" for each outlet.
/// </summary>
/// <remarks>
/// This maps to <see cref="Models.HomeAssistant.baseClasses.baseEntity.Name"/>, ie, "object_id"
/// </remarks>
public Dictionary<int, string> OutletID { get; set; } = new();

/// <summary>
/// Allows overriding the "Display Name" for each outlet.
/// </summary>
/// <remarks>
/// This maps to <see cref="Models.HomeAssistant.baseClasses.baseEntity.DisplayName"/>, ie, "name"
/// </remarks>
public Dictionary<int, string> OutletName { get; set; } = new();

/// <summary>
/// Allows overriding the generated Entity ID for measurements.
/// </summary>
public Dictionary<string, string> MeasurementID { get; set; } = new();
/// <summary>
/// Defines overrides for measurements.
/// </summary>
public class MeasurementOverrides : TypeOverride { }

/// <summary>
/// Allows overriding the generated Entity Name for measurements.
/// </summary>
public Dictionary<string, string> MeasurementName { get; set; } = new();
}
/// <summary>
/// Defines overrides for outlets.
/// </summary>
public class OutletOverrides : TypeOverride { }
56 changes: 56 additions & 0 deletions rPDU2MQTT/Models/Config/TypeOverride.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using rPDU2MQTT.Models.Converters;
using System.Text.Json.Serialization;

namespace rPDU2MQTT.Models.Config;

/// <summary>
/// This defines the schema for overrides for a specific section.
/// </summary>
/// <typeparam name="string">This is the type of key used.</typeparam>
public class TypeOverride
{
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<string>))]
[JsonPropertyName("ID")]
/// <summary>
/// Allows overriding the generated EntityName.
/// </summary>
/// <remarks>
/// This maps to <see cref="Models.HomeAssistant.baseClasses.baseEntity.Name"/>, ie, "object_id"
/// </remarks>
public Dictionary<string, string> ID { get; set; } = new();

[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<string>))]
[JsonPropertyName("Name")]
/// <summary>
/// Allows overriding the Name / Display Name.
/// </summary>
/// <remarks>
/// This maps to <see cref="Models.HomeAssistant.baseClasses.baseEntity.DisplayName"/>, ie, "name"
/// </remarks>
public Dictionary<string, string> Name { get; set; } = new();

[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<bool>))]
[JsonPropertyName("Enabled")]
/// <summary>
/// Allows enabling, or disabling specific entities.
/// </summary>
public Dictionary<string, bool> Enabled { get; set; } = new();

/// <summary>
/// Remove any entites which are marked as disabled.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="Entities"></param>
public void RemoveDisabledRecords<TEntity>(Dictionary<string, TEntity> Entities, Func<TEntity, string>? KeyFunc = null)
{
//Remove any disabled entities.
foreach (var item in Entities.ToList())
{
string Key = KeyFunc is null ? item.Key : KeyFunc.Invoke(item.Value);
if (Enabled.TryGetValue(Key, out bool IsEnabled) && IsEnabled == false)
{
Entities.Remove(item.Key);
}
}
}
}
18 changes: 18 additions & 0 deletions rPDU2MQTT/Models/Converters/CaseInsensitiveDictionaryConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace rPDU2MQTT.Models.Converters;

public sealed class CaseInsensitiveDictionaryConverter<TValue> : JsonConverter<Dictionary<string, TValue>>
{
public override Dictionary<string, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var newDictionary = (Dictionary<string, TValue>)JsonSerializer.Deserialize(ref reader, typeToConvert, options);
return new Dictionary<string, TValue>(newDictionary, StringComparer.OrdinalIgnoreCase);
}

public override void Write(Utf8JsonWriter writer, Dictionary<string, TValue> value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
11 changes: 7 additions & 4 deletions rPDU2MQTT/Models/PDU/basePDU/BaseEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public static DummyEntity FromDevice(IMQTTKey Parent, MqttPath Path)
Entity_Identifier = Parent.CreateChildIdentifier(Path.ToJsonString())
};
}
#region IMQTTKey

/// <inheritdoc cref="IMQTTKey.Entity_Identifier"/>
/// <remarks>
/// This should only bet set by <see cref="Classes.PDU"/>
Expand All @@ -42,6 +40,11 @@ public static DummyEntity FromDevice(IMQTTKey Parent, MqttPath Path)
/// </remarks>
[JsonIgnore]
public IMQTTKey? Record_Parent { get; set; }
#endregion

}
/// <summary>
/// Determine if this entity is enabled, and should be used.
/// </summary>
[JsonIgnore]
public bool Entity_Enabled { get; set; } = true;

}
2 changes: 2 additions & 0 deletions rPDU2MQTT/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
services.Configure<ActionsConfig>(context.Configuration.GetSection("Actions"));
services.Configure<HomeAssistantConfig>(context.Configuration.GetSection("HomeAssistant"));
services.Configure<Overrides>(context.Configuration.GetSection("Overrides"));
services.Configure<OutletOverrides>(context.Configuration.GetSection("Outlets"));
services.Configure<MeasurementOverrides>(context.Configuration.GetSection("Measurements"));

//Bind MQTT
var mqttConfig = context.Configuration.GetSection("Mqtt").Get<MQTTConfig>() ?? throw new NullReferenceException("Unable to load MQTT configuration.");
Expand Down
Loading

0 comments on commit 52b4f6b

Please sign in to comment.