Merge branch 'development' into hotfix/techroom-preset-recall-event

This commit is contained in:
Andrew Welker
2021-05-24 13:19:45 -06:00
committed by GitHub
24 changed files with 9150 additions and 6642 deletions

27
.github/ISSUE_TEMPLATE/rfi_request.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Request for Information
about: Request specific information about capabilities of the framework
title: "[RFI]-"
labels: RFI
assignees: ''
---
**What is your request?**
Please provide as much detail as possible.
**What is the intended use case**
- [ ] Essentials Standalone Application
- [ ] Essentials + SIMPL Windows Hybrid
**User Interface Requirements**
- [ ] Not Applicable (logic only)
- [ ] Crestron Smart Graphics Touchpanel
- [ ] Cisco Touch10
- [ ] Mobile Control
- [ ] Crestron CH5 Touchpanel interface
**Additional context**
Add any other context or screenshots about the request here.

View File

@@ -36,6 +36,7 @@ namespace PepperDash.Essentials
Thread.MaxNumberOfUserThreads = 400; Thread.MaxNumberOfUserThreads = 400;
Global.ControlSystem = this; Global.ControlSystem = this;
DeviceManager.Initialize(this); DeviceManager.Initialize(this);
SecretsManager.Initialize();
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
} }

View File

@@ -25,6 +25,7 @@ namespace PepperDash.Essentials.Core
public GenericComm(DeviceConfig config) public GenericComm(DeviceConfig config)
: base(config) : base(config)
{ {
PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); PropertiesConfig = CommFactory.GetControlPropertiesConfig(config);
var commPort = CommFactory.CreateCommForDevice(config); var commPort = CommFactory.CreateCommForDevice(config);

View File

@@ -31,6 +31,18 @@ namespace PepperDash.Essentials.Core.Config
[JsonProperty("properties")] [JsonProperty("properties")]
[JsonConverter(typeof(DevicePropertiesConverter))] [JsonConverter(typeof(DevicePropertiesConverter))]
public JToken Properties { get; set; } public JToken Properties { get; set; }
public DeviceConfig(DeviceConfig dc)
{
Key = dc.Key;
Uid = dc.Uid;
Name = dc.Name;
Group = dc.Group;
Type = dc.Type;
Properties = JToken.FromObject(dc.Properties);
}
public DeviceConfig() {}
} }
/// <summary> /// <summary>

View File

@@ -54,11 +54,11 @@ namespace PepperDash.Essentials.Core.Config
{ {
bool success = false; bool success = false;
var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(config.Key)); var deviceConfigIndex = ConfigReader.ConfigObject.Devices.FindIndex(d => d.Key.Equals(config.Key));
if (deviceConfig != null) if (deviceConfigIndex >= 0)
{ {
deviceConfig = config; ConfigReader.ConfigObject.Devices[deviceConfigIndex] = config;
Debug.Console(1, "Updated config of device: '{0}'", config.Key); Debug.Console(1, "Updated config of device: '{0}'", config.Key);
@@ -74,13 +74,13 @@ namespace PepperDash.Essentials.Core.Config
{ {
bool success = false; bool success = false;
var deviceConfig = ConfigReader.ConfigObject.Rooms.FirstOrDefault(d => d.Key.Equals(config.Key)); var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
if (deviceConfig != null) if (roomConfigIndex >= 0)
{ {
deviceConfig = config; ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config;
Debug.Console(1, "Updated config of device: '{0}'", config.Key); Debug.Console(1, "Updated room of device: '{0}'", config.Key);
success = true; success = true;
} }

View File

@@ -7,6 +7,8 @@ using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PepperDash.Essentials.Core.Devices namespace PepperDash.Essentials.Core.Devices
{ {
@@ -52,6 +54,8 @@ namespace PepperDash.Essentials.Core.Devices
Name = config.Name; Name = config.Name;
} }
/// <summary> /// <summary>
/// Used by the extending class to allow for any custom actions to be taken (tell the ConfigWriter to write config, etc) /// Used by the extending class to allow for any custom actions to be taken (tell the ConfigWriter to write config, etc)
/// </summary> /// </summary>

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PepperDash.Essentials.Core
{
public static class JsonExtensions
{
public static List<JToken> FindTokens(this JToken containerToken, string name)
{
List<JToken> matches = new List<JToken>();
FindTokens(containerToken, name, matches);
return matches;
}
private static void FindTokens(JToken containerToken, string name, List<JToken> matches)
{
if (containerToken.Type == JTokenType.Object)
{
foreach (JProperty child in containerToken.Children<JProperty>())
{
if (child.Name == name)
{
matches.Add(child.Value);
}
FindTokens(child.Value, name, matches);
}
}
else if (containerToken.Type == JTokenType.Array)
{
foreach (JToken child in containerToken.Children())
{
FindTokens(child, name, matches);
}
}
}
}
}

View File

@@ -7,6 +7,8 @@ using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO; using Crestron.SimplSharpPro.GeneralIO;
using Crestron.SimplSharp.Reflection; using Crestron.SimplSharp.Reflection;
using PepperDash.Core; using PepperDash.Core;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.CrestronIO; using PepperDash.Essentials.Core.CrestronIO;
@@ -85,6 +87,35 @@ namespace PepperDash.Essentials.Core
DeviceFactory.FactoryMethods.Add(typeName, wrapper); DeviceFactory.FactoryMethods.Add(typeName, wrapper);
} }
private static void CheckForSecrets(IEnumerable<JProperty> obj)
{
foreach (var prop in obj.Where(prop => prop.Value as JObject != null))
{
if (prop.Name.ToLower() == "secret")
{
var secret = GetSecret(prop.Children().First().ToObject<SecretsPropertiesConfig>());
//var secret = GetSecret(JsonConvert.DeserializeObject<SecretsPropertiesConfig>(prop.Children().First().ToString()));
prop.Parent.Replace(secret);
}
var recurseProp = prop.Value as JObject;
if (recurseProp == null) return;
CheckForSecrets(recurseProp.Properties());
}
}
private static string GetSecret(SecretsPropertiesConfig data)
{
var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider);
if (secretProvider == null) return null;
var secret = secretProvider.GetSecret(data.Key);
if (secret != null) return (string) secret.Value;
Debug.Console(1,
"Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider",
data.Provider, data.Key);
return String.Empty;
}
/// <summary> /// <summary>
/// The factory method for Core "things". Also iterates the Factory methods that have /// The factory method for Core "things". Also iterates the Factory methods that have
/// been loaded from plugins /// been loaded from plugins
@@ -93,22 +124,41 @@ namespace PepperDash.Essentials.Core
/// <returns></returns> /// <returns></returns>
public static IKeyed GetDevice(DeviceConfig dc) public static IKeyed GetDevice(DeviceConfig dc)
{ {
var key = dc.Key; try
var name = dc.Name;
var type = dc.Type;
var properties = dc.Properties;
var typeName = dc.Type.ToLower();
// Check for types that have been added by plugin dlls.
if (FactoryMethods.ContainsKey(typeName))
{ {
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading '{0}' from Essentials Core", dc.Type); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading '{0}' from Essentials Core", dc.Type);
return FactoryMethods[typeName].FactoryMethod(dc);
var localDc = new DeviceConfig(dc);
var key = localDc.Key;
var name = localDc.Name;
var type = localDc.Type;
var properties = localDc.Properties;
//var propRecurse = properties;
var typeName = localDc.Type.ToLower();
var jObject = properties as JObject;
if (jObject != null)
{
var jProp = jObject.Properties();
CheckForSecrets(jProp);
} }
Debug.Console(2, "typeName = {0}", typeName);
// Check for types that have been added by plugin dlls.
return !FactoryMethods.ContainsKey(typeName) ? null : FactoryMethods[typeName].FactoryMethod(localDc);
}
catch (Exception ex)
{
Debug.Console(0, Debug.ErrorLogLevel.Error, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message);
Debug.Console(2, "{0}", ex.StackTrace);
return null; return null;
} }
}
/// <summary> /// <summary>
/// Prints the type names and associated metadata from the FactoryMethods collection. /// Prints the type names and associated metadata from the FactoryMethods collection.

View File

@@ -209,6 +209,7 @@
<Compile Include="DeviceTypeInterfaces\IHasFarEndContentStatus.cs" /> <Compile Include="DeviceTypeInterfaces\IHasFarEndContentStatus.cs" />
<Compile Include="DeviceTypeInterfaces\IHasPhoneDialing.cs" /> <Compile Include="DeviceTypeInterfaces\IHasPhoneDialing.cs" />
<Compile Include="DeviceTypeInterfaces\IMobileControl.cs" /> <Compile Include="DeviceTypeInterfaces\IMobileControl.cs" />
<Compile Include="Extensions\JsonExtensions.cs" />
<Compile Include="Factory\DeviceFactory.cs" /> <Compile Include="Factory\DeviceFactory.cs" />
<Compile Include="Factory\IDeviceFactory.cs" /> <Compile Include="Factory\IDeviceFactory.cs" />
<Compile Include="Factory\ReadyEventArgs.cs" /> <Compile Include="Factory\ReadyEventArgs.cs" />
@@ -320,6 +321,10 @@
<Compile Include="Feedbacks\BoolFeedbackPulseExtender.cs" /> <Compile Include="Feedbacks\BoolFeedbackPulseExtender.cs" />
<Compile Include="Routing\RoutingPortNames.cs" /> <Compile Include="Routing\RoutingPortNames.cs" />
<Compile Include="Routing\TieLineConfig.cs" /> <Compile Include="Routing\TieLineConfig.cs" />
<Compile Include="Secrets\CrestronSecretsProvider.cs" />
<Compile Include="Secrets\Interfaces.cs" />
<Compile Include="Secrets\SecretsManager.cs" />
<Compile Include="Secrets\SecretsPropertiesConfig.cs" />
<Compile Include="Shades\Shade Interfaces.cs" /> <Compile Include="Shades\Shade Interfaces.cs" />
<Compile Include="Shades\ShadeBase.cs" /> <Compile Include="Shades\ShadeBase.cs" />
<Compile Include="Shades\ShadeController.cs" /> <Compile Include="Shades\ShadeController.cs" />

View File

@@ -123,14 +123,24 @@ namespace PepperDash.Essentials.Core
// No direct tie? Run back out on the inputs' attached devices... // No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices // Only the ones that are routing devices
var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints) foreach (var inputTieToTry in attachedMidpoints)
{ {
Debug.Console(2, destination, "Trying to find route on {0}", inputTieToTry.SourcePort.ParentDevice.Key);
var upstreamDeviceOutputPort = inputTieToTry.SourcePort; var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.Console(2, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key);
// Check if this previous device has already been walked // Check if this previous device has already been walked
if (!(alreadyCheckedDevices != null && alreadyCheckedDevices.Contains(upstreamRoutingDevice))) if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
{ {
Debug.Console(2, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key);
continue;
}
// haven't seen this device yet. Do it. Pass the output port to the next // haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success // level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
@@ -143,7 +153,6 @@ namespace PepperDash.Essentials.Core
} }
} }
} }
}
// we have a route on corresponding inputPort. *** Do the route *** // we have a route on corresponding inputPort. *** Do the route ***
if (goodInputPort != null) if (goodInputPort != null)
@@ -164,10 +173,6 @@ namespace PepperDash.Essentials.Core
return true; return true;
} }
if(alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
Debug.Console(2, destination, "No route found to {0}", source.Key); Debug.Console(2, destination, "No route found to {0}", source.Key);
return false; return false;
} }

