Compare commits

...

10 Commits

Author SHA1 Message Date
Andrew Welker
bf248fe33e Merge pull request #1343 from PepperDash/device-interface-system
device interface system
2025-10-23 10:40:56 -04:00
cdenig
2f44040e4f Merge pull request #1344 from PepperDash/feature/allow-config-tool-v2-structure
Feature/allow config tool v2 structure
2025-10-23 10:15:03 -04:00
Neil Dorin
10399a1be8 Update src/PepperDash.Core/Config/PortalConfigReader.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:54:08 -06:00
Neil Dorin
5409db193c Update src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:53:23 -06:00
Neil Dorin
f1ce54a524 Merge branch 'main' into feature/allow-config-tool-v2-structure 2025-10-22 13:48:22 -06:00
Andrew Welker
7c72a0d905 fix: send device interface list on client join
In order to avoid updating the MC Edge API to send device interfaces, one of the responses to the clientJoined message will now be a message with the type `/system/deviceInterfaces`. This has a corresponding handler in the core library to put the interfaces in the correct location.
2025-10-21 18:19:09 -05:00
Andrew Welker
5d5e78629e chore: update local build version to 2.18.2-local 2025-10-21 18:17:25 -05:00
Neil Dorin
9de94bd65f fix: Update v2 config detection criteria
Changed the logic for identifying v2 configuration files. The check now looks for the presence of a "versions" node instead of the absence of "system" or "template" nodes, reflecting an update in the configuration file structure.
2025-09-22 15:05:06 -06:00
Neil Dorin
ff46fb8f29 feat: Add versioning support to EssentialsConfig
Introduce `Versions` property in `EssentialsConfig` to hold version information.
Add `VersionData` class for Essentials and package versions, and `NugetVersion` class for individual package details.
Retain and document `SystemTemplateConfigs` class.
2025-09-22 14:55:58 -06:00
Neil Dorin
d9243def30 feat: Adds ability to read configs generated from v2 config tool that are pre-merged don't have system or template objects
Refactor config handling and improve documentation

- Updated `PortalConfigReader.cs` to use constants for configuration keys, enhancing maintainability and readability. Improved error logging with `Debug.LogError`.
- Modified `ConfigReader.cs` to handle v2 configuration format, streamlining the loading process and avoiding redundant parsing.
- Added XML documentation comments to properties in `EssentialsConfig.cs`, improving code documentation. Initialized `Rooms` property in the constructor.
- Enhanced `SystemTemplateConfigs` class with XML documentation for better clarity on its properties.
2025-09-22 14:22:57 -06:00
5 changed files with 202 additions and 68 deletions

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.15.1-local</Version>
<Version>2.18.2-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company>

View File

