Compare commits

...

24 Commits

Author SHA1 Message Date
Andrew Welker
44ed067f4d chore: update local build version 2025-10-30 15:57:37 -05:00
Andrew Welker
faa2169baf fix: send correct URL to panel 2025-10-30 15:57:28 -05:00
Andrew Welker
fd40b0c6d1 fix: send all status messages with ClientId 2025-10-30 15:57:15 -05:00
Andrew Welker
afcddad1cc fix: remove async on task, as it's unnecessary 2025-10-30 15:56:50 -05:00
Andrew Welker
c4cf8f13e9 fix: register panel in post phase rather than activation cycle 2025-10-30 15:56:28 -05:00
Andrew Welker
0538a304ed Merge branch 'main' into mc-touchpanel-key 2025-10-29 15:13:53 -05:00
Andrew Welker
705c5db237 Merge pull request #1347 from PepperDash/json-console
fix: print json response to console using \r\n instead of \n
2025-10-29 16:12:49 -04:00
Andrew Welker
16c92afabb fix: print json response to console using \r\n instead of \n 2025-10-27 09:21:51 -05:00
Andrew Welker
dff5d2d32e Merge pull request #1346 from PepperDash/feature/add-infinetEx-comm-method 2025-10-23 18:50:06 -04:00
Neil Dorin
317bde3814 fix: add InfinetEx to eControlMethods enum 2025-10-23 15:22:46 -06:00
Andrew Welker
8bab3dc966 fix: send touchpanelKey message with all room combiner checks 2025-10-23 11:21:17 -05:00
Andrew Welker
514ac850ca fix: send touchpanel key correctly 2025-10-23 10:01:05 -05:00
Andrew Welker
44432f7a41 fix: send touchpanel key to client when client joins direct server 2025-10-23 09:54:03 -05:00
Andrew Welker
99253b30c2 fix: send touchpanel key to client when client joins 2025-10-23 09:49:45 -05:00
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
14 changed files with 376 additions and 148 deletions

View File

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

View File

@@ -78,6 +78,10 @@ namespace PepperDash.Core
/// <summary>
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
/// </summary>
ComBridge
ComBridge,
/// <summary>
/// InfinetEX control
/// </summary>
InfinetEx
}
}

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

@@ -60,9 +60,9 @@ namespace PepperDash.Essentials.Core
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s).Replace(Environment.NewLine, "\r\n")), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s).Replace(Environment.NewLine, "\r\n")), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s).Replace(Environment.NewLine, "\r\n")), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);

View File