View File

@@ -0,0 +1,97 @@
using System;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronDataStore;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public class CrestronSecretsProvider : ISecretProvider
{
public string Key { get; set; }
//Added for reference
private static readonly bool SecureSupported;
public CrestronSecretsProvider(string key)
{
Key = key;
}
static CrestronSecretsProvider()
{
//Added for future encrypted reference
SecureSupported = CrestronSecureStorage.Supported;
CrestronDataStoreStatic.InitCrestronDataStore();
if (SecureSupported)
{
//doThingsFuture
}
}
/// <summary>
/// Set secret for item in the CrestronSecretsProvider
/// </summary>
/// <param name="key">Secret Key</param>
/// <param name="value">Secret Value</param>
public bool SetSecret(string key, object value)
{
var secret = value as string;
if (String.IsNullOrEmpty(secret))
{
Debug.Console(2, this, "Unable to set secret for {0}:{1} - value is empty.", Key, key);
return false;
}
var setErrorCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret);
switch (setErrorCode)
{
case CrestronDataStore.CDS_ERROR.CDS_SUCCESS:
Debug.Console(1, this,"Secret Successfully Set for {0}:{1}", Key, key);
return true;
default:
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Unable to set secret for {0}:{1} - {2}", Key, key, setErrorCode.ToString());
return false;
}
}
/// <summary>
/// Retrieve secret for item in the CrestronSecretsProvider
/// </summary>
/// <param name="key">Secret Key</param>
/// <returns>ISecret Object containing key, provider, and value</returns>
public ISecret GetSecret(string key)
{
string mySecret;
var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret);
switch (getErrorCode)
{
case CrestronDataStore.CDS_ERROR.CDS_SUCCESS:
Debug.Console(2, this, "Secret Successfully retrieved for {0}:{1}", Key, key);
return new CrestronSecret(key, mySecret, this);
default:
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to retrieve secret for {0}:{1} - {2}",
Key, key, getErrorCode.ToString());
return null;
}
}
}
/// <summary>
/// Special container class for CrestronSecret provider
/// </summary>
public class CrestronSecret : ISecret
{
public ISecretProvider Provider { get; private set; }
public string Key { get; private set; }
public object Value { get; private set; }
public CrestronSecret(string key, string value, ISecretProvider provider)
{
Key = key;
Value = value;
Provider = provider;
}
}
}

View File

@@ -0,0 +1,24 @@
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// All ISecrecretProvider classes must implement this interface.
/// </summary>
public interface ISecretProvider : IKeyed
{
bool SetSecret(string key, object value);
ISecret GetSecret(string key);
}
/// <summary>
/// interface for delivering secrets in Essentials.
/// </summary>
public interface ISecret
{
ISecretProvider Provider { get; }
string Key { get; }
object Value { get; }
}
}

View File