@@ -9,40 +9,59 @@ using Serilog.Events;
namespace PepperDash.Core.Config
{
/// <summary>
/// Reads a Portal formatted config file
/// </summary>
public class PortalConfigReader
{
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
const string template = "template";
const string system = "system";
const string systemUrl = "system_url";
const string templateUrl = "template_url";
const string info = "info";
const string devices = "devices";
const string rooms = "rooms";
const string sourceLists = "sourceLists";
const string destinationLists = "destinationLists";
const string cameraLists = "cameraLists";
const string audioControlPointLists = "audioControlPointLists";
const string tieLines = "tieLines";
const string joinMaps = "joinMaps";
const string global = "global";
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
{
try
{
if (!File.Exists(filePath))
{
Debug.Console(1, Debug.ErrorLogLevel.Error,
Debug.LogError(
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
}
using (StreamReader fs = new StreamReader(filePath))
{
var jsonObj = JObject.Parse(fs.ReadToEnd());
if(jsonObj["template"] != null && jsonObj["system"] != null)
if(jsonObj[template] != null && jsonObj[system] != null)
{
// it's a double-config, merge it.
var merged = MergeConfigs(jsonObj);
if (jsonObj["system_url"] != null)
if (jsonObj[systemUrl] != null)
{
merged["systemUrl"] = jsonObj["system_url"].Value<string>();
merged[systemUrl] = jsonObj[systemUrl].Value<string>();
}
if (jsonObj["template_url"] != null)
if (jsonObj[templateUrl] != null)
{
merged["templateUrl"] = jsonObj["template_url"].Value<string>();
merged[templateUrl] = jsonObj[templateUrl].Value<string>();
}
jsonObj = merged;
@@ -77,62 +96,62 @@ namespace PepperDash.Core.Config
var merged = new JObject();
// Put together top-level objects
if (system["info"] != null)
merged.Add("info", Merge(template["info"], system["info"], "infO"));
if (system[info] != null)
merged.Add(info, Merge(template[info], system[info], info));
else
merged.Add("info", template["info"]);
merged.Add(info, template[info]);
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
system["devices"] as JArray, "key", "devices"));
merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray,
system[devices] as JArray, "key", devices));
if (system["rooms"] == null)
merged.Add("rooms", template["rooms"]);
if (system[rooms] == null)
merged.Add(rooms, template[rooms]);
else
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
system["rooms"] as JArray, "key", "rooms"));
merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray,
system[rooms] as JArray, "key", rooms));
if (system["sourceLists"] == null)
merged.Add("sourceLists", template["sourceLists"]);
if (system[sourceLists] == null)
merged.Add(sourceLists, template[sourceLists]);
else
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists));
if (system["destinationLists"] == null)
merged.Add("destinationLists", template["destinationLists"]);
if (system[destinationLists] == null)
merged.Add(destinationLists, template[destinationLists]);
else
merged.Add("destinationLists",
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
merged.Add(destinationLists,
Merge(template[destinationLists], system[destinationLists], destinationLists));
if (system["cameraLists"] == null)
merged.Add("cameraLists", template["cameraLists"]);
if (system[cameraLists] == null)
merged.Add(cameraLists, template[cameraLists]);
else
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists));
if (system["audioControlPointLists"] == null)
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
if (system[audioControlPointLists] == null)
merged.Add(audioControlPointLists, template[audioControlPointLists]);
else
merged.Add("audioControlPointLists",
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
merged.Add(audioControlPointLists,
Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists));
// Template tie lines take precedence. Config tool doesn't do them at system
// level anyway...
if (template["tieLines"] != null)
merged.Add("tieLines", template["tieLines"]);
else if (system["tieLines"] != null)
merged.Add("tieLines", system["tieLines"]);
if (template[tieLines] != null)
merged.Add(tieLines, template[tieLines]);
else if (system[tieLines] != null)
merged.Add(tieLines, system[tieLines]);
else
merged.Add("tieLines", new JArray());
merged.Add(tieLines, new JArray());
if (template["joinMaps"] != null)
merged.Add("joinMaps", template["joinMaps"]);
if (template[joinMaps] != null)
merged.Add(joinMaps, template[joinMaps]);
else
merged.Add("joinMaps", new JObject());
merged.Add(joinMaps, new JObject());
if (system["global"] != null)
merged.Add("global", Merge(template["global"], system["global"], "global"));
if (system[global] != null)
merged.Add(global, Merge(template[global], system[global], global));
else
merged.Add("global", template["global"]);
merged.Add(global, template[global]);
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged;
@@ -228,7 +247,7 @@ namespace PepperDash.Core.Config
}
catch (Exception e)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
Debug.LogError($"Cannot merge items at path {propPath}: \r{e}");
}
}
}

View File

@@ -124,22 +124,35 @@ namespace PepperDash.Essentials.Core.Config
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config");
return true;
}
}
else
{
var doubleObj = JObject.Parse(fs.ReadToEnd());
ConfigObject = PortalConfigReader.MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
var parsedConfig = JObject.Parse(fs.ReadToEnd());
// Extract SystemUrl and TemplateUrl into final config output
if (doubleObj["system_url"] != null)
// Check if it's a v2 config (check for "version" node)
// this means it's already merged by the Portal API
// from the v2 config tool
var isV2Config = parsedConfig["versions"] != null;
if (isV2Config)
{
ConfigObject.SystemUrl = doubleObj["system_url"].Value<string>();
Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary.");
ConfigObject = parsedConfig.ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config");
return true;
}
if (doubleObj["template_url"] != null)
// Extract SystemUrl and TemplateUrl into final config output
ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject<EssentialsConfig>();
if (parsedConfig["system_url"] != null)
{
ConfigObject.TemplateUrl = doubleObj["template_url"].Value<string>();
ConfigObject.SystemUrl = parsedConfig["system_url"].Value<string>();
}
if (parsedConfig["template_url"] != null)
{
ConfigObject.TemplateUrl = parsedConfig["template_url"].Value<string>();
}
}