@@ -1,20 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core;
using Crestron.SimplSharpPro.UI;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events;
namespace PepperDash.Essentials.Core.UI
{
public abstract class TouchpanelBase: EssentialsDevice, IHasBasicTriListWithSmartObject
/// <summary>
/// Base class for Touchpanel devices
/// </summary>
public abstract class TouchpanelBase : EssentialsDevice, IHasBasicTriListWithSmartObject
{
/// <summary>
/// Gets or sets the configuration for the Crestron touchpanel.
/// </summary>
protected CrestronTouchpanelPropertiesConfig _config;
/// <summary>
/// Gets or sets the Panel
@@ -27,12 +29,11 @@ namespace PepperDash.Essentials.Core.UI
/// is provided.
/// </summary>
/// <param name="key">Essentials Device Key</param>
/// <param name="name">Essentials Device Name</param>
/// <param name="type">Touchpanel Type to build</param>
/// <param name="config">Touchpanel Configuration</param>
/// <param name="id">IP-ID to use for touch panel</param>
/// <param name="name">Essentials Device Name</param>
/// <param name="panel">Crestron Touchpanel Device</param>
/// <param name="config">Touchpanel Configuration</param>
protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config)
:base(key, name)
: base(key, name)
{
if (panel == null)
@@ -55,23 +56,21 @@ namespace PepperDash.Essentials.Core.UI
tsw.ButtonStateChange += Tsw_ButtonStateChange;
}
_config = config;
AddPreActivationAction(() => {
if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success)
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason);
_config = config;
AddPreActivationAction(() =>
{
// Give up cleanly if SGD is not present.
var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile;
if (!File.Exists(sgdName))
{
Debug.LogMessage(LogEventLevel.Information, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName);
this.LogInformation("Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName);
sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile;
if (!File.Exists(sgdName))
{
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName);
this.LogWarning("Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName);
return;
}
}
@@ -82,12 +81,11 @@ namespace PepperDash.Essentials.Core.UI
AddPostActivationAction(() =>
{
// Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event
var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner;
if (roomCombiner != null)
if (DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) is IEssentialsRoomCombiner roomCombiner)
{
// Subscribe to the even
roomCombiner.RoomCombinationScenarioChanged += new EventHandler<EventArgs>(roomCombiner_RoomCombinationScenarioChanged);
roomCombiner.RoomCombinationScenarioChanged += new EventHandler<EventArgs>(RoomCombiner_RoomCombinationScenarioChanged);
// Connect to the initial roomKey
if (roomCombiner.CurrentScenario != null)
@@ -106,6 +104,11 @@ namespace PepperDash.Essentials.Core.UI
// No room combiner, use the default key
SetupPanelDrivers(_config.DefaultRoomKey);
}
var panelRegistrationResponse = Panel.Register();
if (panelRegistrationResponse != eDeviceRegistrationUnRegistrationResponse.Success)
this.LogInformation("WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason);
});
}
@@ -115,6 +118,14 @@ namespace PepperDash.Essentials.Core.UI
/// <param name="roomKey">Room Key for this panel</param>
protected abstract void SetupPanelDrivers(string roomKey);
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
}
/// <summary>
/// Event handler for System Extender Events
@@ -129,7 +140,7 @@ namespace PepperDash.Essentials.Core.UI
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e)
protected virtual void RoomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e)
{
var roomCombiner = sender as IEssentialsRoomCombiner;
@@ -156,23 +167,23 @@ namespace PepperDash.Essentials.Core.UI
SetupPanelDrivers(newRoomKey);
}
private void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args)
{
Debug.LogMessage(LogEventLevel.Verbose, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
var uo = args.Sig.UserObject;
if (uo is Action<bool>)
(uo as Action<bool>)(args.Sig.BoolValue);
else if (uo is Action<ushort>)
(uo as Action<ushort>)(args.Sig.UShortValue);
else if (uo is Action<string>)
(uo as Action<string>)(args.Sig.StringValue);
}
private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args)
{
var uo = args.Button.UserObject;
if(uo is Action<bool>)
(uo as Action<bool>)(args.Button.State == eButtonState.Pressed);
}
private void Panel_SigChange(object currentDevice, SigEventArgs args)
{
this.LogVerbose("Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
var uo = args.Sig.UserObject;
if (uo is Action<bool>)
(uo as Action<bool>)(args.Sig.BoolValue);
else if (uo is Action<ushort>)
(uo as Action<ushort>)(args.Sig.UShortValue);
else if (uo is Action<string>)
(uo as Action<string>)(args.Sig.StringValue);
}
private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args)
{
var uo = args.Button.UserObject;
if (uo is Action<bool>)
(uo as Action<bool>)(args.Button.State == eButtonState.Pressed);
}
}
}

View File

@@ -2174,6 +2174,7 @@ namespace PepperDash.Essentials
{
var clientId = content["clientId"].Value<string>();
var roomKey = content["roomKey"].Value<string>();
var touchpanelKey = content.SelectToken("touchpanelKey");
if (_roomCombiner == null)
{
@@ -2185,6 +2186,10 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2196,7 +2201,12 @@ namespace PepperDash.Essentials
ClientId = clientId,
Content = roomKey
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2214,6 +2224,10 @@ namespace PepperDash.Essentials
};
SendMessageObject(message);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
return;
}
@@ -2227,6 +2241,54 @@ namespace PepperDash.Essentials
};
SendMessageObject(newMessage);
SendDeviceInterfaces(clientId);
SendTouchpanelKey(clientId, touchpanelKey);
}
private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken)
{
if (touchpanelKeyToken == null)
{
this.LogWarning("Touchpanel key not found for client {clientId}", clientId);
return;
}
SendMessageObject(new MobileControlMessage
{
Type = "/system/touchpanelKey",
ClientId = clientId,
Content = touchpanelKeyToken.Value<string>()
});
}
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)
@@ -2337,7 +2399,7 @@ namespace PepperDash.Essentials
foreach (var handler in handlers)
{
Task.Run(async () =>
Task.Run(() =>
{
try
{

View File

@@ -33,7 +33,7 @@ namespace PepperDash.Essentials.Touchpanel
return;
}
AddAction($"/fullStatus", (id, context) => SendFullStatus());
AddAction($"/fullStatus", (id, context) => SendFullStatus(id));
AddAction($"/openApp", (id, context) => _appControl.OpenApp());

View File

@@ -252,6 +252,7 @@ namespace PepperDash.Essentials.Touchpanel
if (!x70Panel.ExtenderApplicationControlReservedSigs.HideOpenedApplicationFeedback.BoolValue)
{
x70Panel.ExtenderButtonToolbarReservedSigs.ShowButtonToolbar();
x70Panel.ExtenderButtonToolbarReservedSigs.Button2On();
}
else
@@ -294,17 +295,16 @@ namespace PepperDash.Essentials.Touchpanel
handler(this, new DeviceInfoEventArgs(DeviceInfo));
};
x70Panel.ExtenderButtonToolbarReservedSigs.DeviceExtenderSigChange += (o, a) =>
{
this.LogVerbose("X70 Button Toolbar Device Extender args: {event}:{sig}:{name}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Name, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue);
};
x70Panel.ExtenderApplicationControlReservedSigs.Use();
x70Panel.ExtenderZoomRoomAppReservedSigs.Use();
x70Panel.ExtenderEthernetReservedSigs.Use();
x70Panel.ExtenderButtonToolbarReservedSigs.Use();
x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off();
return;
}
@@ -414,34 +414,79 @@ namespace PepperDash.Essentials.Touchpanel
McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]);
UserCodeFeedback.LinkInputSig(Panel.StringInput[4]);
Panel.IpInformationChange += (sender, args) =>
Panel.IpInformationChange -= Panel_IpInformationChange;
Panel.IpInformationChange += Panel_IpInformationChange;
Panel.OnlineStatusChange -= Panel_OnlineChange;
Panel.OnlineStatusChange += Panel_OnlineChange;
}
private void Panel_OnlineChange(GenericBase sender, OnlineOfflineEventArgs args)
{
try
{
if (args.Connected)
if (!args.DeviceOnLine)
{
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
var appUrl = GetUrlWithCorrectIp(_appUrl);
Panel.StringInput[1].StringValue = appUrl;
SetAppUrl(appUrl);
this.LogInformation("panel is offline");
return;
}
else
{
this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress);
}
};
Panel.OnlineStatusChange += (sender, args) =>
{
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
this.LogDebug("panel is online");
UpdateFeedbacks();
Panel.StringInput[1].StringValue = _appUrl;
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue;
};
if (Panel is TswXX70Base x70Panel)
{
this.LogDebug("setting buttons off");
x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off();
x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off();
}
SendUrlToPanel();
}
catch (Exception ex)
{
this.LogError("Exception in panel online: {message}", ex.Message);
this.LogDebug(ex, "Stack Trace: ");
}
}
private void SendUrlToPanel()
{
var appUrl = GetUrlWithCorrectIp(_appUrl);
this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue);
if (Panel.StringInput[1].StringValue == appUrl)
{
this.LogInformation("App URL already set to {appUrl}, no update needed", AppUrlFeedback.StringValue);
return;
}
Panel.StringInput[1].StringValue = appUrl;
SetAppUrl(appUrl);
}
private void Panel_IpInformationChange(GenericBase sender, ConnectedIpEventArgs args)
{
if (args.Connected)
{
this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress);
SendUrlToPanel();
}
else
{
this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress);
}
}
/// <summary>