@@ -0,0 +1,281 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public static class SecretsManager
{
public static Dictionary<string, ISecretProvider> Secrets { get; private set; }
/// <summary>
/// Initialize the SecretsManager
/// </summary>
public static void Initialize()
{
AddSecretProvider("default", new CrestronSecretsProvider("default"));
CrestronConsole.AddNewConsoleCommand(SetSecretProcess, "setsecret",
"Adds secrets to secret provider",
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(UpdateSecretProcess, "updatesecret",
"Updates secrets in secret provider",
ConsoleAccessLevelEnum.AccessAdministrator);
CrestronConsole.AddNewConsoleCommand(DeleteSecretProcess, "deletesecret",
"Deletes secrets in secret provider",
ConsoleAccessLevelEnum.AccessAdministrator);
}
static SecretsManager()
{
Secrets = new Dictionary<string, ISecretProvider>();
}
/// <summary>
/// Get Secret Provider from dictionary by key
/// </summary>
/// <param name="key">Dictionary Key for provider</param>
/// <returns>ISecretProvider</returns>
public static ISecretProvider GetSecretProviderByKey(string key)
{
ISecretProvider secret;
Secrets.TryGetValue(key, out secret);
if (secret == null)
{
Debug.Console(1, "SecretsManager unable to retrieve SecretProvider with the key '{0}'", key);
}
return secret;
}
/// <summary>
/// Add secret provider to secrets dictionary
/// </summary>
/// <param name="key">Key of new entry</param>
/// <param name="provider">New Provider Entry</param>
public static void AddSecretProvider(string key, ISecretProvider provider)
{
if (!Secrets.ContainsKey(key))
{
Secrets.Add(key, provider);
Debug.Console(1, "Secrets provider '{0}' added to SecretsManager", key);
}
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key );
}
/// <summary>
/// Add secret provider to secrets dictionary, with optional overwrite parameter
/// </summary>
/// <param name="key">Key of new entry</param>
/// <param name="provider">New provider entry</param>
/// <param name="overwrite">true to overwrite any existing providers in the dictionary</param>
public static void AddSecretProvider(string key, ISecretProvider provider, bool overwrite)
{
if (!Secrets.ContainsKey(key))
{
Secrets.Add(key, provider);
Debug.Console(1, "Secrets provider '{0}' added to SecretsManager", key);
}
if (overwrite)
{
Secrets.Add(key, provider);
Debug.Console(1, Debug.ErrorLogLevel.Notice, "Provider with the key '{0}' already exists in secrets. Overwriting with new secrets provider.", key);
}
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key);
}
private static void SetSecretProcess(string cmd)
{
string response;
var args = cmd.Split(' ');
if (args.Length == 0)
{
//some Instructional Text
response = "Adds secrets to secret provider. Format 'setsecret <provider> <secretKey> <secret>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length == 1 && args[0] == "?")
{
response = "Adds secrets to secret provider. Format 'setsecret <provider> <secretKey> <secret>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length < 3)
{
response = "Improper number of arguments";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var provider = GetSecretProviderByKey(args[0]);
if (provider == null)
{
//someFail
response = "Provider key invalid";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var key = args[1];
var secret = args[2];
if (provider.GetSecret(key) == null)
{
response = provider.SetSecret(key, secret)
? String.Format(
"Secret successfully set for {0}:{1}",
provider.Key, key)
: String.Format(
"Unable to set secret for {0}:{1}",
provider.Key, key);
CrestronConsole.ConsoleCommandResponse(response);
return;
}
response =
String.Format(
"Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it");
CrestronConsole.ConsoleCommandResponse(response);
}
private static void UpdateSecretProcess(string cmd)
{
string response;
var args = cmd.Split(' ');
if (args.Length == 0)
{
//some Instructional Text
response = "Updates secrets in secret provider. Format 'updatesecret <provider> <secretKey> <secret>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length == 1 && args[0] == "?")
{
response = "Updates secrets in secret provider. Format 'updatesecret <provider> <secretKey> <secret>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length < 3)
{
//someFail
response = "Improper number of arguments";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var provider = GetSecretProviderByKey(args[0]);
if (provider == null)
{
//someFail
response = "Provider key invalid";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var key = args[1];
var secret = args[2];
if (provider.GetSecret(key) != null)
{
response = provider.SetSecret(key, secret)
? String.Format(
"Secret successfully set for {0}:{1}",
provider.Key, key)
: String.Format(
"Unable to set secret for {0}:{1}",
provider.Key, key);
CrestronConsole.ConsoleCommandResponse(response);
return;
}
response =
String.Format(
"Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to create a new secret");
CrestronConsole.ConsoleCommandResponse(response);
}
private static void DeleteSecretProcess(string cmd)
{
string response;
var args = cmd.Split(' ');
if (args.Length == 0)
{
//some Instructional Text
response = "Deletes secrets in secret provider. Format 'deletesecret <provider> <secretKey>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length == 1 && args[0] == "?")
{
response = "Deletes secrets in secret provider. Format 'deletesecret <provider> <secretKey>";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
if (args.Length < 2)
{
//someFail
response = "Improper number of arguments";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var provider = GetSecretProviderByKey(args[0]);
if (provider == null)
{
//someFail
response = "Provider key invalid";
CrestronConsole.ConsoleCommandResponse(response);
return;
}
var key = args[1];
provider.SetSecret(key, "");
response = provider.SetSecret(key, "")
? String.Format(
"Secret successfully deleted for {0}:{1}",
provider.Key, key)
: String.Format(
"Unable to delete secret for {0}:{1}",
provider.Key, key);
CrestronConsole.ConsoleCommandResponse(response);
return;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Provide a way to easily deserialize into a secret object from config
/// </summary>
public class SecretsPropertiesConfig
{
[JsonProperty("provider")]
public string Provider { get; set; }
[JsonProperty("key")]
public string Key { get; set; }
}
}

View File

@@ -1220,7 +1220,7 @@ namespace PepperDash.Essentials.DM
{ {
dmMdMnxn.AudioEnter.BoolValue = true; dmMdMnxn.AudioEnter.BoolValue = true;
} }
output.VideoOut = input; output.AudioOut = input;
//Chassis.Outputs[output].AudioOut = inCard; //Chassis.Outputs[output].AudioOut = inCard;
} }

View File

@@ -8,6 +8,8 @@ using Crestron.SimplSharp.Reflection;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Presets; using PepperDash.Essentials.Core.Presets;
using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.Codec;
@@ -25,7 +27,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
Focus = 8 Focus = 8
} }
public abstract class CameraBase : EssentialsDevice, IRoutingOutputs public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs
{ {
public eCameraControlMode ControlMode { get; protected set; } public eCameraControlMode ControlMode { get; protected set; }
@@ -70,12 +72,18 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
// A bitmasked value to indicate the movement capabilites of this camera // A bitmasked value to indicate the movement capabilites of this camera
protected eCameraCapabilities Capabilities { get; set; } protected eCameraCapabilities Capabilities { get; set; }
protected CameraBase(string key, string name) : protected CameraBase(DeviceConfig config) : base(config)
base(key, name)
{ {
OutputPorts = new RoutingPortCollection<RoutingOutputPort>(); OutputPorts = new RoutingPortCollection<RoutingOutputPort>();
ControlMode = eCameraControlMode.Manual; ControlMode = eCameraControlMode.Manual;
}
protected CameraBase(string key, string name) :
this (new DeviceConfig{Name = name, Key = key})
{
} }
protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey, protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey,

View File

@@ -176,6 +176,7 @@
<Compile Include="VideoCodec\ZoomRoom\ResponseObjects.cs" /> <Compile Include="VideoCodec\ZoomRoom\ResponseObjects.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoom.cs" /> <Compile Include="VideoCodec\ZoomRoom\ZoomRoom.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomCamera.cs" /> <Compile Include="VideoCodec\ZoomRoom\ZoomRoomCamera.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomJoinMap.cs" />
<Compile Include="VideoCodec\ZoomRoom\ZoomRoomPropertiesConfig.cs" /> <Compile Include="VideoCodec\ZoomRoom\ZoomRoomPropertiesConfig.cs" />
<None Include="Properties\ControlSystem.cfg" /> <None Include="Properties\ControlSystem.cfg" />
</ItemGroup> </ItemGroup>

View File

@@ -19,4 +19,32 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
void LocalLayoutToggleSingleProminent(); void LocalLayoutToggleSingleProminent();
void MinMaxLayoutToggle(); void MinMaxLayoutToggle();
} }
/// <summary>
/// Defines the requirements for Zoom Room layout control
/// </summary>
public interface IHasZoomRoomLayouts : IHasCodecLayouts
{
event EventHandler<LayoutInfoChangedEventArgs> AvailableLayoutsChanged;
BoolFeedback LayoutViewIsOnFirstPageFeedback { get; } // TODO: #697 [*] Consider modifying to report button visibility in func
BoolFeedback LayoutViewIsOnLastPageFeedback { get; } // TODO: #697 [*] Consider modifying to report button visibility in func
BoolFeedback CanSwapContentWithThumbnailFeedback { get; }
BoolFeedback ContentSwappedWithThumbnailFeedback { get; }
ZoomRoom.zConfiguration.eLayoutStyle LastSelectedLayout { get; }
ZoomRoom.zConfiguration.eLayoutStyle AvailableLayouts { get; }
void GetAvailableLayouts(); // Mot sure this is necessary if we're already subscribed to zStatus Call Layout
void SetLayout(ZoomRoom.zConfiguration.eLayoutStyle layoutStyle);
void SwapContentWithThumbnail();
void LayoutTurnNextPage();
void LayoutTurnPreviousPage();
}
public class LayoutInfoChangedEventArgs : EventArgs
{
public ZoomRoom.zConfiguration.eLayoutStyle AvailableLayouts { get; set; }
}
} }

View File

@@ -1,13 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
{ {
/// <summary>
/// Describes a device that has call participants
/// </summary>
public interface IHasParticipants public interface IHasParticipants
{ {
CodecParticipants Participants { get; } CodecParticipants Participants { get; }
} }
/// <summary>
/// Describes the ability to mute and unmute a participant's video in a meeting
/// </summary>
public interface IHasParticipantVideoMute:IHasParticipants public interface IHasParticipantVideoMute:IHasParticipants
{ {
void MuteVideoForParticipant(int userId); void MuteVideoForParticipant(int userId);
@@ -15,6 +22,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
void ToggleVideoForParticipant(int userId); void ToggleVideoForParticipant(int userId);
} }
/// <summary>
/// Describes the ability to mute and unmute a participant's audio in a meeting
/// </summary>
public interface IHasParticipantAudioMute : IHasParticipantVideoMute public interface IHasParticipantAudioMute : IHasParticipantVideoMute
{ {
void MuteAudioForParticipant(int userId); void MuteAudioForParticipant(int userId);
@@ -22,6 +32,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
void ToggleAudioForParticipant(int userId); void ToggleAudioForParticipant(int userId);
} }
/// <summary>
/// Describes the ability to pin and unpin a participant in a meeting
/// </summary>
public interface IHasParticipantPinUnpin : IHasParticipants
{
IntFeedback NumberOfScreensFeedback { get; }
int ScreenIndexToPinUserTo { get; }
void PinParticipant(int userId, int screenIndex);
void UnPinParticipant(int userId);
void ToggleParticipantPinState(int userId, int screenIndex);
}
public class CodecParticipants public class CodecParticipants
{ {
private List<Participant> _currentParticipants; private List<Participant> _currentParticipants;
@@ -31,11 +54,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
set set
{ {
_currentParticipants = value; _currentParticipants = value;
var handler = ParticipantsListHasChanged; OnParticipantsChanged();
if(handler == null) return;
handler(this, new EventArgs());
} }
} }
@@ -45,15 +64,31 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces
{ {
_currentParticipants = new List<Participant>(); _currentParticipants = new List<Participant>();
} }
public void OnParticipantsChanged()
{
var handler = ParticipantsListHasChanged;
if (handler == null) return;
handler(this, new EventArgs());
}
} }
/// <summary>
/// Represents a call participant
/// </summary>
public class Participant public class Participant
{ {
public int UserId { get; set; }
public bool IsHost { get; set; } public bool IsHost { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool CanMuteVideo { get; set; } public bool CanMuteVideo { get; set; }
public bool CanUnmuteVideo { get; set; } public bool CanUnmuteVideo { get; set; }
public bool VideoMuteFb { get; set; } public bool VideoMuteFb { get; set; }
public bool AudioMuteFb { get; set; } public bool AudioMuteFb { get; set; }
public bool HandIsRaisedFb { get; set; }
public bool IsPinnedFb { get; set; }
public int ScreenIndexIsPinnedToFb { get; set; }
} }
} }

View File

@@ -28,6 +28,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced
{ {
private const int XSigEncoding = 28591; private const int XSigEncoding = 28591;
protected const int MaxParticipants = 50;
private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs(); private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs();
protected VideoCodecBase(DeviceConfig config) protected VideoCodecBase(DeviceConfig config)
: base(config) : base(config)
@@ -271,6 +272,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
/// <summary>
/// Use this method when using a plain VideoCodecControllerJoinMap
/// </summary>
/// <param name="codec"></param>
/// <param name="trilist"></param>
/// <param name="joinStart"></param>
/// <param name="joinMapKey"></param>
/// <param name="bridge"></param>
protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, uint joinStart, string joinMapKey, protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge) EiscApiAdvanced bridge)
{ {
@@ -288,10 +297,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
bridge.AddJoinMap(Key, joinMap); bridge.AddJoinMap(Key, joinMap);
} }
LinkVideoCodecToApi(codec, trilist, joinMap);
}
/// <summary>
/// Use this method when you need to pass in a join map that extends VideoCodecControllerJoinMap
/// </summary>
/// <param name="codec"></param>
/// <param name="trilist"></param>
/// <param name="joinMap"></param>
protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap)
{
Debug.Console(1, this, "Linking to Trilist {0}", trilist.ID.ToString("X")); Debug.Console(1, this, "Linking to Trilist {0}", trilist.ID.ToString("X"));
LinkVideoCodecDtmfToApi(trilist, joinMap); LinkVideoCodecDtmfToApi(trilist, joinMap);
LinkVideoCodecCallControlsToApi(trilist, joinMap); LinkVideoCodecCallControlsToApi(trilist, joinMap);
@@ -524,6 +542,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
return; return;
} }
SetParticipantActions(trilist, joinMap, codec.Participants.CurrentParticipants);
participantsXSig = UpdateParticipantsXSig(codec.Participants.CurrentParticipants); participantsXSig = UpdateParticipantsXSig(codec.Participants.CurrentParticipants);
trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig); trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig);
@@ -532,14 +552,59 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
}; };
} }
/// <summary>
/// Sets the actions for each participant in the list
/// </summary>
private void SetParticipantActions(BasicTriList trilist, VideoCodecControllerJoinMap joinMap, List<Participant> currentParticipants)
{
uint index = 0; // track the index of the participant in the
foreach (var participant in currentParticipants)
{
var p = participant;
if (index > MaxParticipants) break;
var audioMuteCodec = this as IHasParticipantAudioMute;
if (audioMuteCodec != null)
{
trilist.SetSigFalseAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index,
() => audioMuteCodec.ToggleAudioForParticipant(p.UserId));
trilist.SetSigFalseAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index,
() => audioMuteCodec.ToggleVideoForParticipant(p.UserId));
}
var pinCodec = this as IHasParticipantPinUnpin;
if (pinCodec != null)
{
trilist.SetSigFalseAction(joinMap.ParticipantPinToggleStart.JoinNumber + index,
() => pinCodec.ToggleParticipantPinState(p.UserId, pinCodec.ScreenIndexToPinUserTo));
}
index++;
}
// Clear out any previously set actions
while (index < MaxParticipants)
{
trilist.ClearBoolSigAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index);
trilist.ClearBoolSigAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index);
trilist.ClearBoolSigAction(joinMap.ParticipantPinToggleStart.JoinNumber + index);
index++;
}
}
private string UpdateParticipantsXSig(List<Participant> currentParticipants) private string UpdateParticipantsXSig(List<Participant> currentParticipants)
{ {
const int maxParticipants = 50; const int maxParticipants = MaxParticipants;
const int maxDigitals = 5; const int maxDigitals = 7;
const int maxStrings = 1; const int maxStrings = 1;
const int offset = maxDigitals + maxStrings; const int maxAnalogs = 1;
var digitalIndex = maxStrings * maxParticipants; //15 const int offset = maxDigitals + maxStrings + maxAnalogs; // 9
var digitalIndex = (maxStrings + maxAnalogs) * maxParticipants; // 100
var stringIndex = 0; var stringIndex = 0;
var analogIndex = stringIndex + maxParticipants;
var meetingIndex = 0; var meetingIndex = 0;
var tokenArray = new XSigToken[maxParticipants * offset]; var tokenArray = new XSigToken[maxParticipants * offset];
@@ -554,29 +619,42 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, participant.CanMuteVideo); tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, participant.CanMuteVideo);
tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, participant.CanUnmuteVideo); tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, participant.CanUnmuteVideo);
tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, participant.IsHost); tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, participant.IsHost);
tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, participant.HandIsRaisedFb);
tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, participant.IsPinnedFb);
//serials //serials
tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, participant.Name); tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, participant.Name);
//analogs
tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, (ushort)participant.ScreenIndexIsPinnedToFb);
digitalIndex += maxDigitals; digitalIndex += maxDigitals;
meetingIndex += offset; meetingIndex += offset;
stringIndex += maxStrings; stringIndex += maxStrings;
analogIndex += maxAnalogs;
} }
while (meetingIndex < maxParticipants * offset) while (meetingIndex < maxParticipants * offset)
{ {
//digitals
tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false);
tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false);
tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, false); tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, false);
tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, false); tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, false);
tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, false); tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, false);
tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, false);
tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, false);
//serials //serials
tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty);
//analogs
tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, 0);
digitalIndex += maxDigitals; digitalIndex += maxDigitals;
meetingIndex += offset; meetingIndex += offset;
stringIndex += maxStrings; stringIndex += maxStrings;
analogIndex += maxAnalogs;
} }
return GetXSigString(tokenArray); return GetXSigString(tokenArray);
@@ -595,7 +673,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, (b) => AutoShareContentWhileInCall = b); trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, (b) => AutoShareContentWhileInCall = b);
} }
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
private List<Meeting> _currentMeetings = new List<Meeting>(); private List<Meeting> _currentMeetings = new List<Meeting>();
private void LinkVideoCodecScheduleToApi(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) private void LinkVideoCodecScheduleToApi(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap)
@@ -607,7 +684,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
codec.CodecSchedule.MeetingWarningMinutes = i; codec.CodecSchedule.MeetingWarningMinutes = i;
}); });
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
trilist.SetSigFalseAction(joinMap.DialMeeting1.JoinNumber, () => trilist.SetSigFalseAction(joinMap.DialMeeting1.JoinNumber, () =>
{ {
var mtg = 1; var mtg = 1;
@@ -617,7 +693,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
if (_currentMeetings[index] != null) if (_currentMeetings[index] != null)
Dial(_currentMeetings[index]); Dial(_currentMeetings[index]);
}); });
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
trilist.SetSigFalseAction(joinMap.DialMeeting2.JoinNumber, () => trilist.SetSigFalseAction(joinMap.DialMeeting2.JoinNumber, () =>
{ {
var mtg = 2; var mtg = 2;
@@ -627,7 +703,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
if (_currentMeetings[index] != null) if (_currentMeetings[index] != null)
Dial(_currentMeetings[index]); Dial(_currentMeetings[index]);
}); });
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
trilist.SetSigFalseAction(joinMap.DialMeeting3.JoinNumber, () => trilist.SetSigFalseAction(joinMap.DialMeeting3.JoinNumber, () =>
{ {
var mtg = 3; var mtg = 3;
@@ -652,14 +728,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
{ {
var currentTime = DateTime.Now; var currentTime = DateTime.Now;
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
// - changed var currentMeetings >> field _currentMeetings
//_currentMeetings.Clear();
_currentMeetings = codec.CodecSchedule.Meetings.Where(m => m.StartTime >= currentTime || m.EndTime >= currentTime).ToList(); _currentMeetings = codec.CodecSchedule.Meetings.Where(m => m.StartTime >= currentTime || m.EndTime >= currentTime).ToList();
// TODO [ ] 2021-01-06, jkd: Added to debug OBTP dialing issues
// - moved the trilist.SetSigFlaseAction(joinMap.DialMeeting1..3.JoinNumber) lambda's to LinkVideoCodecScheduleToApi
var meetingsData = UpdateMeetingsListXSig(_currentMeetings); var meetingsData = UpdateMeetingsListXSig(_currentMeetings);
trilist.SetString(joinMap.Schedule.JoinNumber, meetingsData); trilist.SetString(joinMap.Schedule.JoinNumber, meetingsData);
trilist.SetUshort(joinMap.MeetingCount.JoinNumber, (ushort)_currentMeetings.Count); trilist.SetUshort(joinMap.MeetingCount.JoinNumber, (ushort)_currentMeetings.Count);

View File

@@ -480,12 +480,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public string wifiName { get; set; } public string wifiName { get; set; }
} }
public class NumberOfScreens public class NumberOfScreens : NotifiableObject
{ {
private int _numOfScreens;
[JsonProperty("NumberOfCECScreens")] [JsonProperty("NumberOfCECScreens")]
public int NumOfCECScreens { get; set; } public int NumOfCECScreens { get; set; }
[JsonProperty("NumberOfScreens")] [JsonProperty("NumberOfScreens")]
public int NumOfScreens { get; set; } public int NumOfScreens
{
get
{
return _numOfScreens;
}
set
{
if (value != _numOfScreens)
{
_numOfScreens = value;
NotifyPropertyChanged("NumberOfScreens");
}
}
}
} }
/// <summary> /// <summary>
@@ -551,18 +567,136 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
} }
public class Layout public class Layout : NotifiableObject
{ {
// backer variables
private bool _can_Switch_Speaker_View;
private bool _can_Switch_Wall_View;
private bool _can_Switch_Share_On_All_Screens;
private bool _is_In_First_Page;
private bool _is_In_Last_Page;
private string _video_type;
public bool can_Adjust_Floating_Video { get; set; } public bool can_Adjust_Floating_Video { get; set; }
public bool can_Switch_Floating_Share_Content { get; set; } public bool can_Switch_Floating_Share_Content { get; set; }
public bool can_Switch_Share_On_All_Screens { get; set; }
public bool can_Switch_Speaker_View { get; set; } /// <summary>
public bool can_Switch_Wall_View { get; set; } /// [on/off] // Set to On if it is possible to invoke zConfiguration Call Layout Style: ShareAll, to switch to the ShareAll mode, where the content sharing is shown full screen on all monitors.
public bool is_In_First_Page { get; set; } /// </summary>
public bool is_In_Last_Page { get; set; } [JsonProperty("can_Switch_Share_On_All_Screens")]
public bool can_Switch_Share_On_All_Screens
{
get
{
return _can_Switch_Share_On_All_Screens;
}
set
{
if (value != _can_Switch_Share_On_All_Screens)
{
_can_Switch_Share_On_All_Screens = value;
NotifyPropertyChanged("can_Switch_Share_On_All_Screens");
}
}
}
/// <summary>
/// [on/off] // Set to On if it is possible to switch to Speaker view by invoking zConfiguration Call Layout Style: Speaker. The active speaker is shown full screen, and other video streams, like self-view, are shown in thumbnails.
/// </summary>
[JsonProperty("can_Switch_Speaker_View")]
public bool can_Switch_Speaker_View
{
get
{
return _can_Switch_Speaker_View;
}
set
{
if (value != _can_Switch_Speaker_View)
{
_can_Switch_Speaker_View = value;
NotifyPropertyChanged("can_Switch_Speaker_View");
}
}
}
/// <summary>
/// [on/off] On if it is possible to invoke zConfiguration Call Layout Style: Gallery, to switch to the Gallery mode, showing video participants in tiled windows: The Zoom Room shows up to a 5x5 array of tiled windows per page.
/// </summary>
[JsonProperty("can_Switch_Wall_View")]
public bool can_Switch_Wall_View
{
get
{
return _can_Switch_Wall_View;
}
set
{
if (value != _can_Switch_Wall_View)
{
_can_Switch_Wall_View = value;
NotifyPropertyChanged("can_Switch_Wall_View");
}
}
}
[JsonProperty("is_In_First_Page")]
public bool is_In_First_Page
{
get
{
return _is_In_First_Page;
}
set
{
if (value != _is_In_First_Page)
{
_is_In_First_Page = value;
NotifyPropertyChanged("is_In_First_Page");
}
}
}
[JsonProperty("is_In_Last_Page")]
public bool is_In_Last_Page
{
get
{
return _is_In_Last_Page;
}
set
{
if (value != _is_In_Last_Page)
{
_is_In_Last_Page = value;
NotifyPropertyChanged("is_In_Last_Page");
}
}
}
public bool is_supported { get; set; } public bool is_supported { get; set; }
public int video_Count_In_Current_Page { get; set; } public int video_Count_In_Current_Page { get; set; }
public string video_type { get; set; }
/// <summary>
/// [Gallery | Strip] Indicates which mode applies: Strip or Gallery.
/// </summary>
[JsonProperty("video_type")]
public string video_type
{
get
{
return _video_type;
}
set
{
if (value != _video_type)
{
_video_type = value;
NotifyPropertyChanged("video_type");
}
}
}
} }
public class CallRecordInfo public class CallRecordInfo
@@ -685,6 +819,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class PinStatusOfScreenNotification public class PinStatusOfScreenNotification
{ {
[JsonProperty("can_be_pinned")] [JsonProperty("can_be_pinned")]
public bool CanBePinned { get; set; } public bool CanBePinned { get; set; }
[JsonProperty("can_pin_share")] [JsonProperty("can_pin_share")]
@@ -712,7 +848,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
private bool _offHook; private bool _offHook;
public string CallId { get; set; } public string CallId { get; set; }
public bool IsIncomingCall { public bool IsIncomingCall
{
get { return _isIncomingCall; } get { return _isIncomingCall; }
set set
{ {
@@ -720,7 +857,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
_isIncomingCall = value; _isIncomingCall = value;
NotifyPropertyChanged("IsIncomingCall"); NotifyPropertyChanged("IsIncomingCall");
} } }
}
public string PeerDisplayName public string PeerDisplayName
{ {
@@ -835,12 +973,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
} }
} }
[Flags]
public enum eLayoutStyle public enum eLayoutStyle
{ {
Gallery, None = 0,
Speaker, Gallery = 1,
Strip, Speaker = 2,
ShareAll Strip = 4,
ShareAll = 8,
} }
public enum eLayoutSize public enum eLayoutSize
@@ -867,18 +1007,62 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class Layout : NotifiableObject public class Layout : NotifiableObject
{ {
public bool ShareThumb { get; set; } private bool _shareThumb;
public eLayoutStyle Style { get; set; } private eLayoutStyle _style;
public eLayoutSize Size { get; set; } private eLayoutSize _size;
private eLayoutPosition _position; private eLayoutPosition _position;
public eLayoutPosition Position {
public bool ShareThumb
{
get { return _shareThumb; }
set
{
if (value != _shareThumb)
{
_shareThumb = value;
NotifyPropertyChanged("ShareThumb");
}
}
}
public eLayoutStyle Style
{
get { return _style; }
set
{
if (value != _style)
{
_style = value;
NotifyPropertyChanged("Style");
}
}
}
public eLayoutSize Size
{
get { return _size; }
set
{
if (value != _size)
{
_size = value;
NotifyPropertyChanged("Size");
}
}
}
public eLayoutPosition Position
{
get { return _position; } get { return _position; }
set set
{
if (value != _position)
{ {
_position = value; _position = value;
NotifyPropertyChanged("Position"); NotifyPropertyChanged("Position");
} } }
}
}
} }
public class Lock public class Lock
@@ -998,7 +1182,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
private string _selectedId; private string _selectedId;
[JsonProperty("selectedId")] [JsonProperty("selectedId")]
public string SelectedId { public string SelectedId
{
get get
{ {
return _selectedId; return _selectedId;
@@ -1020,6 +1205,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
{ {
public string appVersion { get; set; } public string appVersion { get; set; }
public string deviceSystem { get; set; } public string deviceSystem { get; set; }
// This doesn't belong here, but there's a bug in the object structure of Zoom Room 5.6.3 that puts it here
public zConfiguration.Call Call { get; set; }
public Client()
{
Call = new zConfiguration.Call();
}
} }
} }
@@ -1135,14 +1328,27 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class HandStatus public class HandStatus
{ {
[JsonProperty("is_raise_hand")] // example return of the "hand_status" object
// !!!! Note the properties contain ': ' within the property name !!!
//"hand_status": {
// "is_raise_hand: ": false,
// "is_valid: ": "on",
// "time_stamp: ": "11825083"
//},
[JsonProperty("is_raise_hand: ")]
public bool IsRaiseHand { get; set; } public bool IsRaiseHand { get; set; }
[JsonProperty("optimize_vis_validideo_sharing")] [JsonProperty("is_valid: ")]
public string IsValid { get; set; } public string IsValid { get; set; }
[JsonProperty("time_stamp")] [JsonProperty("time_stamp: ")]
public string TimeStamp { get; set; } public string TimeStamp { get; set; }
/// <summary>
/// Retuns a boolean value if the participant hand state is raised and is valid (both need to be true)
/// </summary>
public bool HandIsRaisedAndValid
{
get { return IsValid != null && IsValid == "on" && IsRaiseHand; }
}
} }
public class ListParticipant public class ListParticipant
{ {
[JsonProperty("audio_status state")] [JsonProperty("audio_status state")]
@@ -1205,22 +1411,87 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
HandStatus = new HandStatus(); HandStatus = new HandStatus();
} }
/// <summary>
/// Converts ZoomRoom pariticpant list response to an Essentials participant list
/// </summary>
/// <param name="participants"></param>
/// <returns></returns>
public static List<Participant> GetGenericParticipantListFromParticipantsResult( public static List<Participant> GetGenericParticipantListFromParticipantsResult(
List<ListParticipant> participants) List<ListParticipant> participants)
{ {
return //return participants.Select(p => new Participant
participants.Select( // {
p => // UserId = p.UserId,
new Participant // Name = p.UserName,
// IsHost = p.IsHost,
// CanMuteVideo = p.IsVideoCanMuteByHost,
// CanUnmuteVideo = p.IsVideoCanUnmuteByHost,
// AudioMuteFb = p.AudioStatusState == "AUDIO_MUTED",
// VideoMuteFb = p.VideoStatusIsSending,
// HandIsRaisedFb = p.HandStatus.HandIsRaisedAndValid,
// }).ToList();
var sortedParticipants = SortParticipantListByHandStatus(participants);
return sortedParticipants.Select(p => new Participant
{ {
UserId = p.UserId,
Name = p.UserName, Name = p.UserName,
IsHost = p.IsHost, IsHost = p.IsHost,
CanMuteVideo = p.IsVideoCanMuteByHost, CanMuteVideo = p.IsVideoCanMuteByHost,
CanUnmuteVideo = p.IsVideoCanUnmuteByHost, CanUnmuteVideo = p.IsVideoCanUnmuteByHost,
AudioMuteFb = p.AudioStatusState == "AUDIO_MUTED", AudioMuteFb = p.AudioStatusState == "AUDIO_MUTED",
VideoMuteFb = p.VideoStatusIsSending VideoMuteFb = p.VideoStatusIsSending,
HandIsRaisedFb = p.HandStatus.HandIsRaisedAndValid,
}).ToList(); }).ToList();
} }
/// <summary>
/// Will sort by hand-raise status and then alphabetically
/// </summary>
/// <param name="participants">Zoom Room response list of participants</param>
/// <returns>List</returns>
public static List<ListParticipant> SortParticipantListByHandStatus(List<ListParticipant> participants)
{
if (participants == null)
{
Debug.Console(1, "SortParticiapntListByHandStatu(participants == null)");
return null;
}
// debug testing
foreach (ListParticipant participant in participants)
{
Debug.Console(1, "{0} | IsValid: {1} | IsRaiseHand: {2} | HandIsRaisedAndValid: {3}",
participant.UserName, participant.HandStatus.IsValid, participant.HandStatus.IsRaiseHand.ToString(), participant.HandStatus.HandIsRaisedAndValid.ToString());
}
List<ListParticipant> handRaisedParticipantsList = participants.Where(p => p.HandStatus.HandIsRaisedAndValid).ToList();
if (handRaisedParticipantsList != null)
{
IOrderedEnumerable<ListParticipant> orderByDescending = handRaisedParticipantsList.OrderByDescending(p => p.HandStatus.TimeStamp);
foreach (var participant in handRaisedParticipantsList)
Debug.Console(1, "handRaisedParticipantList: {0} | {1}", participant.UserName, participant.UserId);
}
List<ListParticipant> allOtherParticipantsList = participants.Where(p => !p.HandStatus.HandIsRaisedAndValid).ToList();
if (allOtherParticipantsList != null)
{
allOtherParticipantsList.OrderBy(p => p.UserName);
foreach (var participant in allOtherParticipantsList)
Debug.Console(1, "allOtherParticipantsList: {0} | {1}", participant.UserName, participant.UserId);
}
// merge the lists
List<ListParticipant> sortedList = handRaisedParticipantsList.Union(allOtherParticipantsList).ToList();
// return the sorted list
return sortedList;
}
} }
public class CallinCountryList public class CallinCountryList

View File

@@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.CrestronThread;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Intersystem.Tokens;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
@@ -23,7 +25,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectoryHistoryStack, ICommunicationMonitor, public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectoryHistoryStack, ICommunicationMonitor,
IRouting, IRouting,
IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMute, IHasCameraAutoMode, IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMute, IHasCameraAutoMode,
IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing, IHasZoomRoomLayouts, IHasParticipantPinUnpin, IHasParticipantAudioMute
{ {
private const long MeetingRefreshTimer = 60000; private const long MeetingRefreshTimer = 60000;
private const uint DefaultMeetingDurationMin = 30; private const uint DefaultMeetingDurationMin = 30;
@@ -121,6 +123,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
PhoneOffHookFeedback = new BoolFeedback(PhoneOffHookFeedbackFunc); PhoneOffHookFeedback = new BoolFeedback(PhoneOffHookFeedbackFunc);
CallerIdNameFeedback = new StringFeedback(CallerIdNameFeedbackFunc); CallerIdNameFeedback = new StringFeedback(CallerIdNameFeedbackFunc);
CallerIdNumberFeedback = new StringFeedback(CallerIdNumberFeedbackFunc); CallerIdNumberFeedback = new StringFeedback(CallerIdNumberFeedbackFunc);
LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc);
LayoutViewIsOnFirstPageFeedback = new BoolFeedback(LayoutViewIsOnFirstPageFeedbackFunc);
LayoutViewIsOnLastPageFeedback = new BoolFeedback(LayoutViewIsOnLastPageFeedbackFunc);
CanSwapContentWithThumbnailFeedback = new BoolFeedback(CanSwapContentWithThumbnailFeedbackFunc);
ContentSwappedWithThumbnailFeedback = new BoolFeedback(ContentSwappedWithThumbnailFeedbackFunc);
NumberOfScreensFeedback = new IntFeedback(NumberOfScreensFeedbackFunc);
} }
public CommunicationGather PortGather { get; private set; } public CommunicationGather PortGather { get; private set; }
@@ -216,11 +228,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
} }
} }
protected Func<string> LocalLayoutFeedbackFunc
{
get { return () => ""; }
}
protected Func<bool> LocalLayoutIsProminentFeedbackFunc protected Func<bool> LocalLayoutIsProminentFeedbackFunc
{ {
get { return () => false; } get { return () => false; }
@@ -484,11 +491,55 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
Configuration.Call.Layout.PropertyChanged += (o, a) => Configuration.Call.Layout.PropertyChanged += (o, a) =>
{ {
if (a.PropertyName != "Position") return; switch (a.PropertyName)
{
case "Position":
{
ComputeSelfviewPipStatus(); ComputeSelfviewPipStatus();
SelfviewPipPositionFeedback.FireUpdate(); SelfviewPipPositionFeedback.FireUpdate();
break;
}
case "ShareThumb":
{
ContentSwappedWithThumbnailFeedback.FireUpdate();
break;
}
case "Style":
{
LocalLayoutFeedback.FireUpdate();
break;
}
}
};
// This is to deal with incorrect object structure coming back from the Zoom Room on v 5.6.3
Configuration.Client.Call.Layout.PropertyChanged += (o,a) =>
{
switch (a.PropertyName)
{
case "Position":
{
ComputeSelfviewPipStatus();
SelfviewPipPositionFeedback.FireUpdate();
break;
}
case "ShareThumb":
{
ContentSwappedWithThumbnailFeedback.FireUpdate();
break;
}
case "Style":
{
LocalLayoutFeedback.FireUpdate();
break;
}
}
}; };
Status.Call.Sharing.PropertyChanged += (o, a) => Status.Call.Sharing.PropertyChanged += (o, a) =>
@@ -542,6 +593,48 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
break; break;
} }
}; };
Status.Layout.PropertyChanged += (o, a) =>
{
switch (a.PropertyName)
{
case "can_Switch_Speaker_View":
case "can_Switch_Wall_View":
case "can_Switch_Share_On_All_Screens":
{
ComputeAvailableLayouts();
break;
}
case "is_In_First_Page":
{
LayoutViewIsOnFirstPageFeedback.FireUpdate();
break;
}
case "is_In_Last_Page":
{
LayoutViewIsOnLastPageFeedback.FireUpdate();
break;
}
//case "video_type":
// {
// It appears as though the actual value we want to watch is Configuration.Call.Layout.Style
// LocalLayoutFeedback.FireUpdate();
// break;
// }
}
};
Status.NumberOfScreens.PropertyChanged += (o, a) =>
{
switch (a.PropertyName)
{
case "NumberOfScreens":
{
NumberOfScreensFeedback.FireUpdate();
break;
}
}
};
} }
private void SetUpDirectory() private void SetUpDirectory()
@@ -777,7 +870,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
_jsonCurlyBraceCounter--; _jsonCurlyBraceCounter--;
} }
Debug.Console(2, this, "JSON Curly Brace Count: {0}", _jsonCurlyBraceCounter); //Debug.Console(2, this, "JSON Curly Brace Count: {0}", _jsonCurlyBraceCounter);
if (!_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "{" + Delimiter) if (!_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "{" + Delimiter)
// Check for the beginning of a new JSON message // Check for the beginning of a new JSON message
@@ -1220,6 +1313,38 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
case "phonecallstatus": case "phonecallstatus":
{ {
JsonConvert.PopulateObject(responseObj.ToString(), Status.PhoneCall); JsonConvert.PopulateObject(responseObj.ToString(), Status.PhoneCall);
break;
}
case "pinstatusofscreennotification":
{
var status = responseObj.ToObject<zEvent.PinStatusOfScreenNotification>();
var participant = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(status.PinnedUserId));
if (participant != null)
{
participant.IsPinnedFb = true;
participant.ScreenIndexIsPinnedToFb = status.ScreenIndex;
}
else
{
participant = Participants.CurrentParticipants.FirstOrDefault(p => p.ScreenIndexIsPinnedToFb.Equals(status.ScreenIndex));
if (participant == null)
{
Debug.Console(2, this, "no matching participant found by pinned_user_id: {0} or screen_index: {1}", status.PinnedUserId, status.ScreenIndex);
return;
}
else
{
participant.IsPinnedFb = false;
participant.ScreenIndexIsPinnedToFb = -1;
}
}
// fire the event as we've modified the participants list
Participants.OnParticipantsChanged();
break; break;
} }
default: default:
@@ -1600,7 +1725,87 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{ {
LinkVideoCodecToApi(this, trilist, joinStart, joinMapKey, bridge); var joinMap = new ZoomRoomJoinMap(joinStart);
var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey);
if (customJoins != null)
{
joinMap.SetCustomJoinData(customJoins);
}
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
LinkVideoCodecToApi(this, trilist, joinMap);
LinkZoomRoomToApi(trilist, joinMap);
}
/// <summary>
/// Links all the specific Zoom functionality to the API bridge
/// </summary>
/// <param name="trilist"></param>
/// <param name="joinMap"></param>
public void LinkZoomRoomToApi(BasicTriList trilist, ZoomRoomJoinMap joinMap)
{
var layoutsCodec = this as IHasZoomRoomLayouts;
if (layoutsCodec != null)
{
layoutsCodec.AvailableLayoutsChanged += (o, a) =>
{
trilist.SetBool(joinMap.LayoutGalleryIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Gallery
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Gallery));
trilist.SetBool(joinMap.LayoutSpeakerIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Speaker
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Speaker));
trilist.SetBool(joinMap.LayoutStripIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Strip
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.Strip));
trilist.SetBool(joinMap.LayoutShareAllIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.ShareAll
== (a.AvailableLayouts & zConfiguration.eLayoutStyle.ShareAll));
// pass the names used to set the layout through the bridge
trilist.SetString(joinMap.LayoutGalleryIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Gallery.ToString());
trilist.SetString(joinMap.LayoutSpeakerIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Speaker.ToString());
trilist.SetString(joinMap.LayoutStripIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.Strip.ToString());
trilist.SetString(joinMap.LayoutShareAllIsAvailable.JoinNumber, zConfiguration.eLayoutStyle.ShareAll.ToString());
};
layoutsCodec.CanSwapContentWithThumbnailFeedback.LinkInputSig(trilist.BooleanInput[joinMap.CanSwapContentWithThumbnail.JoinNumber]);
trilist.SetSigFalseAction(joinMap.SwapContentWithThumbnail.JoinNumber, () => layoutsCodec.SwapContentWithThumbnail());
layoutsCodec.ContentSwappedWithThumbnailFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SwapContentWithThumbnail.JoinNumber]);
layoutsCodec.LayoutViewIsOnFirstPageFeedback.LinkInputSig(trilist.BooleanInput[joinMap.LayoutIsOnFirstPage.JoinNumber]);
layoutsCodec.LayoutViewIsOnLastPageFeedback.LinkInputSig(trilist.BooleanInput[joinMap.LayoutIsOnLastPage.JoinNumber]);
trilist.SetSigFalseAction(joinMap.LayoutTurnToNextPage.JoinNumber, () => layoutsCodec.LayoutTurnNextPage());
trilist.SetSigFalseAction(joinMap.LayoutTurnToPreviousPage.JoinNumber, () => layoutsCodec.LayoutTurnPreviousPage());
trilist.SetSigFalseAction(joinMap.GetAvailableLayouts.JoinNumber, () => layoutsCodec.GetAvailableLayouts());
trilist.SetStringSigAction(joinMap.GetSetCurrentLayout.JoinNumber, (s) =>
{
try
{
var style = (zConfiguration.eLayoutStyle)Enum.Parse(typeof(zConfiguration.eLayoutStyle), s, true);
SetLayout(style);
}
catch (Exception e)
{
Debug.Console(1, this, "Unable to parse '{0}' to zConfiguration.eLayoutStyle: {1}", s, e);
}
});
layoutsCodec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.GetSetCurrentLayout.JoinNumber]);
}
var pinCodec = this as IHasParticipantPinUnpin;
if (pinCodec != null)
{
pinCodec.NumberOfScreensFeedback.LinkInputSig(trilist.UShortInput[joinMap.NumberOfScreens.JoinNumber]);
// Set the value of the local property to be used when pinning a participant
trilist.SetUShortSigAction(joinMap.ScreenIndexToPinUserTo.JoinNumber, (u) => ScreenIndexToPinUserTo = u);
}
} }
public override void ExecuteSwitch(object selector) public override void ExecuteSwitch(object selector)
@@ -1770,6 +1975,114 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
#endregion #endregion
#region IHasParticipantAudioMute Members
public void MuteAudioForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipant Mute: on Id: {0}", userId));
}
public void UnmuteAudioForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipant Mute: off Id: {0}", userId));
}
public void ToggleAudioForParticipant(int userId)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if (user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.AudioMuteFb)
{
UnmuteAudioForParticipant(userId);
}
else
{
MuteAudioForParticipant(userId);
}
}
#endregion
#region IHasParticipantVideoMute Members
public void MuteVideoForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipantVideo Mute: on Id: {0}", userId));
}
public void UnmuteVideoForParticipant(int userId)
{
SendText(string.Format("zCommand Call MuteParticipantVideo Mute: off Id: {0}", userId));
}
public void ToggleVideoForParticipant(int userId)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if (user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.VideoMuteFb)
{
UnmuteVideoForParticipant(userId);
}
else
{
MuteVideoForParticipant(userId);
}
}
#endregion
#region IHasParticipantPinUnpin Members
private Func<int> NumberOfScreensFeedbackFunc { get { return () => Status.NumberOfScreens.NumOfScreens; } }
public IntFeedback NumberOfScreensFeedback { get; private set; }
public int ScreenIndexToPinUserTo { get; private set; }
public void PinParticipant(int userId, int screenIndex)
{
SendText(string.Format("zCommand Call Pin Id: {0} Enable: on Screen: {1}", userId, screenIndex));
}
public void UnPinParticipant(int userId)
{
SendText(string.Format("zCommand Call Pin Id: {0} Enable: off", userId));
}
public void ToggleParticipantPinState(int userId, int screenIndex)
{
var user = Participants.CurrentParticipants.FirstOrDefault(p => p.UserId.Equals(userId));
if (user == null)
{
Debug.Console(2, this, "Unable to find user with id: {0}", userId);
return;
}
if (user.IsPinnedFb)
{
UnPinParticipant(userId);
}
else
{
PinParticipant(userId, screenIndex);
}
}
#endregion
#region Implementation of IHasCameraOff #region Implementation of IHasCameraOff
public BoolFeedback CameraIsOffFeedback { get; private set; } public BoolFeedback CameraIsOffFeedback { get; private set; }
@@ -1899,6 +2212,137 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
} }
#endregion #endregion
#region IHasZoomRoomLayouts Members
public event EventHandler<LayoutInfoChangedEventArgs> AvailableLayoutsChanged;
private Func<bool> LayoutViewIsOnFirstPageFeedbackFunc { get { return () => Status.Layout.is_In_First_Page; } }
private Func<bool> LayoutViewIsOnLastPageFeedbackFunc { get { return () => Status.Layout.is_In_Last_Page; } }
private Func<bool> CanSwapContentWithThumbnailFeedbackFunc { get { return () => Status.Layout.can_Switch_Floating_Share_Content; } }
private Func<bool> ContentSwappedWithThumbnailFeedbackFunc { get { return () => Configuration.Call.Layout.ShareThumb; } }
public BoolFeedback LayoutViewIsOnFirstPageFeedback { get; private set; }
public BoolFeedback LayoutViewIsOnLastPageFeedback { get; private set; }
public BoolFeedback CanSwapContentWithThumbnailFeedback { get; private set; }
public BoolFeedback ContentSwappedWithThumbnailFeedback { get; private set; }
public zConfiguration.eLayoutStyle LastSelectedLayout { get; private set; }
public zConfiguration.eLayoutStyle AvailableLayouts { get; private set; }
/// <summary>
/// Reads individual properties to determine if which layouts are avalailable
/// </summary>
private void ComputeAvailableLayouts()
{
zConfiguration.eLayoutStyle availableLayouts = zConfiguration.eLayoutStyle.None;
// TODO: #697 [X] Compute the avaialble layouts and set the value of AvailableLayouts
// Will need to test and confirm that this logic evaluates correctly
if (Status.Layout.can_Switch_Wall_View)
{
availableLayouts |= zConfiguration.eLayoutStyle.Gallery;
}
if (Status.Layout.can_Switch_Speaker_View)
{
availableLayouts |= zConfiguration.eLayoutStyle.Speaker;
}
if (Status.Layout.can_Switch_Share_On_All_Screens)
{
availableLayouts |= zConfiguration.eLayoutStyle.ShareAll;
}
// There is no property that directly reports if strip mode is valid, but API stipulates
// that strip mode is available if the number of screens is 1
if (Status.NumberOfScreens.NumOfScreens == 1)
{
availableLayouts |= zConfiguration.eLayoutStyle.Strip;
}
var handler = AvailableLayoutsChanged;
if (handler != null)
{
handler(this, new LayoutInfoChangedEventArgs() { AvailableLayouts = availableLayouts });
}
AvailableLayouts = availableLayouts;
}
public void GetAvailableLayouts()
{
SendText("zStatus Call Layout");
}
public void SetLayout(zConfiguration.eLayoutStyle layoutStyle)
{
LastSelectedLayout = layoutStyle;
SendText(String.Format("zConfiguration Call Layout Style: {0}", layoutStyle.ToString()));
}
public void SwapContentWithThumbnail()
{
if (CanSwapContentWithThumbnailFeedback.BoolValue)
{
var oppositeValue = ContentSwappedWithThumbnailFeedback.BoolValue ? "on" : "off"; // Get the value based on the opposite of the current state
// TODO: #697 [*] Need to verify the ternary above and make sure that the correct on/off value is being send based on the true/false value of the feedback
// to toggle the state
SendText(String.Format("zConfiguration Call Layout ShareThumb: {0}", oppositeValue));
}
}
public void LayoutTurnNextPage()
{
SendText("zCommand Call Layout TurnPage Forward: On");
}
public void LayoutTurnPreviousPage()
{
SendText("zCommand Call Layout TurnPage Forward: Off");
}
#endregion
#region IHasCodecLayouts Members
private Func<string> LocalLayoutFeedbackFunc
{
get
{
return () =>
{
if (Configuration.Call.Layout.Style != zConfiguration.eLayoutStyle.None)
return Configuration.Call.Layout.Style.ToString();
else
return Configuration.Client.Call.Layout.Style.ToString();
};
}
}
public StringFeedback LocalLayoutFeedback { get; private set; }
public void LocalLayoutToggle()
{
throw new NotImplementedException();
}
public void LocalLayoutToggleSingleProminent()
{
throw new NotImplementedException();
}
public void MinMaxLayoutToggle()
{
throw new NotImplementedException();
}
#endregion
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,273 @@
using System;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges.JoinMaps;
namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
{
public class ZoomRoomJoinMap : VideoCodecControllerJoinMap
{
// TODO: #697 [X] Set join numbers
#region Digital
[JoinName("CanSwapContentWithThumbnail")]
public JoinDataComplete CanSwapContentWithThumbnail = new JoinDataComplete(
new JoinData
{
JoinNumber = 206,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if content can be swapped with thumbnail",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("SwapContentWithThumbnail")]
public JoinDataComplete SwapContentWithThumbnail = new JoinDataComplete(
new JoinData
{
JoinNumber = 206,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Pulse to swap content with thumbnail. FB reports current state",
JoinCapabilities = eJoinCapabilities.ToFromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("GetAvailableLayouts")]
public JoinDataComplete GetAvailableLayouts = new JoinDataComplete(
new JoinData
{
JoinNumber = 215,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Gets the available layouts. Will update the LayoutXXXXXIsAvailbale signals.",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutIsOnFirstPage")]
public JoinDataComplete LayoutIsOnFirstPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 216,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Indicates if layout is on first page",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutIsOnLastPage")]
public JoinDataComplete LayoutIsOnLastPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 217,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Indicates if layout is on first page",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutTurnToNextPage")]
public JoinDataComplete LayoutTurnToNextPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 216,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Turns layout view to next page",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutTurnToPreviousPage")]
public JoinDataComplete LayoutTurnToPreviousPage = new JoinDataComplete(
new JoinData
{
JoinNumber = 217,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Turns layout view to previous page",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Digital
});
[JoinName("LayoutGalleryIsAvailable")]
public JoinDataComplete LayoutGalleryIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 221,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Gallery' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.DigitalSerial
});
[JoinName("LayoutSpeakerIsAvailable")]
public JoinDataComplete LayoutSpeakerIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 222,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Speaker' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.DigitalSerial
});
[JoinName("LayoutStripIsAvailable")]
public JoinDataComplete LayoutStripIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 223,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'Strip' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.DigitalSerial
});
[JoinName("LayoutShareAllIsAvailable")]
public JoinDataComplete LayoutShareAllIsAvailable = new JoinDataComplete(
new JoinData
{
JoinNumber = 224,
JoinSpan = 1
},
new JoinMetadata
{
Description = "FB Indicates if layout 'ShareAll' is available",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.DigitalSerial
});
//[JoinName("ParticipantAudioMuteToggleStart")]
//public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete(
// new JoinData
// {
// JoinNumber = 500,
// JoinSpan = 100
// },
// new JoinMetadata
// {
// Description = "Toggles the participant's audio mute status",
// JoinCapabilities = eJoinCapabilities.ToSIMPL,
// JoinType = eJoinType.Digital
// });
//[JoinName("ParticipantVideoMuteToggleStart")]
//public JoinDataComplete ParticipantVideoMuteToggleStart = new JoinDataComplete(
// new JoinData
// {
// JoinNumber = 800,
// JoinSpan = 100
// },
// new JoinMetadata
// {
// Description = "Toggles the participant's video mute status",
// JoinCapabilities = eJoinCapabilities.ToSIMPL,
// JoinType = eJoinType.Digital
// });
//[JoinName("ParticipantPinToggleStart")]
//public JoinDataComplete ParticipantPinToggleStart = new JoinDataComplete(
// new JoinData
// {
// JoinNumber = 1100,
// JoinSpan = 100
// },
// new JoinMetadata
// {
// Description = "Toggles the participant's pin status",
// JoinCapabilities = eJoinCapabilities.ToSIMPL,
// JoinType = eJoinType.Digital
// });
#endregion
#region Analog
[JoinName("NumberOfScreens")]
public JoinDataComplete NumberOfScreens = new JoinDataComplete(
new JoinData
{
JoinNumber = 11,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Reports the number of screens connected",
JoinCapabilities = eJoinCapabilities.ToSIMPL,
JoinType = eJoinType.Analog
});
[JoinName("ScreenIndexToPinUserTo")]
public JoinDataComplete ScreenIndexToPinUserTo = new JoinDataComplete(
new JoinData
{
JoinNumber = 11,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Specifies the screen index a participant should be pinned to",
JoinCapabilities = eJoinCapabilities.FromSIMPL,
JoinType = eJoinType.Analog
});
#endregion
#region Serials
[JoinName("GetSetCurrentLayout")]
public JoinDataComplete GetSetCurrentLayout = new JoinDataComplete(
new JoinData
{
JoinNumber = 215,
JoinSpan = 1
},
new JoinMetadata
{
Description = "Sets and reports the current layout. Use the LayoutXXXXIsAvailable signals to determine valid layouts",
JoinCapabilities = eJoinCapabilities.ToFromSIMPL,
JoinType = eJoinType.Serial
});
#endregion
public ZoomRoomJoinMap(uint joinStart)
: base(joinStart, typeof(ZoomRoomJoinMap))
{
}
public ZoomRoomJoinMap(uint joinStart, Type type)
: base(joinStart, type)
{
}
}
}