View File

@@ -16,13 +16,21 @@ namespace PepperDash.Essentials.Core.Config
/// </summary>
public class EssentialsConfig : BasicConfig
{
[JsonProperty("system_url")]
/// <summary>
/// Gets or sets the SystemUrl
/// </summary>
[JsonProperty("system_url")]
public string SystemUrl { get; set; }
[JsonProperty("template_url")]
/// <summary>
/// Gets or sets the TemplateUrl
/// </summary>
[JsonProperty("template_url")]
public string TemplateUrl { get; set; }
/// <summary>
/// Gets the SystemUuid extracted from the SystemUrl
/// </summary>
[JsonProperty("systemUuid")]
public string SystemUuid
{
@@ -45,6 +53,9 @@ namespace PepperDash.Essentials.Core.Config
}
}
/// <summary>
/// Gets the TemplateUuid extracted from the TemplateUrl
/// </summary>
[JsonProperty("templateUuid")]
public string TemplateUuid
{
@@ -67,30 +78,84 @@ namespace PepperDash.Essentials.Core.Config
}
}
[JsonProperty("rooms")]
/// <summary>
/// Gets or sets the Rooms
/// </summary>
[JsonProperty("rooms")]
public List<DeviceConfig> Rooms { get; set; }
/// <summary>
/// Gets or sets the Versions
/// </summary>
public VersionData Versions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsConfig"/> class.
/// </summary>
public EssentialsConfig()
: base()
{
Rooms = new List<DeviceConfig>();
}
}
/// <summary>
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
/// <summary>
/// Represents version data for Essentials and its packages
/// </summary>
public class VersionData
{
/// <summary>
/// Gets or sets the Essentials version
/// </summary>
[JsonProperty("essentials")]
public NugetVersion Essentials { get; set; }
/// <summary>
/// Gets or sets the list of Packages
/// </summary>
[JsonProperty("packages")]
public List<NugetVersion> Packages { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VersionData"/> class.
/// </summary>
public VersionData()
{
Packages = new List<NugetVersion>();
}
}
/// <summary>
/// Represents a NugetVersion
/// </summary>
public class NugetVersion
{
/// <summary>
/// Gets or sets the Version
/// </summary>
[JsonProperty("version")]
public string Version { get; set; }
/// <summary>
/// Gets or sets the PackageId
/// </summary>
[JsonProperty("packageId")]
public string PackageId { get; set; }
}
/// <summary>
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
{
/// <summary>
/// Gets or sets the System
/// </summary>
/// <summary>
/// Gets or sets the System
/// </summary>
public EssentialsConfig System { get; set; }
/// <summary>
/// Gets or sets the Template
/// </summary>
public EssentialsConfig Template { get; set; }
}
}

View File

@@ -2185,6 +2185,8 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
return;
}
@@ -2196,7 +2198,10 @@ namespace PepperDash.Essentials
ClientId = clientId,
Content = roomKey
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
return;
}
@@ -2214,6 +2219,8 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
return;
}
@@ -2227,6 +2234,36 @@ namespace PepperDash.Essentials
};
SendMessageObject(newMessage);
SendDeviceInterfaces(clientId);
}
private void SendDeviceInterfaces(string clientId)
{
this.LogDebug("Sending Device interfaces");
var devices = DeviceManager.GetDevices();
Dictionary<string, DeviceInterfaceInfo> deviceInterfaces = new Dictionary<string, DeviceInterfaceInfo>();
foreach (var device in devices)
{
var interfaces = device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List<string>();
deviceInterfaces.Add(device.Key, new DeviceInterfaceInfo
{
Key = device.Key,
Name = (device as IKeyName)?.Name ?? "",
Interfaces = interfaces
});
}
var message = new MobileControlMessage
{
Type = "/system/deviceInterfaces",
ClientId = clientId,
Content = JToken.FromObject(new { deviceInterfaces })
};
SendMessageObject(message);
}
private void HandleUserCode(JToken content, Action<string, string> action = null)