diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs
index 0e987fb4..971b6be0 100644
--- a/PepperDashEssentials/ControlSystem.cs
+++ b/PepperDashEssentials/ControlSystem.cs
@@ -12,12 +12,9 @@ using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
-using PepperDash.Essentials.Devices.Common;
+using PepperDash.Essentials.Core.Rooms.Config;
using PepperDash.Essentials.DM;
using PepperDash.Essentials.Fusion;
-using PepperDash.Essentials.Room.Config;
-using PepperDash.Essentials.Room.MobileControl;
-
using Newtonsoft.Json;
namespace PepperDash.Essentials
diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj
index ce1a0c74..f5dbefdd 100644
--- a/PepperDashEssentials/PepperDashEssentials.csproj
+++ b/PepperDashEssentials/PepperDashEssentials.csproj
@@ -153,13 +153,6 @@
-
-
-
-
-
-
-
@@ -176,11 +169,6 @@
-
-
-
-
-
@@ -210,7 +198,6 @@
-
diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs
index 576e3cca..d6aa6780 100644
--- a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs
+++ b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using Crestron.SimplSharp;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
@@ -10,25 +8,17 @@ using PepperDash.Essentials.Room.Config;
namespace PepperDash.Essentials
{
public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IRunRouteAction,
- IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay
+ IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay, IHasCurrentSourceInfoChange
{
- public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; }
-
- ///
- /// If room is off, enables power on to last source. Default true
- ///
- public bool EnablePowerOnToLastSource { get; set; }
-
public EssentialsHuddleSpaceRoom(DeviceConfig config)
: base(config)
{
try
{
- PropertiesConfig = JsonConvert.DeserializeObject
- (config.Properties.ToString());
+ PropertiesConfig = config.Properties.ToObject();
DefaultDisplay =
DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching;
-
+ //why are we assuming IRoutingSinkWithSwitching here?
DefaultAudioDevice =
DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IRoutingSinkWithSwitching;
@@ -41,6 +31,8 @@ namespace PepperDash.Essentials
}
}
+ public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; }
+
private void Initialize()
{
if (DefaultAudioDevice is IBasicVolumeControls)
@@ -53,7 +45,10 @@ namespace PepperDash.Essentials
}
CurrentVolumeControls = DefaultVolumeControls;
- SourceListKey = "default";
+ SourceListKey = String.IsNullOrEmpty(PropertiesConfig.SourceListKey)
+ ? "default"
+ : PropertiesConfig.SourceListKey;
+
EnablePowerOnToLastSource = true;
var disp = DefaultDisplay as DisplayBase;
@@ -132,37 +127,6 @@ namespace PepperDash.Essentials
ConfigWriter.UpdateRoomConfig(config);
}
- ///
- ///
- ///
- protected override void EndShutdown()
- {
- SetDefaultLevels();
-
- RunDefaultPresentRoute();
-
- //CrestronEnvironment.Sleep(1000); //why?
-
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
-
- RunRouteAction("roomOff");
- }
-
- ///
- /// Routes the default source item, if any
- ///
- public override bool RunDefaultPresentRoute()
- {
- if (DefaultSourceItem == null)
- {
- Debug.Console(0, this, "Unable to run default present route, DefaultSourceItem is null.");
- return false;
- }
-
- RunRouteAction(DefaultSourceItem);
- return true;
- }
-
public override bool CustomActivate()
{
// Add Occupancy object from config
@@ -180,31 +144,6 @@ namespace PepperDash.Essentials
return base.CustomActivate();
}
- ///
- /// Will power the room on with the last-used source
- ///
- public override void PowerOnToDefaultOrLastSource()
- {
- if (!EnablePowerOnToLastSource || LastSourceKey == null)
- {
- return;
- }
- RunRouteAction(LastSourceKey);
- }
-
- ///
- /// Does what it says
- ///
- public override void SetDefaultLevels()
- {
- Debug.Console(1, this, "Restoring default levels");
- var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
- if (vc != null)
- {
- vc.SetVolume(DefaultVolume);
- }
- }
-
public override void RoomVacatedForTimeoutPeriod(object o)
{
//TODO: Implement RoomVacatedForTimeoutPeriod
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
index 746036b3..fc982a88 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
@@ -259,9 +259,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs
index 11debb03..50c22255 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs
@@ -1,19 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-using Crestron.SimplSharp;
using Crestron.SimplSharp.Scheduler;
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
using PepperDash.Core;
-using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
-namespace PepperDash.Essentials.Core
+namespace PepperDash.Essentials.Core.Rooms
{
///
/// A device that when linked to a room can power the room on when enabled during scheduled hours.
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs
new file mode 100644
index 00000000..70840893
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ public class DDVC01RoomPropertiesConfig : EssentialsHuddleVtc1PropertiesConfig
+ {
+ [JsonProperty("roomPhoneNumber")]
+ public string RoomPhoneNumber { get; set; }
+ [JsonProperty("roomURI")]
+ public string RoomURI { get; set; }
+ [JsonProperty("speedDials")]
+ public List SpeedDials { get; set; }
+ [JsonProperty("volumeSliderNames")]
+ public List VolumeSliderNames { get; set; }
+ }
+
+ public class DDVC01SpeedDial
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ [JsonProperty("number")]
+ public string Number { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs
new file mode 100644
index 00000000..d1d1ee4d
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs
@@ -0,0 +1,9 @@
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs
new file mode 100644
index 00000000..5d97ffb1
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ ///
+ ///
+ ///
+ public class EssentialsHuddleRoomPropertiesConfig : EssentialsRoomPropertiesConfig
+ {
+ ///
+ /// The key of the default display device
+ ///
+ [JsonProperty("defaultDisplayKey")]
+ public string DefaultDisplayKey { get; set; }
+
+ ///
+ /// The key of the default audio device
+ ///
+ [JsonProperty("defaultAudioKey")]
+ public string DefaultAudioKey { get; set; }
+
+ ///
+ /// The key of the source list for the room
+ ///
+ [JsonProperty("sourceListKey")]
+ public string SourceListKey { get; set; }
+
+ ///
+ /// The key of the default source item from the source list
+ ///
+ [JsonProperty("defaultSourceItem")]
+ public string DefaultSourceItem { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs
new file mode 100644
index 00000000..7d9bfa5b
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+
+ public class EssentialsHuddleVtc1PropertiesConfig : EssentialsConferenceRoomPropertiesConfig
+ {
+ [JsonProperty("defaultDisplayKey")]
+ public string DefaultDisplayKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs
new file mode 100644
index 00000000..2d2d95c4
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using PepperDash.Core;
+using PepperDash.Essentials.Core;
+
+using Newtonsoft.Json;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ ///
+ ///
+ ///
+ public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPropertiesConfig
+ {
+ [JsonProperty("defaultAudioBehavior")]
+ public string DefaultAudioBehavior { get; set; }
+ [JsonProperty("defaultVideoBehavior")]
+ public string DefaultVideoBehavior { get; set; }
+ [JsonProperty("displays")]
+ public Dictionary Displays { get; set; }
+
+ public EssentialsNDisplayRoomPropertiesConfig()
+ {
+ Displays = new Dictionary();
+ }
+
+ }
+
+ public class DisplayItem : IKeyName
+ {
+ public string Key { get; set; }
+ public string Name { get; set; }
+ }
+
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs
new file mode 100644
index 00000000..de0ca9da
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ ///
+ ///
+ ///
+ public class EssentialsPresentationRoomPropertiesConfig : EssentialsRoomPropertiesConfig
+ {
+ public string DefaultAudioBehavior { get; set; }
+ public string DefaultAudioKey { get; set; }
+ public string DefaultVideoBehavior { get; set; }
+ public List DisplayKeys { get; set; }
+ public string SourceListKey { get; set; }
+ public bool HasDsp { get; set; }
+
+ public EssentialsPresentationRoomPropertiesConfig()
+ {
+ DisplayKeys = new List();
+ }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs
new file mode 100644
index 00000000..4fb046fb
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs
@@ -0,0 +1,305 @@
+using System.Collections.Generic;
+using Crestron.SimplSharp;
+using Newtonsoft.Json;
+
+using PepperDash.Core;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ public class EssentialsRoomConfigHelper
+ {
+ ///
+ /// Returns a room object from this config data
+ ///
+ ///
+ public static Device GetRoomObject(DeviceConfig roomConfig)
+ {
+ var typeName = roomConfig.Type.ToLower();
+ if (typeName == "huddle")
+ {
+ var huddle = new EssentialsHuddleSpaceRoom(roomConfig);
+
+ return huddle;
+ }
+ else if (typeName == "huddlevtc1")
+ {
+ var rm = new EssentialsHuddleVtc1Room(roomConfig);
+
+ return rm;
+ }
+ else if (typeName == "ddvc01Bridge")
+ {
+ return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing.
+ }
+ else if (typeName == "dualdisplay")
+ {
+ var rm = new EssentialsDualDisplayRoom(roomConfig);
+
+ return rm;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets and operating, standalone emergegncy object that can be plugged into a room.
+ /// Returns null if there is no emergency defined
+ ///
+ public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room)
+ {
+ // This emergency
+ var emergency = props.Emergency;
+ if (emergency != null)
+ {
+ //switch on emergency type here. Right now only contact and shutdown
+ var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room);
+ DeviceManager.AddDevice(e);
+ }
+ return null;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Core.Privacy.MicrophonePrivacyController GetMicrophonePrivacy(
+ EssentialsRoomPropertiesConfig props, IPrivacy room)
+ {
+ var microphonePrivacy = props.MicrophonePrivacy;
+ if (microphonePrivacy == null)
+ {
+ Debug.Console(0, "Cannot create microphone privacy with null properties");
+ return null;
+ }
+ // Get the MicrophonePrivacy device from the device manager
+ var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as
+ Core.Privacy.MicrophonePrivacyController);
+ // Set this room as the IPrivacy device
+ if (mP == null)
+ {
+ Debug.Console(0, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey);
+ return null;
+ }
+ mP.SetPrivacyDevice(room);
+
+ var behaviour = props.MicrophonePrivacy.Behaviour.ToLower();
+
+ if (behaviour == null)
+ {
+ Debug.Console(0, "WARNING: No behaviour defined for MicrophonePrivacyController");
+ return null;
+ }
+ if (behaviour == "trackroomstate")
+ {
+ // Tie LED enable to room power state
+ var essRoom = room as EssentialsRoomBase;
+ essRoom.OnFeedback.OutputChange += (o, a) =>
+ {
+ if (essRoom.OnFeedback.BoolValue)
+ mP.EnableLeds = true;
+ else
+ mP.EnableLeds = false;
+ };
+
+ mP.EnableLeds = essRoom.OnFeedback.BoolValue;
+ }
+ else if (behaviour == "trackcallstate")
+ {
+ // Tie LED enable to room power state
+ var inCallRoom = room as IHasInCallFeedback;
+ inCallRoom.InCallFeedback.OutputChange += (o, a) =>
+ {
+ if (inCallRoom.InCallFeedback.BoolValue)
+ mP.EnableLeds = true;
+ else
+ mP.EnableLeds = false;
+ };
+
+ mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue;
+ }
+
+ return mP;
+ }
+
+ }
+
+ ///
+ ///
+ ///
+ public class EssentialsRoomPropertiesConfig
+ {
+ [JsonProperty("addresses")]
+ public EssentialsRoomAddressPropertiesConfig Addresses { get; set; }
+
+ [JsonProperty("description")]
+ public string Description { get; set; }
+
+ [JsonProperty("emergency")]
+ public EssentialsRoomEmergencyConfig Emergency { get; set; }
+
+ [JsonProperty("help")]
+ public EssentialsHelpPropertiesConfig Help { get; set; }
+
+ [JsonProperty("helpMessage")]
+ public string HelpMessage { get; set; }
+
+ [JsonProperty("environment")]
+ public EssentialsEnvironmentPropertiesConfig Environment { get; set; }
+
+ [JsonProperty("logo")]
+ public EssentialsLogoPropertiesConfig Logo { get; set; }
+
+ [JsonProperty("microphonePrivacy")]
+ public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; }
+
+ [JsonProperty("occupancy")]
+ public EssentialsRoomOccSensorConfig Occupancy { get; set; }
+
+ [JsonProperty("oneButtonMeeting")]
+ public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; }
+
+ [JsonProperty("shutdownVacancySeconds")]
+ public int ShutdownVacancySeconds { get; set; }
+
+ [JsonProperty("shutdownPromptSeconds")]
+ public int ShutdownPromptSeconds { get; set; }
+
+ [JsonProperty("tech")]
+ public EssentialsRoomTechConfig Tech { get; set; }
+
+ [JsonProperty("volumes")]
+ public EssentialsRoomVolumesConfig Volumes { get; set; }
+
+ [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")]
+ public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; }
+ }
+
+ public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig
+ {
+ [JsonProperty("defaultAudioKey")]
+ public string DefaultAudioKey { get; set; }
+ [JsonProperty("sourceListKey")]
+ public string SourceListKey { get; set; }
+ [JsonProperty("defaultSourceItem")]
+ public string DefaultSourceItem { get; set; }
+
+ }
+
+ public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig
+ {
+ [JsonProperty("videoCodecKey")]
+ public string VideoCodecKey { get; set; }
+ [JsonProperty("audioCodecKey")]
+ public string AudioCodecKey { get; set; }
+ }
+
+ public class EssentialsEnvironmentPropertiesConfig
+ {
+ public bool Enabled { get; set; }
+
+ [JsonProperty("deviceKeys")]
+ public List DeviceKeys { get; set; }
+
+ public EssentialsEnvironmentPropertiesConfig()
+ {
+ DeviceKeys = new List();
+ }
+
+ }
+
+ public class EssentialsRoomMicrophonePrivacyConfig
+ {
+ [JsonProperty("deviceKey")]
+ public string DeviceKey { get; set; }
+
+ [JsonProperty("behaviour")]
+ public string Behaviour { get; set; }
+ }
+
+ ///
+ /// Properties for the help text box
+ ///
+ public class EssentialsHelpPropertiesConfig
+ {
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ [JsonProperty("showCallButton")]
+ public bool ShowCallButton { get; set; }
+
+ ///
+ /// Defaults to "Call Help Desk"
+ ///
+ [JsonProperty("callButtonText")]
+ public string CallButtonText { get; set; }
+
+ public EssentialsHelpPropertiesConfig()
+ {
+ CallButtonText = "Call Help Desk";
+ }
+ }
+
+ ///
+ ///
+ ///
+ public class EssentialsOneButtonMeetingPropertiesConfig
+ {
+ [JsonProperty("enable")]
+ public bool Enable { get; set; }
+ }
+
+ public class EssentialsRoomAddressPropertiesConfig
+ {
+ [JsonProperty("phoneNumber")]
+ public string PhoneNumber { get; set; }
+
+ [JsonProperty("sipAddress")]
+ public string SipAddress { get; set; }
+ }
+
+
+ ///
+ /// Properties for the room's logo on panels
+ ///
+ public class EssentialsLogoPropertiesConfig
+ {
+ [JsonProperty("type")]
+ public string Type { get; set; }
+
+ [JsonProperty("url")]
+ public string Url { get; set; }
+ ///
+ /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo
+ ///
+ public string GetUrl()
+ {
+ if (Type == "url")
+ return Url;
+ if (Type == "system")
+ return string.Format("http://{0}:8080/logo.png",
+ CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0));
+ return null;
+ }
+ }
+
+ ///
+ /// Represents occupancy sensor(s) setup for a room
+ ///
+ public class EssentialsRoomOccSensorConfig
+ {
+ [JsonProperty("deviceKey")]
+ public string DeviceKey { get; set; }
+
+ [JsonProperty("timeoutMinutes")]
+ public int TimeoutMinutes { get; set; }
+ }
+
+ public class EssentialsRoomTechConfig
+ {
+ [JsonProperty("password")]
+ public string Password { get; set; }
+ }
+}
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs
new file mode 100644
index 00000000..017c1a6e
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs
@@ -0,0 +1,30 @@
+namespace PepperDash.Essentials.Core.Rooms.Config
+{
+ ///
+ ///
+ ///
+ public class EssentialsRoomEmergencyConfig
+ {
+ public EssentialsRoomEmergencyTriggerConfig Trigger { get; set; }
+
+ public string Behavior { get; set; }
+ }
+
+ ///
+ ///
+ ///
+ public class EssentialsRoomEmergencyTriggerConfig
+ {
+ ///
+ /// contact,
+ ///
+ public string Type { get; set; }
+ ///
+ /// Input number if contact
+ ///
+ public int Number { get; set; }
+
+ public bool TriggerOnClose { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs
new file mode 100644
index 00000000..df8124e8
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs
@@ -0,0 +1,47 @@
+using System;
+using Crestron.SimplSharpPro;
+using PepperDash.Essentials.Core.Rooms.Config;
+
+namespace PepperDash.Essentials.Core.Rooms
+{
+ public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase
+ {
+ EssentialsRoomBase Room;
+ string Behavior;
+ bool TriggerOnClose;
+
+ public EssentialsRoomEmergencyContactClosure(string key, EssentialsRoomEmergencyConfig config, EssentialsRoomBase room) :
+ base(key)
+ {
+ Room = room;
+ var cs = Global.ControlSystem;
+
+ if (config.Trigger.Type.Equals("contact", StringComparison.OrdinalIgnoreCase))
+ {
+ var portNum = (uint)config.Trigger.Number;
+ if (portNum <= cs.NumberOfDigitalInputPorts)
+ {
+ cs.DigitalInputPorts[portNum].Register();
+ cs.DigitalInputPorts[portNum].StateChange += EsentialsRoomEmergencyContactClosure_StateChange;
+ }
+ }
+ Behavior = config.Behavior;
+ TriggerOnClose = config.Trigger.TriggerOnClose;
+ }
+
+ void EsentialsRoomEmergencyContactClosure_StateChange(DigitalInput digitalInput, DigitalInputEventArgs args)
+ {
+ if (args.State && TriggerOnClose || !args.State && !TriggerOnClose)
+ RunEmergencyBehavior();
+ }
+
+ ///
+ ///
+ ///
+ public void RunEmergencyBehavior()
+ {
+ if (Behavior.Equals("shutdown"))
+ Room.Shutdown();
+ }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs
index e9d844f0..901bc52d 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs
@@ -5,24 +5,76 @@ using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
+using PepperDash.Essentials.Core.Rooms.Config;
namespace PepperDash.Essentials.Core
{
///
///
///
- public abstract class EssentialsRoomBase : ReconfigurableDevice, IHasCurrentSourceInfoChange
+ public abstract class EssentialsRoomBase : ReconfigurableDevice
{
- public event EventHandler CurrentVolumeDeviceChange;
- public event SourceInfoChangeHandler CurrentSourceChange;
- public string CurrentSourceInfoKey { get; set; }
+ protected EssentialsRoomPropertiesConfig BaseConfig;
+ protected IBasicVolumeControls CurrentAudioDevice;
+ protected Func IsCoolingFeedbackFunc;
+ protected Func IsWarmingFeedbackFunc;
+ protected string LastSourceKey;
+
+ ///
+ ///
+ ///
+ protected Func OnFeedbackFunc;
+
+ ///
+ /// Seconds after vacancy detected until prompt is displayed
+ ///
+ protected int RoomVacancyShutdownPromptSeconds;
+
+ ///
+ /// Seconds after vacancy prompt is displayed until shutdown
+ ///
+ protected int RoomVacancyShutdownSeconds;
+
+ protected CCriticalSection RoutingLock = new CCriticalSection();
+
+ protected Dictionary SavedVolumeLevels =
+ new Dictionary();
+
+ private SourceListItem _currentSourceInfo;
+
+ ///
+ /// If room is off, enables power on to last source. Default true
+ ///
+ public bool EnablePowerOnToLastSource { get; set; }
+
+ protected EssentialsRoomBase(DeviceConfig config)
+ : base(config)
+ {
+ BaseConfig = config.Properties.ToObject();
+
+ ZeroVolumeWhenSwtichingVolumeDevices = BaseConfig.ZeroVolumeWhenSwtichingVolumeDevices;
+ SetupShutdownPrompt();
+
+ SetupRoomVacancyShutdown();
+
+ OnFeedback = new BoolFeedback(OnFeedbackFunc);
+
+ IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc);
+ IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc);
+
+ AddPostActivationAction(() =>
+ {
+ if (RoomOccupancy != null)
+ {
+ OnRoomOccupancyIsSet();
+ }
+ });
+ }
public IRoutingSinkWithSwitching DefaultDisplay { get; protected set; }
public IRoutingSink DefaultAudioDevice { get; protected set; }
public IBasicVolumeControls DefaultVolumeControls { get; protected set; }
- protected CCriticalSection RoutingLock = new CCriticalSection();
-
public string DefaultSourceItem { get; set; }
public ushort DefaultVolume { get; set; }
@@ -45,75 +97,31 @@ namespace PepperDash.Essentials.Core
if (handler != null)
{
- CurrentVolumeDeviceChange(this,
+ handler(this,
new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.WillChange));
- CurrentVolumeDeviceChange(this,
- new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.DidChange));
}
- var oldDevice = value as IInUseTracking;
+ var oldDevice = CurrentAudioDevice as IInUseTracking;
var newDevice = value as IInUseTracking;
UpdateInUseTracking(oldDevice, newDevice);
CurrentAudioDevice = value;
- }
- }
- protected string LastSourceKey;
-
- ///
- /// The SourceListItem last run - containing names and icons
- ///
- public SourceListItem CurrentSourceInfo
- {
- get { return _currentSourceInfo; }
- set
- {
- if (value == _currentSourceInfo)
+ if (handler == null)
{
return;
}
- var handler = CurrentSourceChange;
- // remove from in-use tracker, if so equipped
- if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking)
- {
- (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control");
- }
-
- if (handler != null)
- {
- handler(_currentSourceInfo, ChangeType.WillChange);
- }
-
- _currentSourceInfo = value;
-
- // add to in-use tracking
- if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking)
- {
- (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control");
- }
- if (handler != null)
- {
- handler(_currentSourceInfo, ChangeType.DidChange);
- }
+ handler(this,
+ new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.DidChange));
}
}
- private SourceListItem _currentSourceInfo;
-
public bool ExcludeFromGlobalFunctions { get; set; }
- protected IBasicVolumeControls CurrentAudioDevice;
-
public BoolFeedback OnFeedback { get; private set; }
- ///
- /// Fires when the RoomOccupancy object is set
- ///
- public event EventHandler RoomOccupancyIsSet;
-
public BoolFeedback IsWarmingUpFeedback { get; private set; }
public BoolFeedback IsCoolingDownFeedback { get; private set; }
@@ -121,9 +129,6 @@ namespace PepperDash.Essentials.Core
public bool OccupancyStatusProviderIsRemote { get; private set; }
- protected Func IsWarmingFeedbackFunc;
- protected Func IsCoolingFeedbackFunc;
-
///
/// The config name of the source list
///
@@ -148,51 +153,62 @@ namespace PepperDash.Essentials.Core
public eVacancyMode VacancyMode { get; private set; }
- ///
- /// Seconds after vacancy prompt is displayed until shutdown
- ///
- protected int RoomVacancyShutdownSeconds;
-
- ///
- /// Seconds after vacancy detected until prompt is displayed
- ///
- protected int RoomVacancyShutdownPromptSeconds;
-
- ///
- ///
- ///
- protected Func OnFeedbackFunc;
-
- protected Dictionary SavedVolumeLevels =
- new Dictionary();
-
///
/// When volume control devices change, should we zero the one that we are leaving?
///
public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; }
+ #region IHasCurrentSourceInfoChange Members
- protected EssentialsRoomBase(DeviceConfig config)
- : base(config)
+ public event SourceInfoChangeHandler CurrentSourceChange;
+ public string CurrentSourceInfoKey { get; set; }
+
+ ///
+ /// The SourceListItem last run - containing names and icons
+ ///
+ public SourceListItem CurrentSourceInfo
{
- SetupShutdownPrompt();
-
- SetupRoomVacancyShutdown();
-
- OnFeedback = new BoolFeedback(OnFeedbackFunc);
-
- IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc);
- IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc);
-
- AddPostActivationAction(() =>
+ get { return _currentSourceInfo; }
+ set
{
- if (RoomOccupancy != null)
+ if (value == _currentSourceInfo)
{
- OnRoomOccupancyIsSet();
+ return;
}
- });
+
+ var handler = CurrentSourceChange;
+
+ if (handler != null)
+ {
+ handler(_currentSourceInfo, ChangeType.WillChange);
+ }
+
+ var oldSource = _currentSourceInfo as IInUseTracking;
+ var newSource = value as IInUseTracking;
+
+ UpdateInUseTracking(oldSource, newSource);
+
+ _currentSourceInfo = value;
+
+ if (handler == null)
+ {
+ return;
+ }
+
+ handler(_currentSourceInfo, ChangeType.DidChange);
+ }
}
+ #endregion
+
+ public event EventHandler CurrentVolumeDeviceChange;
+
+ ///
+ /// Fires when the RoomOccupancy object is set
+ ///
+ public event EventHandler RoomOccupancyIsSet;
+
+
private void SetupRoomVacancyShutdown()
{
RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer");
@@ -328,13 +344,32 @@ namespace PepperDash.Essentials.Core
/// This method is for the derived class to define it's specific shutdown
/// requirements but should not be called directly. It is called by Shutdown()
///
- protected abstract void EndShutdown();
+ protected virtual void EndShutdown()
+ {
+ SetDefaultLevels();
+
+ RunDefaultPresentRoute();
+
+ //CrestronEnvironment.Sleep(1000); //why?
+
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
+
+ RunRouteAction("roomOff");
+ }
///
/// Override this to implement a default volume level(s) method
///
- public abstract void SetDefaultLevels();
+ public virtual void SetDefaultLevels()
+ {
+ Debug.Console(1, this, "Restoring default levels");
+ var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ if (vc != null)
+ {
+ vc.SetVolume(DefaultVolume);
+ }
+ }
///
/// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device
@@ -388,13 +423,30 @@ namespace PepperDash.Essentials.Core
///
/// To allow base class to power room on to last source
///
- public abstract void PowerOnToDefaultOrLastSource();
+ public virtual void PowerOnToDefaultOrLastSource()
+ {
+ if (!EnablePowerOnToLastSource || LastSourceKey == null)
+ {
+ return;
+ }
+ RunRouteAction(LastSourceKey);
+ }
///
/// To allow base class to power room on to default source
///
///
- public abstract bool RunDefaultPresentRoute();
+ public virtual bool RunDefaultPresentRoute()
+ {
+ if (DefaultSourceItem == null)
+ {
+ Debug.Console(0, this, "Unable to run default present route, DefaultSourceItem is null.");
+ return false;
+ }
+
+ RunRouteAction(DefaultSourceItem);
+ return true;
+ }
private void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e)
{
@@ -744,11 +796,15 @@ namespace PepperDash.Essentials.Core
public abstract class EssentialsRoomEmergencyBase : IKeyed
{
- public string Key { get; private set; }
-
protected EssentialsRoomEmergencyBase(string key)
{
Key = key;
}
+
+ #region IKeyed Members
+
+ public string Key { get; private set; }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs
new file mode 100644
index 00000000..a267c30a
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs
@@ -0,0 +1,660 @@
+using System;
+using System.Linq;
+using Crestron.SimplSharp;
+
+using Newtonsoft.Json;
+
+using PepperDash.Core;
+using PepperDash.Essentials.Core;
+using PepperDash.Essentials.Core.Config;
+using PepperDash.Essentials.Room.Config;
+using PepperDash.Essentials.Devices.Common.Codec;
+using PepperDash.Essentials.Devices.Common.VideoCodec;
+using PepperDash.Essentials.Devices.Common.AudioCodec;
+
+namespace PepperDash.Essentials
+{
+ public class EssentialsDualDisplayRoom : EssentialsNDisplayRoomBase, IHasCurrentVolumeControls,
+ IRunRouteAction, IPrivacy, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasInCallFeedback
+ {
+ public event EventHandler CurrentVolumeDeviceChange;
+
+ public EssentialsDualDisplayRoomPropertiesConfig PropertiesConfig { get; private set; }
+
+ //************************
+ // Call-related stuff
+
+ public BoolFeedback InCallFeedback { get; private set; }
+
+ ///
+ /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis
+ ///
+ public IntFeedback CallTypeFeedback { get; private set; }
+
+ ///
+ ///
+ ///
+ public BoolFeedback PrivacyModeIsOnFeedback { get; private set; }
+
+ ///
+ /// When something in the room is sharing with the far end or through other means
+ ///
+ public BoolFeedback IsSharingFeedback { get; private set; }
+
+ public IRoutingSinkWithSwitching LeftDisplay { get; private set; }
+ public IRoutingSinkWithSwitching RightDisplay { get; private set; }
+
+
+ protected override Func OnFeedbackFunc
+ {
+ get
+ {
+ return () =>
+ {
+ var leftDisp = LeftDisplay as DisplayBase;
+ var rightDisp = RightDisplay as DisplayBase;
+ var val = leftDisp != null && leftDisp.CurrentSourceInfo != null
+ && leftDisp.CurrentSourceInfo.Type == eSourceListItemType.Route
+ && rightDisp != null && rightDisp.CurrentSourceInfo != null
+ && rightDisp.CurrentSourceInfo.Type == eSourceListItemType.Route;
+ return val;
+ };
+ }
+ }
+
+ ///
+ ///
+ ///
+ protected override Func IsWarmingFeedbackFunc
+ {
+ get
+ {
+ return () =>
+ {
+ var leftDisp = LeftDisplay as DisplayBase;
+ var rightDisp = RightDisplay as DisplayBase;
+ if (leftDisp != null && RightDisplay != null)
+ return rightDisp != null && (leftDisp.IsWarmingUpFeedback.BoolValue || rightDisp.IsWarmingUpFeedback.BoolValue);
+ return false;
+ };
+ }
+ }
+
+ ///
+ ///
+ ///
+ protected override Func IsCoolingFeedbackFunc
+ {
+ get
+ {
+ return () =>
+ {
+ var leftDisp = LeftDisplay as DisplayBase;
+ var rightDisp = RightDisplay as DisplayBase;
+ if (leftDisp != null && RightDisplay != null)
+ return rightDisp != null && (leftDisp.IsCoolingDownFeedback.BoolValue || rightDisp.IsCoolingDownFeedback.BoolValue);
+ return false;
+ };
+ }
+ }
+
+ public IBasicVolumeControls DefaultAudioDevice { get; private set; }
+ public IBasicVolumeControls DefaultVolumeControls { get; private set; }
+
+ public VideoCodecBase VideoCodec { get; private set; }
+
+ public AudioCodecBase AudioCodec { get; private set; }
+
+ public bool ExcludeFromGlobalFunctions { get; set; }
+
+ public string DefaultSourceItem { get; set; }
+
+ public ushort DefaultVolume { get; set; }
+
+ ///
+ /// If room is off, enables power on to last source. Default true
+ ///
+ public bool EnablePowerOnToLastSource { get; set; }
+ string _lastSourceKey;
+
+ ///
+ /// Sets the volume control device, and attaches/removes InUseTrackers with "audio"
+ /// tag to device.
+ ///
+ public IBasicVolumeControls CurrentVolumeControls
+ {
+ get { return _currentAudioDevice; }
+ set
+ {
+ if (value == _currentAudioDevice) return;
+
+ var oldDev = _currentAudioDevice;
+ // derigister this room from the device, if it can
+ if (oldDev is IInUseTracking)
+ (oldDev as IInUseTracking).InUseTracker.RemoveUser(this, "audio");
+ var handler = CurrentVolumeDeviceChange;
+ if (handler != null)
+ CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.WillChange));
+ _currentAudioDevice = value;
+ if (handler != null)
+ CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.DidChange));
+ // register this room with new device, if it can
+ if (_currentAudioDevice is IInUseTracking)
+ (_currentAudioDevice as IInUseTracking).InUseTracker.AddUser(this, "audio");
+ }
+ }
+ IBasicVolumeControls _currentAudioDevice;
+
+ ///
+ /// "codecOsd"
+ ///
+ public string DefaultCodecRouteString { get { return "codecOsd"; } }
+
+ ///
+ /// Temporary implementation. Returns the schedule-ready object or null if none. Fow now,
+ /// always returns the VideoCodec if it is capable
+ ///
+ public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } }
+
+ readonly CCriticalSection _sourceSelectLock = new CCriticalSection();
+
+ public EssentialsDualDisplayRoom(DeviceConfig config)
+ : base(config)
+ {
+ try
+ {
+ PropertiesConfig = JsonConvert.DeserializeObject
+ (config.Properties.ToString());
+
+ var leftDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.leftDisplay];
+ if (leftDisp != null)
+ {
+ if (!string.IsNullOrEmpty(leftDisp.Key))
+ {
+ LeftDisplay = DeviceManager.GetDeviceForKey(leftDisp.Key) as IRoutingSinkWithSwitching;
+ Displays.Add(eSourceListItemDestinationTypes.leftDisplay, LeftDisplay);
+ }
+ else
+ Debug.Console(0, this, "Unable to get LeftDisplay for Room");
+ }
+
+ var rightDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.rightDisplay];
+ if (rightDisp != null)
+ {
+ if (!string.IsNullOrEmpty(rightDisp.Key))
+ {
+ LeftDisplay = DeviceManager.GetDeviceForKey(rightDisp.Key) as IRoutingSinkWithSwitching;
+ Displays.Add(eSourceListItemDestinationTypes.rightDisplay, RightDisplay);
+ }
+ else
+ Debug.Console(0, this, "Unable to get LeftDisplay for Room");
+ }
+
+ VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as
+ VideoCodecBase;
+ if (VideoCodec == null)
+ throw new ArgumentNullException("codec cannot be null");
+
+ AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as
+ AudioCodecBase;
+ if (AudioCodec == null)
+ Debug.Console(0, this, "No Audio Codec Found");
+
+ DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls;
+
+ Initialize();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "Error building room \n{0}", e);
+ }
+ }
+
+ void Initialize()
+ {
+ if (DefaultAudioDevice != null)
+ DefaultVolumeControls = DefaultAudioDevice;
+ else if (DefaultAudioDevice is IHasVolumeDevice)
+ DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice;
+ CurrentVolumeControls = DefaultVolumeControls;
+
+
+ var leftDisp = LeftDisplay as DisplayBase;
+ if (leftDisp != null)
+ InitializeDisplay(leftDisp);
+
+ var rightDisp = RightDisplay as DisplayBase;
+ if (rightDisp != null)
+ InitializeDisplay(rightDisp);
+
+ // Get Microphone Privacy object, if any
+ MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this);
+
+ Debug.Console(2, this, "Microphone Privacy Config evaluated.");
+
+ // Get emergency object, if any
+ Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this);
+
+ Debug.Console(2, this, "Emergency Config evaluated.");
+
+ // Combines call feedback from both codecs if available
+ InCallFeedback = new BoolFeedback(() =>
+ {
+ bool inAudioCall = false;
+ bool inVideoCall = false;
+
+ if (AudioCodec != null)
+ inAudioCall = AudioCodec.IsInCall;
+
+ if (VideoCodec != null)
+ inVideoCall = VideoCodec.IsInCall;
+
+ return inAudioCall || inVideoCall;
+ });
+
+ VideoCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate();
+
+ if (AudioCodec != null)
+ AudioCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate();
+
+ IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue);
+ VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => IsSharingFeedback.FireUpdate();
+
+ // link privacy to VC (for now?)
+ PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue);
+ VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => PrivacyModeIsOnFeedback.FireUpdate();
+
+ CallTypeFeedback = new IntFeedback(() => 0);
+
+ SourceListKey = "default";
+ EnablePowerOnToLastSource = true;
+ }
+
+ void InitializeDisplay(DisplayBase disp)
+ {
+ if (disp != null)
+ {
+ // Link power, warming, cooling to display
+ disp.PowerIsOnFeedback.OutputChange += (o, a) =>
+ {
+ if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue)
+ {
+ if (!disp.PowerIsOnFeedback.BoolValue)
+ disp.CurrentSourceInfo = null;
+ OnFeedback.FireUpdate();
+ }
+ if (disp.PowerIsOnFeedback.BoolValue)
+ {
+ SetDefaultLevels();
+ }
+ };
+
+ disp.IsWarmingUpFeedback.OutputChange += (o, a) =>
+ {
+ IsWarmingUpFeedback.FireUpdate();
+ if (IsWarmingUpFeedback.BoolValue)
+ {
+ return;
+ }
+
+ var basicVolumeWithFeedback = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ if (basicVolumeWithFeedback != null)
+ {
+ basicVolumeWithFeedback.SetVolume(DefaultVolume);
+ }
+ };
+ disp.IsCoolingDownFeedback.OutputChange += (o, a) => IsCoolingDownFeedback.FireUpdate();
+ }
+ }
+
+ protected override void CustomSetConfig(DeviceConfig config)
+ {
+ var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString());
+
+ if (newPropertiesConfig != null)
+ PropertiesConfig = newPropertiesConfig;
+
+ ConfigWriter.UpdateRoomConfig(config);
+ }
+
+ public override bool CustomActivate()
+ {
+ // Add Occupancy object from config
+ if (PropertiesConfig.Occupancy != null)
+ SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as
+ IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes);
+
+ LogoUrl = PropertiesConfig.Logo.GetUrl();
+ SourceListKey = PropertiesConfig.SourceListKey;
+ DefaultSourceItem = PropertiesConfig.DefaultSourceItem;
+ DefaultVolume = (ushort)(PropertiesConfig.Volumes.Master.Level * 65535 / 100);
+
+ return base.CustomActivate();
+ }
+
+ ///
+ ///
+ ///
+ protected override void EndShutdown()
+ {
+ VideoCodec.EndAllCalls();
+
+ SetDefaultLevels();
+
+ RunDefaultPresentRoute();
+
+ CrestronEnvironment.Sleep(1000);
+
+ RunRouteAction("roomOff", SourceListKey);
+ }
+
+ ///
+ /// Routes the default source item, if any. Returns true when default route exists
+ ///
+ public override bool RunDefaultPresentRoute()
+ {
+ if (DefaultSourceItem != null)
+ RunRouteAction(DefaultSourceItem, SourceListKey);
+
+ return DefaultSourceItem != null;
+ }
+
+ ///
+ /// Sets up the room when started into call mode without presenting a source
+ ///
+ ///
+ public bool RunDefaultCallRoute()
+ {
+ RunRouteAction(DefaultCodecRouteString, SourceListKey);
+ return true;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RunRouteAction(string routeKey, string sourceListKey)
+ {
+ RunRouteAction(routeKey, sourceListKey, null);
+ }
+
+ ///
+ /// Gets a source from config list SourceListKey and dynamically build and executes the
+ /// route or commands
+ ///
+ ///
+ ///
+ ///
+ public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback)
+ {
+ // Run this on a separate thread
+ //new CTimer
+ CrestronInvoke.BeginInvoke(o =>
+ {
+ // try to prevent multiple simultaneous selections
+ _sourceSelectLock.TryEnter();
+
+ try
+ {
+
+ Debug.Console(1, this, "Run route action '{0}'", routeKey);
+ var dict = ConfigReader.ConfigObject.GetSourceListForKey(sourceListKey);
+ if (dict == null)
+ {
+ Debug.Console(1, this, "WARNING: Config source list '{0}' not found", sourceListKey);
+ return;
+ }
+
+ // Try to get the list item by it's string key
+ if (!dict.ContainsKey(routeKey))
+ {
+ Debug.Console(1, this, "WARNING: No item '{0}' found on config list '{1}'",
+ routeKey, SourceListKey);
+ return;
+ }
+
+ // End usage timer on last source
+ if (!string.IsNullOrEmpty(_lastSourceKey))
+ {
+ var usageLastSource = dict[_lastSourceKey].SourceDevice as IUsageTracking;
+ if (usageLastSource != null && usageLastSource.UsageTracker != null)
+ {
+ try
+ {
+ // There MAY have been failures in here. Protect
+ usageLastSource.UsageTracker.EndDeviceUsage();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "*#* EXCEPTION in end usage tracking:\r{0}", e);
+ }
+ }
+ }
+
+ // Let's run it
+ var item = dict[routeKey];
+ if (routeKey.ToLower() != "roomoff")
+ {
+
+ _lastSourceKey = routeKey;
+ }
+ //else
+ // CurrentSourceInfoKey = null;
+
+ // hand off the individual routes to this helper
+ foreach (var route in item.RouteList)
+ DoRouteItem(route, item, routeKey);
+
+ // Start usage timer on routed source
+ var usageNewSource = item.SourceDevice as IUsageTracking;
+ if (usageNewSource != null && usageNewSource.UsageTracker != null) // Have to make sure there is a usage tracker!
+ {
+ (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage();
+ }
+
+ // See if this can be moved into common, base-class method -------------
+
+
+ // Set volume control, using default if non provided
+ IBasicVolumeControls volDev = null;
+ // Handle special cases for volume control
+ if (string.IsNullOrEmpty(item.VolumeControlKey)
+ || item.VolumeControlKey.Equals("$defaultAudio", StringComparison.OrdinalIgnoreCase))
+ volDev = DefaultVolumeControls;
+
+ // Or a specific device, probably rarely used.
+ else
+ {
+ var dev = DeviceManager.GetDeviceForKey(item.VolumeControlKey);
+ if (dev is IBasicVolumeControls)
+ volDev = dev as IBasicVolumeControls;
+ else if (dev is IHasVolumeDevice)
+ volDev = (dev as IHasVolumeDevice).VolumeDevice;
+ }
+
+ if (volDev != CurrentVolumeControls)
+ {
+ // zero the volume on the device we are leaving.
+ // Set the volume to default on device we are entering
+ if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback)
+ {
+ var vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ SavedVolumeLevels[vd] = (uint)vd.VolumeLevelFeedback.IntValue;
+ vd.SetVolume(0);
+ }
+
+ CurrentVolumeControls = volDev;
+ if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback)
+ {
+ var vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ ushort vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort)SavedVolumeLevels[vd] : DefaultVolume);
+ vd.SetVolume(vol);
+ }
+ }
+ // -----------------------------------------------------------------------
+
+
+
+ // store the name and UI info for routes
+ if (item.SourceKey == "$off")
+ {
+ LeftDisplay.CurrentSourceInfoKey = routeKey;
+ LeftDisplay.CurrentSourceInfo = null;
+ RightDisplay.CurrentSourceInfoKey = routeKey;
+ RightDisplay.CurrentSourceInfo = null;
+ }
+ //else if (item.SourceKey != null)
+ //{
+ // if(item.RouteList
+ // CurrentSourceInfoKey = routeKey;
+ // CurrentSourceInfo = item;
+ //}
+
+ OnFeedback.FireUpdate();
+
+ // report back when done
+ if (successCallback != null)
+ successCallback();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "ERROR in routing: {0}", e);
+ }
+
+ _sourceSelectLock.Leave();
+ }, 0); // end of CTimer
+ }
+
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void DoRouteItem(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey)
+ {
+ // if there is a $defaultAll on route, run two separate
+ if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase))
+ {
+ // Going to assume a single-path route for now
+ var tempVideo = new SourceRouteListItem
+ {
+ DestinationKey = "$defaultDisplay",
+ SourceKey = route.SourceKey,
+ Type = eRoutingSignalType.Video
+ };
+ DoRoute(tempVideo, sourceItem, sourceItemKey);
+ }
+ else
+ DoRoute(route, sourceItem, sourceItemKey);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool DoRoute(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey)
+ {
+ IRoutingSink dest = null;
+
+ if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase))
+ dest = DefaultAudioDevice as IRoutingSinkNoSwitching;
+ else if (route.DestinationKey.Equals(LeftDisplay.Key, StringComparison.OrdinalIgnoreCase))
+ dest = LeftDisplay;
+ else if (route.DestinationKey.Equals(RightDisplay.Key, StringComparison.OrdinalIgnoreCase))
+ dest = RightDisplay;
+ else
+ dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching;
+
+ if (dest == null)
+ {
+ Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey);
+ return false;
+ }
+
+ if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase))
+ {
+ dest.ReleaseRoute();
+
+
+
+ if (dest is IPower)
+ (dest as IPower).PowerOff();
+ }
+ else
+ {
+ var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs;
+ if (source == null)
+ {
+ Debug.Console(1, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey);
+ return false;
+ }
+ dest.ReleaseAndMakeRoute(source, route.Type);
+
+ dest.CurrentSourceInfoKey = sourceItemKey;
+ dest.CurrentSourceInfo = sourceItem;
+ }
+ return true;
+ }
+
+ public override void RoomVacatedForTimeoutPeriod(object o)
+ {
+ //Implement this
+ }
+
+ ///
+ /// Does what it says
+ ///
+ public override void SetDefaultLevels()
+ {
+ Debug.Console(1, this, "Restoring default levels");
+ var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ if (vc != null)
+ vc.SetVolume(DefaultVolume);
+ }
+ ///
+ /// Will power the room on with the last-used source
+ ///
+ public override void PowerOnToDefaultOrLastSource()
+ {
+ if (!EnablePowerOnToLastSource || _lastSourceKey == null)
+ return;
+ RunRouteAction(_lastSourceKey, SourceListKey);
+ }
+
+ ///
+ /// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions
+ ///
+ public static void AllRoomsOff()
+ {
+ var allRooms = DeviceManager.AllDevices.OfType().Where(d => !d.ExcludeFromGlobalFunctions);
+ foreach (var room in allRooms)
+ {
+ room.RunRouteAction("roomOff", room.SourceListKey);
+ }
+ }
+
+ #region IPrivacy Members
+
+
+ public void PrivacyModeOff()
+ {
+ VideoCodec.PrivacyModeOff();
+ }
+
+ public void PrivacyModeOn()
+ {
+ VideoCodec.PrivacyModeOn();
+ }
+
+ public void PrivacyModeToggle()
+ {
+ VideoCodec.PrivacyModeToggle();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs
new file mode 100644
index 00000000..75b2523d
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs
@@ -0,0 +1,152 @@
+using System;
+using Newtonsoft.Json;
+using PepperDash.Core;
+using PepperDash.Essentials.Core;
+using PepperDash.Essentials.Core.Config;
+using PepperDash.Essentials.Room.Config;
+
+namespace PepperDash.Essentials
+{
+ public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IRunRouteAction,
+ IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay, IHasCurrentSourceInfoChange
+ {
+ public EssentialsHuddleSpaceRoom(DeviceConfig config)
+ : base(config)
+ {
+ try
+ {
+ PropertiesConfig = config.Properties.ToObject();
+ DefaultDisplay =
+ DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching;
+ //why are we assuming IRoutingSinkWithSwitching here?
+
+ DefaultAudioDevice =
+ DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IRoutingSinkWithSwitching;
+
+ Initialize();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "Error building room: \n{0}", e);
+ }
+ }
+
+ public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; }
+
+ private void Initialize()
+ {
+ if (DefaultAudioDevice is IBasicVolumeControls)
+ {
+ DefaultVolumeControls = DefaultAudioDevice as IBasicVolumeControls;
+ }
+ else if (DefaultAudioDevice is IHasVolumeDevice)
+ {
+ DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice;
+ }
+ CurrentVolumeControls = DefaultVolumeControls;
+
+ SourceListKey = String.IsNullOrEmpty(PropertiesConfig.SourceListKey)
+ ? "default"
+ : PropertiesConfig.SourceListKey;
+
+ EnablePowerOnToLastSource = true;
+
+ var disp = DefaultDisplay as DisplayBase;
+ if (disp == null)
+ {
+ return;
+ }
+
+ IsWarmingFeedbackFunc = () => disp.IsWarmingUpFeedback.BoolValue;
+
+ IsCoolingFeedbackFunc = () => disp.IsCoolingDownFeedback.BoolValue;
+
+ OnFeedbackFunc = () => CurrentSourceInfo != null
+ && CurrentSourceInfo.Type == eSourceListItemType.Route;
+
+ InitializeDisplay(disp);
+ }
+
+ protected override void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
+ {
+ IsCoolingDownFeedback.FireUpdate();
+ }
+
+ protected override void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
+ {
+ var display = sender as DisplayBase;
+
+ if (display == null)
+ {
+ return;
+ }
+
+ if (display.PowerIsOnFeedback.BoolValue == OnFeedback.BoolValue)
+ {
+ return;
+ }
+
+ if (!display.PowerIsOnFeedback.BoolValue)
+ {
+ CurrentSourceInfo = null;
+ }
+ OnFeedback.FireUpdate();
+ }
+
+ protected override void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs)
+ {
+ IsWarmingUpFeedback.FireUpdate();
+
+ if (IsWarmingUpFeedback.BoolValue)
+ {
+ return;
+ }
+
+ var displayVolumeControl = DefaultDisplay as IBasicVolumeWithFeedback;
+
+ if (displayVolumeControl == null)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Error,
+ "Default display {0} is not volume control control provider", DefaultDisplay.Key);
+ return;
+ }
+
+ displayVolumeControl.SetVolume(DefaultVolume);
+ }
+
+ protected override void CustomSetConfig(DeviceConfig config)
+ {
+ var newPropertiesConfig =
+ JsonConvert.DeserializeObject(config.Properties.ToString());
+
+ if (newPropertiesConfig != null)
+ {
+ PropertiesConfig = newPropertiesConfig;
+ }
+
+ ConfigWriter.UpdateRoomConfig(config);
+ }
+
+ public override bool CustomActivate()
+ {
+ // Add Occupancy object from config
+ if (PropertiesConfig.Occupancy != null)
+ {
+ SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as
+ IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes);
+ }
+
+ LogoUrl = PropertiesConfig.Logo.GetUrl();
+ SourceListKey = PropertiesConfig.SourceListKey;
+ DefaultSourceItem = PropertiesConfig.DefaultSourceItem;
+ DefaultVolume = (ushort) (PropertiesConfig.Volumes.Master.Level*65535/100);
+
+ return base.CustomActivate();
+ }
+
+ public override void RoomVacatedForTimeoutPeriod(object o)
+ {
+ //TODO: Implement RoomVacatedForTimeoutPeriod
+ }
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs
new file mode 100644
index 00000000..6f91053b
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs
@@ -0,0 +1,627 @@
+using System;
+using System.Linq;
+using Crestron.SimplSharp;
+
+using Newtonsoft.Json;
+
+using PepperDash.Core;
+using PepperDash.Essentials.Core;
+using PepperDash.Essentials.Core.Config;
+using PepperDash.Essentials.Room.Config;
+using PepperDash.Essentials.Devices.Common.Codec;
+using PepperDash.Essentials.Devices.Common.VideoCodec;
+using PepperDash.Essentials.Devices.Common.AudioCodec;
+
+namespace PepperDash.Essentials
+{
+ public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange,
+ IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback
+ {
+ public event EventHandler CurrentVolumeDeviceChange;
+ public event SourceInfoChangeHandler CurrentSourceChange;
+
+
+ //************************
+ // Call-related stuff
+
+ public BoolFeedback InCallFeedback { get; private set; }
+
+ /////
+ ///// Make this more specific
+ /////
+ //public List ActiveCalls { get; private set; }
+
+ ///
+ /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis
+ ///
+ public IntFeedback CallTypeFeedback { get; private set; }
+
+ ///
+ ///
+ ///
+ public BoolFeedback PrivacyModeIsOnFeedback { get; private set; }
+
+ ///
+ /// When something in the room is sharing with the far end or through other means
+ ///
+ public BoolFeedback IsSharingFeedback { get; private set; }
+
+ //************************
+
+ public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; }
+
+ public IRoutingSinkWithSwitching DefaultDisplay { get; private set; }
+ public IBasicVolumeControls DefaultAudioDevice { get; private set; }
+ public IBasicVolumeControls DefaultVolumeControls { get; private set; }
+
+ public VideoCodecBase VideoCodec { get; private set; }
+
+ public AudioCodecBase AudioCodec { get; private set; }
+
+ public bool ExcludeFromGlobalFunctions { get; set; }
+
+ public string DefaultSourceItem { get; set; }
+
+ public ushort DefaultVolume { get; set; }
+
+ ///
+ /// If room is off, enables power on to last source. Default true
+ ///
+ public bool EnablePowerOnToLastSource { get; set; }
+ private string _lastSourceKey;
+
+ ///
+ /// The SourceListItem last run - containing names and icons
+ ///
+ public SourceListItem CurrentSourceInfo
+ {
+ get { return _currentSourceInfo; }
+ set
+ {
+ if (value == _currentSourceInfo) return;
+
+ var handler = CurrentSourceChange;
+ // remove from in-use tracker, if so equipped
+ if(_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking)
+ (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control");
+
+ if (handler != null)
+ handler(_currentSourceInfo, ChangeType.WillChange);
+
+ _currentSourceInfo = value;
+
+ // add to in-use tracking
+ if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking)
+ (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control");
+ if (handler != null)
+ handler(_currentSourceInfo, ChangeType.DidChange);
+ }
+ }
+ private SourceListItem _currentSourceInfo;
+
+ public string CurrentSourceInfoKey { get; set; }
+
+ ///
+ /// "codecOsd"
+ ///
+ public string DefaultCodecRouteString { get { return "codecOsd"; } }
+
+ ///
+ /// Temporary implementation. Returns the schedule-ready object or null if none. Fow now,
+ /// always returns the VideoCodec if it is capable
+ ///
+ public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } }
+
+ private readonly CCriticalSection _sourceSelectLock = new CCriticalSection();
+
+ public EssentialsHuddleVtc1Room(DeviceConfig config)
+ : base(config)
+ {
+ try
+ {
+ PropertiesConfig = JsonConvert.DeserializeObject
+ (config.Properties.ToString());
+ DefaultDisplay = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching;
+
+ VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as
+ VideoCodecBase;
+ if (VideoCodec == null)
+ throw new ArgumentNullException("codec cannot be null");
+
+ AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as
+ AudioCodecBase;
+ if (AudioCodec == null)
+ Debug.Console(0, this, "No Audio Codec Found");
+
+ DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls;
+
+ Initialize();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "Error building room: \n{0}", e);
+ }
+ }
+
+ void Initialize()
+ {
+ try
+ {
+ if (DefaultAudioDevice != null)
+ DefaultVolumeControls = DefaultAudioDevice;
+ else if (DefaultAudioDevice is IHasVolumeDevice)
+ DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice;
+ CurrentVolumeControls = DefaultVolumeControls;
+
+
+ // Combines call feedback from both codecs if available
+ InCallFeedback = new BoolFeedback(() =>
+ {
+ var inAudioCall = false;
+ var inVideoCall = false;
+
+ if (AudioCodec != null)
+ inAudioCall = AudioCodec.IsInCall;
+
+ if (VideoCodec != null)
+ inVideoCall = VideoCodec.IsInCall;
+
+ return inAudioCall || inVideoCall;
+ });
+
+ // Get Microphone Privacy object, if any MUST HAPPEN AFTER setting InCallFeedback
+ MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this);
+
+ Debug.Console(2, this, "Microphone Privacy Config evaluated.");
+
+ // Get emergency object, if any
+ Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this);
+
+ Debug.Console(2, this, "Emergency Config evaluated.");
+
+
+ VideoCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate();
+
+ if (AudioCodec != null)
+ AudioCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate();
+
+ IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue);
+ VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => IsSharingFeedback.FireUpdate();
+
+ // link privacy to VC (for now?)
+ PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue);
+ VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => PrivacyModeIsOnFeedback.FireUpdate();
+
+ CallTypeFeedback = new IntFeedback(() => 0);
+
+ SourceListKey = "default";
+ EnablePowerOnToLastSource = true;
+
+ var disp = DefaultDisplay as DisplayBase;
+ if (disp == null)
+ {
+ return;
+ }
+
+ OnFeedbackFunc = () => CurrentSourceInfo != null
+ && CurrentSourceInfo.Type == eSourceListItemType.Route;
+
+ InitializeDisplay(disp);
+
+ }
+ catch (Exception e)
+ {
+ Debug.Console(0, this, "Error Initializing Room: {0}", e);
+ }
+ }
+
+ #region Overrides of EssentialsRoomBase
+
+ protected override void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
+ {
+ var disp = sender as DisplayBase;
+
+ if (disp == null) return;
+
+ if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue)
+ {
+ if (!disp.PowerIsOnFeedback.BoolValue)
+ CurrentSourceInfo = null;
+ OnFeedback.FireUpdate();
+ }
+ if (disp.PowerIsOnFeedback.BoolValue)
+ {
+ SetDefaultLevels();
+ }
+ }
+
+ protected override void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
+ {
+ IsCoolingDownFeedback.FireUpdate();
+ }
+
+ protected override void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
+ {
+ IsWarmingUpFeedback.FireUpdate();
+
+ if (IsWarmingUpFeedback.BoolValue)
+ {
+ return;
+ }
+
+ var basicVolumeWithFeedback = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ if (basicVolumeWithFeedback != null)
+ {
+ basicVolumeWithFeedback.SetVolume(DefaultVolume);
+ }
+ }
+
+
+ #endregion
+
+ protected override void CustomSetConfig(DeviceConfig config)
+ {
+ var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString());
+
+ if (newPropertiesConfig != null)
+ PropertiesConfig = newPropertiesConfig;
+
+ ConfigWriter.UpdateRoomConfig(config);
+ }
+
+ public override bool CustomActivate()
+ {
+ // Add Occupancy object from config
+ if (PropertiesConfig.Occupancy != null)
+ {
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Setting Occupancy Provider for room");
+ SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as
+ IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes);
+ }
+
+ LogoUrl = PropertiesConfig.Logo.GetUrl();
+ SourceListKey = PropertiesConfig.SourceListKey;
+ DefaultSourceItem = PropertiesConfig.DefaultSourceItem;
+ DefaultVolume = (ushort)(PropertiesConfig.Volumes.Master.Level * 65535 / 100);
+
+ return base.CustomActivate();
+ }
+
+
+ ///
+ ///
+ ///
+ protected override void EndShutdown()
+ {
+ VideoCodec.EndAllCalls();
+
+ SetDefaultLevels();
+
+ RunDefaultPresentRoute();
+
+ CrestronEnvironment.Sleep(1000);
+
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
+
+ RunRouteAction("roomOff");
+ }
+
+ ///
+ /// Routes the default source item, if any. Returns true when default route exists
+ ///
+ public override bool RunDefaultPresentRoute()
+ {
+ if (DefaultSourceItem != null)
+ RunRouteAction(DefaultSourceItem);
+
+ return DefaultSourceItem != null;
+ }
+
+ ///
+ /// Sets up the room when started into call mode without presenting a source
+ ///
+ ///
+ public bool RunDefaultCallRoute()
+ {
+ RunRouteAction(DefaultCodecRouteString);
+ return true;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void RunRouteAction(string routeKey)
+ {
+ RunRouteAction(routeKey, () => { });
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RunRouteAction(string routeKey, string souceListKey)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RunRouteAction(string routeKey, string souceListKey, Action successCallback)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets a source from config list SourceListKey and dynamically build and executes the
+ /// route or commands
+ ///
+ public void RunRouteAction(string routeKey, Action successCallback)
+ {
+ // Run this on a separate thread
+ //new CTimer
+ CrestronInvoke.BeginInvoke(o =>
+ {
+ // try to prevent multiple simultaneous selections
+ _sourceSelectLock.TryEnter();
+
+ try
+ {
+
+ Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Run route action '{0}'", routeKey);
+ var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey);
+ if (dict == null)
+ {
+ Debug.Console(1, this, "WARNING: Config source list '{0}' not found", SourceListKey);
+ return;
+ }
+
+ // Try to get the list item by it's string key
+ if (!dict.ContainsKey(routeKey))
+ {
+ Debug.Console(1, this, "WARNING: No item '{0}' found on config list '{1}'",
+ routeKey, SourceListKey);
+ return;
+ }
+
+ // End usage timer on last source
+ if (!string.IsNullOrEmpty(_lastSourceKey))
+ {
+ var usageLastSource = dict[_lastSourceKey].SourceDevice as IUsageTracking;
+ if (usageLastSource != null && usageLastSource.UsageTracker != null)
+ {
+ try
+ {
+ // There MAY have been failures in here. Protect
+ usageLastSource.UsageTracker.EndDeviceUsage();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "*#* EXCEPTION in end usage tracking:\r{0}", e);
+ }
+ }
+ }
+
+ // Let's run it
+ var item = dict[routeKey];
+ if (routeKey.ToLower() != "roomoff")
+ {
+
+ _lastSourceKey = routeKey;
+ }
+ else
+ CurrentSourceInfoKey = null;
+
+ // hand off the individual routes to this helper
+ foreach (var route in item.RouteList)
+ DoRouteItem(route);
+
+ // Start usage timer on routed source
+ var usageNewSource = item.SourceDevice as IUsageTracking;
+ if (usageNewSource != null && usageNewSource.UsageTracker != null) // Have to make sure there is a usage tracker!
+ {
+ (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage();
+ }
+
+ // See if this can be moved into common, base-class method -------------
+
+
+ // Set volume control, using default if non provided
+ IBasicVolumeControls volDev = null;
+ // Handle special cases for volume control
+ if (string.IsNullOrEmpty(item.VolumeControlKey)
+ || item.VolumeControlKey.Equals("$defaultAudio", StringComparison.OrdinalIgnoreCase))
+ volDev = DefaultVolumeControls;
+ else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase))
+ volDev = DefaultDisplay as IBasicVolumeControls;
+ // Or a specific device, probably rarely used.
+ else
+ {
+ var dev = DeviceManager.GetDeviceForKey(item.VolumeControlKey);
+ if (dev is IBasicVolumeControls)
+ volDev = dev as IBasicVolumeControls;
+ else if (dev is IHasVolumeDevice)
+ volDev = (dev as IHasVolumeDevice).VolumeDevice;
+ }
+
+ if (volDev != CurrentVolumeControls)
+ {
+ // zero the volume on the device we are leaving.
+ // Set the volume to default on device we are entering
+ if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback)
+ {
+ var vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ SavedVolumeLevels[vd] = (uint)vd.VolumeLevelFeedback.IntValue;
+ vd.SetVolume(0);
+ }
+
+ CurrentVolumeControls = volDev;
+ if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback)
+ {
+ var vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ var vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort)SavedVolumeLevels[vd] : DefaultVolume);
+ vd.SetVolume(vol);
+ }
+ }
+ // -----------------------------------------------------------------------
+
+
+
+ // store the name and UI info for routes
+ if (item.SourceKey == "$off")
+ {
+ CurrentSourceInfoKey = routeKey;
+ CurrentSourceInfo = null;
+ }
+ else if (item.SourceKey != null)
+ {
+ CurrentSourceInfoKey = routeKey;
+ CurrentSourceInfo = item;
+ }
+
+ OnFeedback.FireUpdate();
+
+ // report back when done
+ if (successCallback != null)
+ successCallback();
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, this, "ERROR in routing: {0}", e);
+ }
+
+ _sourceSelectLock.Leave();
+ }, 0); // end of CTimer
+ }
+
+ ///
+ ///
+ ///
+ ///
+ void DoRouteItem(SourceRouteListItem route)
+ {
+ // if there is a $defaultAll on route, run two separate
+ if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase))
+ {
+ // Going to assume a single-path route for now
+ var tempVideo = new SourceRouteListItem
+ {
+ DestinationKey = "$defaultDisplay",
+ SourceKey = route.SourceKey,
+ Type = eRoutingSignalType.Video
+ };
+ DoRoute(tempVideo);
+ }
+ else
+ DoRoute(route);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool DoRoute(SourceRouteListItem route)
+ {
+ IRoutingSink dest;
+
+ if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase))
+ dest = DefaultAudioDevice as IRoutingSinkNoSwitching;
+ else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase))
+ dest = DefaultDisplay;
+ else
+ dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching;
+
+ if (dest == null)
+ {
+ Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey);
+ return false;
+ }
+
+ if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase))
+ {
+ dest.ReleaseRoute();
+ if (dest is IPower)
+ (dest as IPower).PowerOff();
+ }
+ else
+ {
+ var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs;
+ if (source == null)
+ {
+ Debug.Console(1, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey);
+ return false;
+ }
+ dest.ReleaseAndMakeRoute(source, route.Type);
+ }
+ return true;
+ }
+
+ public override void RoomVacatedForTimeoutPeriod(object o)
+ {
+ //Implement this
+ }
+
+ ///
+ /// Does what it says
+ ///
+ public override void SetDefaultLevels()
+ {
+ Debug.Console(1, this, "Restoring default levels");
+ var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
+ if (vc != null)
+ vc.SetVolume(DefaultVolume);
+ }
+ ///
+ /// Will power the room on with the last-used source
+ ///
+ public override void PowerOnToDefaultOrLastSource()
+ {
+ if (!EnablePowerOnToLastSource || _lastSourceKey == null)
+ return;
+ RunRouteAction(_lastSourceKey);
+ }
+
+ ///
+ /// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions
+ ///
+ public static void AllRoomsOff()
+ {
+ var allRooms = DeviceManager.AllDevices.Where(d =>
+ d is EssentialsHuddleSpaceRoom && !(d as EssentialsHuddleSpaceRoom).ExcludeFromGlobalFunctions);
+ foreach (var room in allRooms)
+ {
+ var essentialsHuddleSpaceRoom = room as EssentialsHuddleSpaceRoom;
+ if (essentialsHuddleSpaceRoom != null)
+ {
+ essentialsHuddleSpaceRoom.RunRouteAction("roomOff");
+ }
+ }
+ }
+
+ #region IPrivacy Members
+
+
+ public void PrivacyModeOff()
+ {
+ VideoCodec.PrivacyModeOff();
+ }
+
+ public void PrivacyModeOn()
+ {
+ VideoCodec.PrivacyModeOn();
+ }
+
+ public void PrivacyModeToggle()
+ {
+ VideoCodec.PrivacyModeToggle();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs
new file mode 100644
index 00000000..17e764e9
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using PepperDash.Essentials.Core;
+using PepperDash.Essentials.Core.Config;
+
+namespace PepperDash.Essentials
+{
+ ///
+ /// Base class for rooms with more than a single display
+ ///
+ public abstract class EssentialsNDisplayRoomBase : EssentialsRoomBase, IHasMultipleDisplays
+ {
+ //public event SourceInfoChangeHandler CurrentSingleSourceChange;
+
+ public Dictionary Displays { get; protected set;}
+
+ protected EssentialsNDisplayRoomBase(DeviceConfig config)
+ : base (config)
+ {
+ Displays = new Dictionary();
+
+ }
+ }
+}
\ No newline at end of file