View File

@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.AppServer;
using PepperDash.Essentials.AppServer.Messengers;
@@ -29,17 +30,17 @@ namespace PepperDash.Essentials.Touchpanel
{
AddAction("/fullStatus", (id, content) =>
{
PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme });
PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme }, id);
});
AddAction("/saveTheme", (id, content) =>
{
var theme = content.ToObject<MobileControlSimpleContent<string>>();
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Setting theme to {theme}", this, theme.Value);
this.LogInformation("Setting theme to {theme}", this, theme.Value);
_tpDevice.UpdateTheme(theme.Value);
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }));
PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id);
});
}
}

View File

@@ -385,9 +385,9 @@ namespace PepperDash.Essentials.WebSocketServer
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
this.LogVerbose("Sending URL {appUrl}", appUrl);
this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key);
touchpanel.Messenger.UpdateAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
}
}
@@ -723,7 +723,7 @@ namespace PepperDash.Essentials.WebSocketServer
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
{
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token);
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey);
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
c.Controller = _parent;
c.RoomKey = roomKey;

View File

@@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer
/// </summary>
public string Token { get; private set; }
/// <summary>
/// Touchpanel Key associated with this client
/// </summary>
public string TouchpanelKey { get; private set; }
/// <summary>
/// Gets or sets the mobile control system controller that handles this client's messages
/// </summary>
@@ -75,11 +80,13 @@ namespace PepperDash.Essentials.WebSocketServer
/// <param name="key">The unique key to identify this client</param>
/// <param name="id">The client ID used by the client for this connection</param>
/// <param name="token">The token associated with this client</param>
public UiClient(string key, string id, string token)
/// <param name="touchpanelKey">The touchpanel key associated with this client</param>
public UiClient(string key, string id, string token, string touchpanelKey = "")
{
Key = key;
Id = id;
Token = token;
TouchpanelKey = touchpanelKey;
}
/// <inheritdoc />
@@ -105,6 +112,7 @@ namespace PepperDash.Essentials.WebSocketServer
{
clientId = Id,
roomKey = RoomKey,
touchpanelKey = string.IsNullOrEmpty(TouchpanelKey) ? string.Empty : TouchpanelKey,
})
};

View File

@@ -104,7 +104,7 @@ namespace PepperDash.Essentials
CrestronConsole.ConsoleCommandResponse
("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine);
CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented));
(ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented).Replace(Environment.NewLine, "\r\n"));
}, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s =>