diff --git a/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs b/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs
new file mode 100644
index 00000000..4107d120
--- /dev/null
+++ b/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs
@@ -0,0 +1,47 @@
+using System;
+using Newtonsoft.Json;
+
+namespace PepperDash.Essentials
+{
+ ///
+ /// Represents the version information reported by a connected Mobile Control UI client
+ ///
+ public class ConnectedClientVersionInfo
+ {
+ ///
+ /// Gets or sets the client id
+ ///
+ [JsonProperty("clientId")]
+ public string ClientId { get; set; }
+
+ ///
+ /// Gets or sets the room key the client joined
+ ///
+ [JsonProperty("roomKey")]
+ public string RoomKey { get; set; }
+
+ ///
+ /// Gets or sets the touchpanel key the client joined as, if any
+ ///
+ [JsonProperty("touchpanelKey")]
+ public string TouchpanelKey { get; set; }
+
+ ///
+ /// Gets or sets the app version reported by the client (e.g. the React app's build-time APP_VERSION)
+ ///
+ [JsonProperty("appVersion")]
+ public string AppVersion { get; set; }
+
+ ///
+ /// Gets or sets the expected app version from the system config's versions.touchpanelWrapperApp, if configured
+ ///
+ [JsonProperty("expectedAppVersion")]
+ public string ExpectedAppVersion { get; set; }
+
+ ///
+ /// Gets or sets the UTC time the client last reported this version
+ ///
+ [JsonProperty("lastSeen")]
+ public DateTime LastSeen { get; set; }
+ }
+}
diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs
index 3d466f5b..91fc55c5 100644
--- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs
+++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs
@@ -69,11 +69,32 @@ namespace PepperDash.Essentials
private readonly Dictionary _defaultMessengers =
new Dictionary();
+ private readonly Dictionary _connectedClientVersions =
+ new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ private readonly object _connectedClientVersionsLock = new object();
+
///
/// Get the custom messengers with subscriptions
///
public ReadOnlyDictionary Messengers => new ReadOnlyDictionary(_messengers.Values.OfType().ToDictionary(k => k.Key, v => v));
+ ///
+ /// Gets the most recently reported UI app version for each connected client, keyed by clientId
+ ///
+ public ReadOnlyDictionary ConnectedClientVersions
+ {
+ get
+ {
+ lock (_connectedClientVersionsLock)
+ {
+ return new ReadOnlyDictionary(
+ new Dictionary(_connectedClientVersions)
+ );
+ }
+ }
+ }
+
///
/// Get the default messengers
///
@@ -1782,6 +1803,28 @@ namespace PepperDash.Essentials
" Not Enabled in Config.\r\n"
);
}
+
+ var connectedClientVersions = ConnectedClientVersions;
+
+ if (connectedClientVersions.Count == 0)
+ {
+ CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions: None reported yet\r\n");
+ }
+ else
+ {
+ CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions:\r\n");
+ foreach (var kv in connectedClientVersions)
+ {
+ var v = kv.Value;
+ var match = string.IsNullOrEmpty(v.ExpectedAppVersion) || string.Equals(v.ExpectedAppVersion, v.AppVersion, StringComparison.OrdinalIgnoreCase);
+
+ CrestronConsole.ConsoleCommandResponse(
+ $" Client: {v.ClientId} Touchpanel: {v.TouchpanelKey} Room: {v.RoomKey}\r\n" +
+ $" Reported: {v.AppVersion} Expected: {v.ExpectedAppVersion ?? "(not configured)"} Match: {(match ? "Yes" : "NO - MISMATCH")}\r\n" +
+ $" Last Seen (UTC): {v.LastSeen:yyyy-MM-dd HH:mm:ss}\r\n"
+ );
+ }
+ }
}
///
@@ -2181,6 +2224,8 @@ namespace PepperDash.Essentials
var roomKey = content["roomKey"].Value();
var touchpanelKey = content.SelectToken("touchpanelKey");
+ TrackClientAppVersion(clientId, roomKey, touchpanelKey?.Value(), content.SelectToken("appVersion")?.Value());
+
if (_roomCombiner == null)
{
var message = new MobileControlMessage
@@ -2252,6 +2297,50 @@ namespace PepperDash.Essentials
SendTouchpanelKey(clientId, touchpanelKey);
}
+ ///
+ /// Records the app version reported by a connecting UI client (e.g. the mobile control React app's
+ /// build-time APP_VERSION) and compares it against the configured versions.touchpanelWrapperApp version.
+ ///
+ private void TrackClientAppVersion(string clientId, string roomKey, string touchpanelKey, string appVersion)
+ {
+ if (string.IsNullOrEmpty(appVersion))
+ {
+ return;
+ }
+
+ var expectedVersion = ConfigReader.ConfigObject?.Versions?.TouchpanelWrapperApp?.Version;
+
+ var info = new ConnectedClientVersionInfo
+ {
+ ClientId = clientId,
+ RoomKey = roomKey,
+ TouchpanelKey = touchpanelKey,
+ AppVersion = appVersion,
+ ExpectedAppVersion = expectedVersion,
+ LastSeen = DateTime.UtcNow
+ };
+
+ lock (_connectedClientVersionsLock)
+ {
+ _connectedClientVersions[clientId] = info;
+ }
+
+ if (!string.IsNullOrEmpty(expectedVersion) && !string.Equals(expectedVersion, appVersion, StringComparison.OrdinalIgnoreCase))
+ {
+ this.LogWarning(
+ "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}, which does not match configured versions.touchpanelWrapperApp version {expectedVersion}",
+ clientId, touchpanelKey, appVersion, expectedVersion
+ );
+ }
+ else
+ {
+ this.LogVerbose(
+ "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}",
+ clientId, touchpanelKey, appVersion
+ );
+ }
+ }
+
private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken)
{
if (touchpanelKeyToken == null)