renamed room -> rooms

started on Dual Display room;
This commit is contained in:
Andrew Welker
2020-06-30 00:34:24 -06:00
parent 35388ddf66
commit ddb1f254e6
22 changed files with 201 additions and 34 deletions

View File

@@ -0,0 +1,533 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp.Scheduler;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
namespace PepperDash.Essentials.Core.Rooms
{
/// <summary>
/// A device that when linked to a room can power the room on when enabled during scheduled hours.
/// </summary>
public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice
{
RoomOnToDefaultSourceWhenOccupiedConfig PropertiesConfig;
public bool FeatureEnabled { get; private set; }
public DateTime FeatureEnabledTime { get; private set; }
ScheduledEvent FeatureEnableEvent;
const string FeatureEnableEventName = "EnableRoomOnToDefaultSourceWhenOccupied";
public DateTime FeatureDisabledTime { get; private set; }
ScheduledEvent FeatureDisableEvent;
const string FeatureDisableEventName = "DisableRoomOnToDefaultSourceWhenOccupied";
ScheduledEventGroup FeatureEventGroup;
public EssentialsRoomBase Room { get; private set; }
private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom;
public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) :
base (config)
{
PropertiesConfig = JsonConvert.DeserializeObject<RoomOnToDefaultSourceWhenOccupiedConfig>(config.Properties.ToString());
FeatureEventGroup = new ScheduledEventGroup(this.Key);
FeatureEventGroup.RetrieveAllEvents();
// Add to the global class for tracking
Scheduler.AddEventGroup(FeatureEventGroup);
AddPostActivationAction(() =>
{
// Subscribe to room event to know when RoomOccupancy is set and ready to be subscribed to
if (Room != null)
Room.RoomOccupancyIsSet += new EventHandler<EventArgs>(RoomOccupancyIsSet);
else
Debug.Console(1, this, "Room has no RoomOccupancy object set");
var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion";
FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase;
if (FusionRoom == null)
Debug.Console(1, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey);
});
}
public override bool CustomActivate()
{
SetUpDevice();
return base.CustomActivate();
}
/// <summary>
/// Sets up device based on config values
/// </summary>
void SetUpDevice()
{
Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as EssentialsRoomBase;
if (Room != null)
{
try
{
FeatureEnabledTime = DateTime.Parse(PropertiesConfig.OccupancyStartTime);
if (FeatureEnabledTime != null)
{
Debug.Console(1, this, "Enabled Time: {0}", FeatureEnabledTime.ToString());
}
else
Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyStartTime);
}
catch (Exception e)
{
Debug.Console(1, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", PropertiesConfig.OccupancyStartTime, e);
}
try
{
FeatureDisabledTime = DateTime.Parse(PropertiesConfig.OccupancyEndTime);
if (FeatureDisabledTime != null)
{
Debug.Console(1, this, "Disabled Time: {0}", FeatureDisabledTime.ToString());
}
else
Debug.Console(1, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyEndTime);
}
catch (Exception e)
{
Debug.Console(1, this, "Unable to parse a DateTime config value \n Error: {1}", e);
}
if (!PropertiesConfig.EnableRoomOnWhenOccupied)
FeatureEventGroup.ClearAllEvents();
else
{
AddEnableEventToGroup();
AddDisableEventToGroup();
FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack);
FeatureEventGroup.EnableAllEvents();
}
FeatureEnabled = CheckIfFeatureShouldBeEnabled();
}
else
Debug.Console(1, this, "Unable to get room from Device Manager with key: {0}", PropertiesConfig.RoomKey);
}
protected override void CustomSetConfig(DeviceConfig config)
{
var newPropertiesConfig = JsonConvert.DeserializeObject<RoomOnToDefaultSourceWhenOccupiedConfig>(config.Properties.ToString());
if(newPropertiesConfig != null)
PropertiesConfig = newPropertiesConfig;
ConfigWriter.UpdateDeviceConfig(config);
SetUpDevice();
}
/// <summary>
/// Subscribe to feedback from RoomIsOccupiedFeedback on Room
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void RoomOccupancyIsSet(object sender, EventArgs e)
{
if (Room.RoomOccupancy != null)
{
Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange;
Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler<FeedbackEventArgs>(RoomIsOccupiedFeedback_OutputChange);
Debug.Console(1, this, "Subscribed to RoomOccupancy status from: '{0}'", Room.Key);
}
}
void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type)
{
if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration)
{
if (SchEvent.Name == FeatureEnableEventName)
{
if (PropertiesConfig.EnableRoomOnWhenOccupied)
FeatureEnabled = true;
Debug.Console(1, this, "*****Feature Enabled by event.*****");
}
else if (SchEvent.Name == FeatureDisableEventName)
{
FeatureEnabled = false;
Debug.Console(1, this, "*****Feature Disabled by event.*****");
}
}
}
/// <summary>
/// Checks if the feature should be currently enabled. Used on startup if processor starts after start time but before end time
/// </summary>
/// <returns></returns>
bool CheckIfFeatureShouldBeEnabled()
{
bool enabled = false;
if(PropertiesConfig.EnableRoomOnWhenOccupied)
{
Debug.Console(1, this, "Current Time: {0} \n FeatureEnabledTime: {1} \n FeatureDisabledTime: {2}", DateTime.Now, FeatureEnabledTime, FeatureDisabledTime);
if (DateTime.Now.TimeOfDay.CompareTo(FeatureEnabledTime.TimeOfDay) >= 0 && FeatureDisabledTime.TimeOfDay.CompareTo(DateTime.Now.TimeOfDay) > 0)
{
if (SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(DateTime.Now, CalculateDaysOfWeekRecurrence()))
{
enabled = true;
}
}
}
if(enabled)
Debug.Console(1, this, "*****Feature Enabled*****");
else
Debug.Console(1, this, "*****Feature Disabled*****");
return enabled;
}
/// <summary>
/// Respond to Occupancy status event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
Debug.Console(1, this, "RoomIsOccupiedFeeback.OutputChange event fired. e.BoolValue: {0}", e.BoolValue);
if(e.BoolValue)
{
// Occupancy detected
if (FeatureEnabled)
{
// Check room power state first
if (!Room.OnFeedback.BoolValue)
{
Debug.Console(1, this, "Powering Room on to default source");
Room.RunDefaultPresentRoute();
}
}
}
}
void CreateEvent(ScheduledEvent schEvent, string name)
{
Debug.Console(1, this, "Adding Event: '{0}'", name);
// Create the event
if (schEvent == null)
schEvent = new ScheduledEvent(name, FeatureEventGroup);
// Set up its initial properties
if(!schEvent.Acknowledgeable)
schEvent.Acknowledgeable = true;
if(!schEvent.Persistent)
schEvent.Persistent = true;
schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday);
// Set config driven properties
if (schEvent.Name == FeatureEnableEventName)
{
schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature";
var eventRecurrennce = CalculateDaysOfWeekRecurrence();
var eventTime = new DateTime();
// Check to make sure the date for this event is in the future
if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0)
eventTime = FeatureEnabledTime.AddDays(1);
else
eventTime = FeatureEnabledTime;
Debug.Console(1, this, "eventTime (before recurrence check): {0}", eventTime);
// Check day of week against recurrence days and move date ahead as necessary to avoid throwing an exception by trying to set the event
// start date on a day of the week that doesn't match teh recurrence values
while(!SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(eventTime, eventRecurrennce))
{
eventTime = eventTime.AddDays(1);
Debug.Console(1, this, "eventTime does not fall on a recurrence weekday. eventTime: {0}", eventTime);
}
schEvent.DateAndTime.SetAbsoluteEventTime(eventTime);
Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString());
CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime);
schEvent.Recurrence.Weekly(eventRecurrennce);
}
else if (schEvent.Name == FeatureDisableEventName)
{
schEvent.Description = "Disables the RoomOnToDefaultSourceWhenOccupiedFeature";
// Check to make sure the date for this event is in the future
if (DateTime.Now.CompareTo(FeatureDisabledTime) > 0)
schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime.AddDays(1));
else
schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime);
Debug.Console(1, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString());
CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureDisabledTime, FeatureEnabledTime);
schEvent.Recurrence.Daily();
}
}
void CalculateAndSetAcknowledgeExpirationTimeout(ScheduledEvent schEvent, DateTime time1, DateTime time2)
{
Debug.Console(1, this, "time1.Hour = {0}", time1.Hour);
Debug.Console(1, this, "time2.Hour = {0}", time2.Hour);
Debug.Console(1, this, "time1.Minute = {0}", time1.Minute);
Debug.Console(1, this, "time2.Minute = {0}", time2.Minute);
// Calculate the Acknowledge Expiration timer to be the time between the enable and dispable events, less one minute
var ackHours = time2.Hour - time1.Hour;
if(ackHours < 0)
ackHours = ackHours + 24;
var ackMinutes = time2.Minute - time1.Minute;
Debug.Console(1, this, "ackHours = {0}, ackMinutes = {1}", ackHours, ackMinutes);
var ackTotalMinutes = ((ackHours * 60) + ackMinutes) - 1;
var ackExpHour = ackTotalMinutes / 60;
var ackExpMinutes = ackTotalMinutes % 60;
Debug.Console(1, this, "Acknowledge Expiration Timeout: {0} hours, {1} minutes", ackExpHour, ackExpMinutes);
schEvent.AcknowledgeExpirationTimeout.Hour = (ushort)(ackHours);
schEvent.AcknowledgeExpirationTimeout.Minute = (ushort)(ackExpMinutes);
}
/// <summary>
/// Checks existing event to see if it matches the execution time
/// </summary>
/// <param name="existingEvent"></param>
/// <returns></returns>
bool CheckExistingEventTimeForMatch(ScheduledEvent existingEvent, DateTime newTime)
{
bool isMatch = true;
// Check to see if hour and minute match
if (existingEvent.DateAndTime.Hour != newTime.Hour || existingEvent.DateAndTime.Minute != newTime.Minute)
return false;
return isMatch;
}
/// <summary>
/// Checks existing event to see if it matches the recurrence days
/// </summary>
/// <param name="existingEvent"></param>
/// <param name="eWeekdays"></param>
/// <returns></returns>
bool CheckExistingEventRecurrenceForMatch(ScheduledEvent existingEvent, ScheduledEventCommon.eWeekDays eWeekdays)
{
bool isMatch = true;
// Check to see if recurrence matches
if (eWeekdays != existingEvent.Recurrence.RecurrenceDays)
return false;
return isMatch;
}
/// <summary>
/// Adds the Enable event to the local event group and sets its properties based on config
/// </summary>
void AddEnableEventToGroup()
{
if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureEnableEventName))
{
CreateEvent(FeatureEnableEvent, FeatureEnableEventName);
}
else
{
// Check if existing event has same time and recurrence as config values
FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName];
Debug.Console(1, this, "Enable event already found in group");
// Check config times and days against DateAndTime of existing event. If different, delete existing event and create new event
if(!CheckExistingEventTimeForMatch(FeatureEnableEvent, FeatureEnabledTime) || !CheckExistingEventRecurrenceForMatch(FeatureEnableEvent, CalculateDaysOfWeekRecurrence()))
{
Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureEnableEvent.Name);
FeatureEventGroup.DeleteEvent(FeatureEnableEvent);
FeatureEnableEvent = null;
CreateEvent(FeatureEnableEvent, FeatureEnableEventName);
}
}
}
/// <summary>
/// Adds the Enable event to the local event group and sets its properties based on config
/// </summary>
void AddDisableEventToGroup()
{
if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureDisableEventName))
{
CreateEvent(FeatureDisableEvent, FeatureDisableEventName);
}
else
{
FeatureDisableEvent = FeatureEventGroup.ScheduledEvents[FeatureDisableEventName];
Debug.Console(1, this, "Disable event already found in group");
// Check config times against DateAndTime of existing event. If different, delete existing event and create new event
if(!CheckExistingEventTimeForMatch(FeatureDisableEvent, FeatureDisabledTime))
{
Debug.Console(1, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureDisableEvent.Name);
FeatureEventGroup.DeleteEvent(FeatureDisableEvent);
FeatureDisableEvent = null;
CreateEvent(FeatureDisableEvent, FeatureDisableEventName);
}
}
}
/// <summary>
/// Calculates the correct bitfield enum value for the event recurrence based on the config values
/// </summary>
/// <returns></returns>
ScheduledEventCommon.eWeekDays CalculateDaysOfWeekRecurrence()
{
ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays();
if (PropertiesConfig.EnableSunday)
value = value | ScheduledEventCommon.eWeekDays.Sunday;
if (PropertiesConfig.EnableMonday)
value = value | ScheduledEventCommon.eWeekDays.Monday;
if (PropertiesConfig.EnableTuesday)
value = value | ScheduledEventCommon.eWeekDays.Tuesday;
if (PropertiesConfig.EnableWednesday)
value = value | ScheduledEventCommon.eWeekDays.Wednesday;
if (PropertiesConfig.EnableThursday)
value = value | ScheduledEventCommon.eWeekDays.Thursday;
if (PropertiesConfig.EnableFriday)
value = value | ScheduledEventCommon.eWeekDays.Friday;
if (PropertiesConfig.EnableSaturday)
value = value | ScheduledEventCommon.eWeekDays.Saturday;
return value;
}
/// <summary>
/// Callback for event that enables feature. Enables feature if config property is true
/// </summary>
/// <param name="SchEvent"></param>
/// <param name="type"></param>
void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type)
{
if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration)
{
if(PropertiesConfig.EnableRoomOnWhenOccupied)
FeatureEnabled = true;
Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled.");
}
}
/// <summary>
/// Callback for event that enables feature. Disables feature
/// </summary>
/// <param name="SchEvent"></param>
/// <param name="type"></param>
void FeatureDisableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type)
{
if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration)
{
FeatureEnabled = false;
Debug.Console(1, this, "RoomOnToDefaultSourceWhenOccupied Feature Disabled.");
}
}
}
public class RoomOnToDefaultSourceWhenOccupiedConfig
{
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("enableRoomOnWhenOccupied")]
public bool EnableRoomOnWhenOccupied { get; set; }
[JsonProperty("occupancyStartTime")]
public string OccupancyStartTime { get; set; }
[JsonProperty("occupancyEndTime")]
public string OccupancyEndTime { get; set; }
[JsonProperty("enableSunday")]
public bool EnableSunday { get; set; }
[JsonProperty("enableMonday")]
public bool EnableMonday { get; set; }
[JsonProperty("enableTuesday")]
public bool EnableTuesday { get; set; }
[JsonProperty("enableWednesday")]
public bool EnableWednesday { get; set; }
[JsonProperty("enableThursday")]
public bool EnableThursday { get; set; }
[JsonProperty("enableFriday")]
public bool EnableFriday { get; set; }
[JsonProperty("enableSaturday")]
public bool EnableSaturday { get; set; }
}
public class RoomOnToDefaultSourceWhenOccupiedFactory : EssentialsDeviceFactory<RoomOnToDefaultSourceWhenOccupied>
{
public RoomOnToDefaultSourceWhenOccupiedFactory()
{
TypeNames = new List<string>() { "roomonwhenoccupancydetectedfeature" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new RoomOnToDefaultSourceWhenOccupied Device");
return new RoomOnToDefaultSourceWhenOccupied(dc);
}
}
}

View File

@@ -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<DDVC01SpeedDial> SpeedDials { get; set; }
[JsonProperty("volumeSliderNames")]
public List<string> VolumeSliderNames { get; set; }
}
public class DDVC01SpeedDial
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("number")]
public string Number { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.Rooms.Config
{
public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig
{
}
}

View File

@@ -0,0 +1,35 @@
using Newtonsoft.Json;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.Rooms.Config
{
/// <summary>
///
/// </summary>
public class EssentialsHuddleRoomPropertiesConfig : EssentialsRoomPropertiesConfig
{
/// <summary>
/// The key of the default display device
/// </summary>
[JsonProperty("defaultDisplayKey")]
public string DefaultDisplayKey { get; set; }
/// <summary>
/// The key of the default audio device
/// </summary>
[JsonProperty("defaultAudioKey")]
public string DefaultAudioKey { get; set; }
/// <summary>
/// The key of the source list for the room
/// </summary>
[JsonProperty("sourceListKey")]
public string SourceListKey { get; set; }
/// <summary>
/// The key of the default source item from the source list
/// </summary>
[JsonProperty("defaultSourceItem")]
public string DefaultSourceItem { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core.Rooms.Config
{
public class EssentialsHuddleVtc1PropertiesConfig : EssentialsHuddleRoomPropertiesConfig
{
[JsonProperty("videoCodecKey")]
public string VideoCodecKey { get; set; }
[JsonProperty("audioCodecKey")]
public string AudioCodecKey { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
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
{
/// <summary>
///
/// </summary>
public class EssentialsNDisplayRoomPropertiesConfig : EssentialsHuddleRoomPropertiesConfig
{
[JsonProperty("defaultAudioBehavior")]
public string DefaultAudioBehavior { get; set; }
[JsonProperty("defaultVideoBehavior")]
public string DefaultVideoBehavior { get; set; }
[JsonProperty("destinationListKey")]
public string DestinationListKey { get; set; }
}
public class DisplayItem : IKeyName
{
public string Key { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.Rooms.Config
{
/// <summary>
///
/// </summary>
public class EssentialsPresentationRoomPropertiesConfig : EssentialsRoomPropertiesConfig
{
public string DefaultAudioBehavior { get; set; }
public string DefaultAudioKey { get; set; }
public string DefaultVideoBehavior { get; set; }
public List<string> DisplayKeys { get; set; }
public string SourceListKey { get; set; }
public bool HasDsp { get; set; }
public EssentialsPresentationRoomPropertiesConfig()
{
DisplayKeys = new List<string>();
}
}
}

View File

@@ -0,0 +1,296 @@
using System.Collections.Generic;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Privacy;
namespace PepperDash.Essentials.Core.Rooms.Config
{
public class EssentialsRoomConfigHelper
{
/// <summary>
/// Returns a room object from this config data
/// </summary>
/// <returns></returns>
public static Device GetRoomObject(DeviceConfig roomConfig)
{
var typeName = roomConfig.Type.ToLower();
EssentialsRoomBase rm;
if (typeName == "huddle")
{
var huddle = new EssentialsHuddleSpaceRoom(roomConfig);
return huddle;
}
if (typeName == "huddlevtc1")
{
rm = new EssentialsHuddleVtc1Room(roomConfig);
return rm;
}
if (typeName == "ddvc01Bridge")
{
return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing.
}
if (typeName != "dualdisplay")
{
return null;
}
//rm = new EssentialsDualDisplayRoom();
return null;
}
/// <summary>
/// Gets and operating, standalone emergegncy object that can be plugged into a room.
/// Returns null if there is no emergency defined
/// </summary>
public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room)
{
// This emergency
var emergency = props.Emergency;
if (emergency == null)
{
return 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;
}
/// <summary>
///
/// </summary>
/// <param name="props"></param>
/// <param name="room"></param>
/// <returns></returns>
public static 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
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;
}
switch (behaviour)
{
case "trackroomstate":
{
// Tie LED enable to room power state
var essRoom = room as EssentialsRoomBase;
if (essRoom != null)
{
essRoom.OnFeedback.OutputChange += (o, a) =>
{
mP.EnableLeds = essRoom.OnFeedback.BoolValue;
};
mP.EnableLeds = essRoom.OnFeedback.BoolValue;
}
}
break;
case "trackcallstate":
{
// Tie LED enable to room power state
var inCallRoom = room as IHasInCallFeedback;
if (inCallRoom != null)
{
inCallRoom.InCallFeedback.OutputChange += (o, a) =>
{
mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue;
};
mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue;
}
}
break;
}
return mP;
}
}
/// <summary>
///
/// </summary>
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 EssentialsEnvironmentPropertiesConfig
{
public bool Enabled { get; set; }
[JsonProperty("deviceKeys")]
public List<string> DeviceKeys { get; set; }
public EssentialsEnvironmentPropertiesConfig()
{
DeviceKeys = new List<string>();
}
}
public class EssentialsRoomMicrophonePrivacyConfig
{
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
[JsonProperty("behaviour")]
public string Behaviour { get; set; }
}
/// <summary>
/// Properties for the help text box
/// </summary>
public class EssentialsHelpPropertiesConfig
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("showCallButton")]
public bool ShowCallButton { get; set; }
/// <summary>
/// Defaults to "Call Help Desk"
/// </summary>
[JsonProperty("callButtonText")]
public string CallButtonText { get; set; }
public EssentialsHelpPropertiesConfig()
{
CallButtonText = "Call Help Desk";
}
}
/// <summary>
///
/// </summary>
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; }
}
/// <summary>
/// Properties for the room's logo on panels
/// </summary>
public class EssentialsLogoPropertiesConfig
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
/// <summary>
/// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo
/// </summary>
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;
}
}
/// <summary>
/// Represents occupancy sensor(s) setup for a room
/// </summary>
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; }
}
}

View File

@@ -0,0 +1,30 @@
namespace PepperDash.Essentials.Core.Rooms.Config
{
/// <summary>
///
/// </summary>
public class EssentialsRoomEmergencyConfig
{
public EssentialsRoomEmergencyTriggerConfig Trigger { get; set; }
public string Behavior { get; set; }
}
/// <summary>
///
/// </summary>
public class EssentialsRoomEmergencyTriggerConfig
{
/// <summary>
/// contact,
/// </summary>
public string Type { get; set; }
/// <summary>
/// Input number if contact
/// </summary>
public int Number { get; set; }
public bool TriggerOnClose { get; set; }
}
}

View File

@@ -0,0 +1,64 @@
namespace PepperDash.Essentials
{
/// <summary>
///
/// </summary>
public class EssentialsRoomVolumesConfig
{
public EssentialsVolumeLevelConfig Master { get; set; }
public EssentialsVolumeLevelConfig Program { get; set; }
public EssentialsVolumeLevelConfig AudioCallRx { get; set; }
public EssentialsVolumeLevelConfig AudioCallTx { get; set; }
}
/// <summary>
///
/// </summary>
public class EssentialsVolumeLevelConfig
{
public string DeviceKey { get; set; }
public string Label { get; set; }
public int Level { get; set; }
/*
/// <summary>
/// Helper to get the device associated with key - one timer.
/// </summary>
public IBasicVolumeWithFeedback GetDevice()
{
// DM output card format: deviceKey--output~number, dm8x8-1--output~4
var match = Regex.Match(DeviceKey, @"([-_\w]+)--(\w+)~(\d+)");
if (match.Success)
{
var devKey = match.Groups[1].Value;
var chassis = DeviceManager.GetDeviceForKey(devKey) as DmChassisController;
if (chassis != null)
{
var outputNum = Convert.ToUInt32(match.Groups[3].Value);
if (chassis.VolumeControls.ContainsKey(outputNum)) // should always...
return chassis.VolumeControls[outputNum];
}
// No volume for some reason. We have failed as developers
return null;
}
// DSP format: deviceKey--levelName, biampTesira-1--master
match = Regex.Match(DeviceKey, @"([-_\w]+)--(.+)");
if (match.Success)
{
var devKey = match.Groups[1].Value;
var dsp = DeviceManager.GetDeviceForKey(devKey) as BiampTesiraForteDsp;
if (dsp != null)
{
var levelTag = match.Groups[2].Value;
if (dsp.LevelControlPoints.ContainsKey(levelTag)) // should always...
return dsp.LevelControlPoints[levelTag];
}
// No volume for some reason. We have failed as developers
return null;
}
return null;
}*/
}
}

View File

@@ -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();
}
/// <summary>
///
/// </summary>
public void RunEmergencyBehavior()
{
if (Behavior.Equals("shutdown"))
Room.Shutdown();
}
}
}

View File

@@ -0,0 +1,812 @@
using System;
using System.Collections.Generic;
using System.Linq;
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
{
/// <summary>
///
/// </summary>
public abstract class EssentialsRoomBase : ReconfigurableDevice
{
protected EssentialsRoomPropertiesConfig BaseConfig;
protected IBasicVolumeControls CurrentAudioDevice;
protected Func<bool> IsCoolingFeedbackFunc;
protected Func<bool> IsWarmingFeedbackFunc;
protected string LastSourceKey;
/// <summary>
///
/// </summary>
protected Func<bool> OnFeedbackFunc;
/// <summary>
/// Seconds after vacancy detected until prompt is displayed
/// </summary>
protected int RoomVacancyShutdownPromptSeconds;
/// <summary>
/// Seconds after vacancy prompt is displayed until shutdown
/// </summary>
protected int RoomVacancyShutdownSeconds;
protected CCriticalSection RoutingLock = new CCriticalSection();
protected Dictionary<IBasicVolumeWithFeedback, uint> SavedVolumeLevels =
new Dictionary<IBasicVolumeWithFeedback, uint>();
private SourceListItem _currentSourceInfo;
/// <summary>
/// If room is off, enables power on to last source. Default true
/// </summary>
public bool EnablePowerOnToLastSource { get; set; }
public const string DefaultSourceListKey = "default";
protected EssentialsRoomBase(DeviceConfig config)
: base(config)
{
BaseConfig = config.Properties.ToObject<EssentialsRoomPropertiesConfig>();
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; }
public string DefaultSourceItem { get; set; }
public ushort DefaultVolume { get; set; }
/// <summary>
/// Sets the volume control device, and attaches/removes InUseTrackers with "audio"
/// tag to device.
/// </summary>
public IBasicVolumeControls CurrentVolumeControls
{
get { return CurrentAudioDevice; }
set
{
if (value == CurrentAudioDevice)
{
return;
}
var handler = CurrentVolumeDeviceChange;
if (handler != null)
{
handler(this,
new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.WillChange));
}
var oldDevice = CurrentAudioDevice as IInUseTracking;
var newDevice = value as IInUseTracking;
UpdateInUseTracking(oldDevice, newDevice);
CurrentAudioDevice = value;
if (handler == null)
{
return;
}
handler(this,
new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.DidChange));
}
}
public bool ExcludeFromGlobalFunctions { get; set; }
public BoolFeedback OnFeedback { get; private set; }
public BoolFeedback IsWarmingUpFeedback { get; private set; }
public BoolFeedback IsCoolingDownFeedback { get; private set; }
public IOccupancyStatusProvider RoomOccupancy { get; private set; }
public bool OccupancyStatusProviderIsRemote { get; private set; }
/// <summary>
/// The config name of the source list
/// </summary>
public string SourceListKey { get; set; }
/// <summary>
/// Timer used for informing the UIs of a shutdown
/// </summary>
public SecondsCountdownTimer ShutdownPromptTimer { get; private set; }
public int ShutdownPromptSeconds { get; set; }
public int ShutdownVacancySeconds { get; set; }
public eShutdownType ShutdownType { get; private set; }
public EssentialsRoomEmergencyBase Emergency { get; set; }
public Privacy.MicrophonePrivacyController MicrophonePrivacy { get; set; }
public string LogoUrl { get; set; }
protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; }
public eVacancyMode VacancyMode { get; private set; }
/// <summary>
/// When volume control devices change, should we zero the one that we are leaving?
/// </summary>
public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; }
#region IHasCurrentSourceInfoChange Members
public event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; }
/// <summary>
/// The SourceListItem last run - containing names and icons
/// </summary>
public SourceListItem CurrentSourceInfo
{
get { return _currentSourceInfo; }
set
{
if (value == _currentSourceInfo)
{
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<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
/// <summary>
/// Fires when the RoomOccupancy object is set
/// </summary>
public event EventHandler<EventArgs> RoomOccupancyIsSet;
private void SetupRoomVacancyShutdown()
{
RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer");
RoomVacancyShutdownTimer.HasFinished += RoomVacancyShutdownPromptTimer_HasFinished; // Shutdown is triggered
RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning
RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt
VacancyMode = eVacancyMode.None;
}
private void SetupShutdownPrompt()
{
// Setup the ShutdownPromptTimer
ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer");
ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) =>
{
if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue)
{
ShutdownType = eShutdownType.None;
}
};
ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered
ShutdownPromptSeconds = 60;
ShutdownVacancySeconds = 120;
ShutdownType = eShutdownType.None;
}
protected void InitializeDisplay(DisplayBase display)
{
// Link power, warming, cooling to display
display.PowerIsOnFeedback.OutputChange += PowerIsOnFeedbackOnOutputChange;
display.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedbackOnOutputChange;
display.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedbackOnOutputChange;
}
protected void UpdateInUseTracking(IInUseTracking oldDev, IInUseTracking newDev)
{
// derigister this room from the device, if it can
if (oldDev != null)
{
oldDev.InUseTracker.RemoveUser(this, "audio");
}
// register this room with new device, if it can
if (newDev != null)
{
newDev.InUseTracker.AddUser(this, "audio");
}
}
protected abstract void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs args);
protected abstract void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs args);
protected abstract void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs args);
private void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e)
{
switch (VacancyMode)
{
case eVacancyMode.None:
StartRoomVacancyTimer(eVacancyMode.InInitialVacancy);
break;
case eVacancyMode.InInitialVacancy:
StartRoomVacancyTimer(eVacancyMode.InShutdownWarning);
break;
case eVacancyMode.InShutdownWarning:
{
StartShutdown(eShutdownType.Vacancy);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting Down due to vacancy.");
break;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="type"></param>
public void StartShutdown(eShutdownType type)
{
// Check for shutdowns running. Manual should override other shutdowns
switch (type)
{
case eShutdownType.Manual:
ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds;
break;
case eShutdownType.Vacancy:
ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds;
break;
}
ShutdownType = type;
ShutdownPromptTimer.Start();
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "ShutdownPromptTimer Started. Type: {0}. Seconds: {1}",
ShutdownType, ShutdownPromptTimer.SecondsToCount);
}
public void StartRoomVacancyTimer(eVacancyMode mode)
{
switch (mode)
{
case eVacancyMode.None:
RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownPromptSeconds;
break;
case eVacancyMode.InInitialVacancy:
RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds;
break;
case eVacancyMode.InShutdownWarning:
RoomVacancyShutdownTimer.SecondsToCount = 60;
break;
}
VacancyMode = mode;
RoomVacancyShutdownTimer.Start();
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Vacancy Timer Started. Mode: {0}. Seconds: {1}",
VacancyMode, RoomVacancyShutdownTimer.SecondsToCount);
}
/// <summary>
/// Resets the vacancy mode and shutsdwon the room
/// </summary>
public void Shutdown()
{
VacancyMode = eVacancyMode.None;
EndShutdown();
}
/// <summary>
/// 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()
/// </summary>
protected virtual void EndShutdown()
{
SetDefaultLevels();
RunDefaultPresentRoute();
//CrestronEnvironment.Sleep(1000); //why?
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
RunRouteAction("roomOff");
}
/// <summary>
/// Override this to implement a default volume level(s) method
/// </summary>
public virtual void SetDefaultLevels()
{
Debug.Console(1, this, "Restoring default levels");
var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
if (vc != null)
{
vc.SetVolume(DefaultVolume);
}
}
/// <summary>
/// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device
/// </summary>
/// <param name="statusProvider"></param>
/// <param name="timeoutMinutes"></param>
public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes)
{
var provider = statusProvider as IKeyed;
if (provider == null)
{
Debug.Console(0, this, "ERROR: Occupancy sensor device is null");
return;
}
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Room Occupancy set to device: '{0}'", provider.Key);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Timeout Minutes from Config is: {0}", timeoutMinutes);
// If status provider is fusion, set flag to remote
if (statusProvider is Fusion.EssentialsHuddleSpaceFusionSystemControllerBase)
{
OccupancyStatusProviderIsRemote = true;
}
if (timeoutMinutes > 0)
{
RoomVacancyShutdownSeconds = timeoutMinutes*60;
}
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "RoomVacancyShutdownSeconds set to {0}",
RoomVacancyShutdownSeconds);
RoomOccupancy = statusProvider;
RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange;
RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange;
OnRoomOccupancyIsSet();
}
private void OnRoomOccupancyIsSet()
{
var handler = RoomOccupancyIsSet;
if (handler != null)
{
handler(this, new EventArgs());
}
}
/// <summary>
/// To allow base class to power room on to last source
/// </summary>
public virtual void PowerOnToDefaultOrLastSource()
{
if (!EnablePowerOnToLastSource || LastSourceKey == null)
{
return;
}
RunRouteAction(LastSourceKey);
}
/// <summary>
/// To allow base class to power room on to default source
/// </summary>
/// <returns></returns>
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)
{
if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Notice: Vacancy Detected");
// Trigger the timer when the room is vacant
StartRoomVacancyTimer(eVacancyMode.InInitialVacancy);
}
else
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Notice: Occupancy Detected");
// Reset the timer when the room is occupied
RoomVacancyShutdownTimer.Cancel();
}
}
/// <summary>
/// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed
/// </summary>
/// <param name="o"></param>
public abstract void RoomVacatedForTimeoutPeriod(object o);
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
public virtual void RunRouteAction(string routeKey)
{
RunRouteAction(routeKey, String.Empty, () => { });
}
/// <summary>
/// Gets a source from config list SourceListKey and dynamically build and executes the
/// route or commands
/// </summary>
public virtual void RunRouteAction(string routeKey, Action successCallback)
{
RunRouteAction(routeKey, String.Empty, successCallback);
}
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
/// <param name="sourceListKey"></param>
public virtual void RunRouteAction(string routeKey, string sourceListKey)
{
RunRouteAction(routeKey, sourceListKey, () => { });
}
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
/// <param name="sourceListKey"></param>
/// <param name="successCallback"></param>
public virtual void RunRouteAction(string routeKey, string sourceListKey, Action successCallback)
{
var routeObject =
new {RouteKey = routeKey, SourceListKey = sourceListKey, SuccessCallback = successCallback};
CrestronInvoke.BeginInvoke(RunRouteAction, routeObject); // end of BeginInvoke
}
protected virtual void RunRouteAction(object routeObject)
{
try
{
RoutingLock.Enter();
var routeObj = new {RouteKey = "", SourceListKey = "", SuccessCallback = new Action(() => { })};
routeObj = Cast(routeObj, routeObject);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Run route action '{0}'", routeObj.RouteKey);
var sourceList = GetSourceListForKey(routeObj.RouteKey, routeObj.SourceListKey);
if (sourceList == null)
{
Debug.Console(0, this, "No source list found for key {0}", routeObj.SourceListKey);
return;
}
var item = sourceList[routeObj.RouteKey];
// End usage timer on last source
StopUsageTrackingOnCurrentSource(sourceList);
// Let's run it
if (routeObj.RouteKey.ToLower() != "roomoff")
{
LastSourceKey = routeObj.RouteKey;
}
else
{
CurrentSourceInfoKey = null;
}
foreach (var route in item.RouteList)
{
var tempVideo = new SourceRouteListItem
{
DestinationKey = "$defaultDisplay",
SourceKey = route.SourceKey,
Type = eRoutingSignalType.Video
};
var routeItem = route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)
? tempVideo
: route;
DoRoute(routeItem);
}
// Start usage timer on routed source
if (item.SourceDevice is IUsageTracking)
{
(item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage();
}
// Set volume control, using default if non provided
SetVolumeControl(item);
// store the name and UI info for routes
if (item.SourceKey == "$off")
{
CurrentSourceInfoKey = routeObj.RouteKey;
CurrentSourceInfo = null;
}
else if (item.SourceKey != null)
{
CurrentSourceInfoKey = routeObj.RouteKey;
CurrentSourceInfo = item;
}
OnFeedback.FireUpdate();
// report back when done
if (routeObj.SuccessCallback != null)
{
routeObj.SuccessCallback();
}
}
finally
{
RoutingLock.Leave();
}
}
private static T Cast<T>(T typeHolder, object m)
{
return (T) m;
}
/// <summary>
///
/// </summary>
/// <param name="route"></param>
/// <returns></returns>
protected void DoRoute(SourceRouteListItem route)
{
var dest = GetDestination(route);
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;
}
dest.ReleaseAndMakeRoute(source, route.Type);
}
}
private IRoutingSink GetDestination(SourceRouteListItem route)
{
IRoutingSink dest;
if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase))
{
dest = DefaultAudioDevice;
}
else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase))
{
dest = DefaultDisplay;
}
else
{
dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSink;
}
if (dest != null)
{
return dest;
}
Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey);
return dest;
}
private void SetVolumeControl(SourceListItem item)
{
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;
}
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)
{
return;
}
IBasicVolumeWithFeedback vd;
// zero the volume on the device we are leaving.
// Set the volume to default on device we are entering
if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback)
{
vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
SavedVolumeLevels[vd] = (uint) vd.VolumeLevelFeedback.IntValue;
vd.SetVolume(0);
}
CurrentVolumeControls = volDev;
if (!ZeroVolumeWhenSwtichingVolumeDevices || !(CurrentVolumeControls is IBasicVolumeWithFeedback))
{
return;
}
vd = CurrentVolumeControls as IBasicVolumeWithFeedback;
var vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort) SavedVolumeLevels[vd] : DefaultVolume);
vd.SetVolume(vol);
}
private void StopUsageTrackingOnCurrentSource(Dictionary<string, SourceListItem> sourceList)
{
if (string.IsNullOrEmpty(LastSourceKey))
{
return;
}
var lastSource = sourceList[LastSourceKey].SourceDevice;
try
{
if (lastSource is IUsageTracking)
{
(lastSource as IUsageTracking).UsageTracker.EndDeviceUsage();
}
}
catch (Exception e)
{
Debug.Console(1, this, "*#* EXCEPTION in end usage tracking (257):\r{0}", e);
}
}
private Dictionary<string, SourceListItem> GetSourceListForKey(string routeKey, string sourceListKey)
{
var slKey = String.IsNullOrEmpty(sourceListKey) ? SourceListKey : sourceListKey;
var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(slKey);
if (sourceList == null)
{
Debug.Console(1, this, "WARNING: Config source list '{0}' not found", slKey);
return null;
}
// Try to get the list item by it's string key
if (sourceList.ContainsKey(routeKey))
{
return sourceList;
}
Debug.Console(1, this, "WARNING: No source list '{0}' found in config source lists '{1}'",
routeKey, SourceListKey);
return null;
}
/// <summary>
/// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions
/// </summary>
public static void AllRoomsOff()
{
var allRooms = DeviceManager.AllDevices.OfType<EssentialsRoomBase>().Where(d =>
!d.ExcludeFromGlobalFunctions);
foreach (var room in allRooms)
{
room.RunRouteAction("roomOff");
}
}
}
/// <summary>
/// To describe the various ways a room may be shutting down
/// </summary>
public enum eShutdownType
{
None = 0,
External,
Manual,
Vacancy
}
public enum eVacancyMode
{
None = 0,
InInitialVacancy,
InShutdownWarning
}
/// <summary>
///
/// </summary>
public enum eWarmingCoolingMode
{
None,
Warming,
Cooling
}
public abstract class EssentialsRoomEmergencyBase : IKeyed
{
protected EssentialsRoomEmergencyBase(string key)
{
Key = key;
}
#region IKeyed Members
public string Key { get; private set; }
#endregion
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core.Rooms
{
/// <summary>
/// For rooms with in call feedback
/// </summary>
public interface IHasInCallFeedback
{
BoolFeedback InCallFeedback { get; }
}
/// <summary>
/// For rooms with a single display
/// </summary>
public interface IHasDefaultDisplay
{
IRoutingSinkWithSwitching DefaultDisplay { get; }
}
/// <summary>
/// For rooms with multiple displays
/// </summary>
public interface IHasMultipleDisplays
{
Dictionary<eSourceListItemDestinationTypes, IRoutingSinkWithSwitching> Displays { get; }
}
/// <summary>
/// For rooms with routing
/// </summary>
public interface IRunRouteAction
{
void RunRouteAction(string routeKey, string sourceListKey);
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
}
/// <summary>
/// For rooms that default presentation only routing
/// </summary>
public interface IRunDefaultPresentRoute
{
bool RunDefaultPresentRoute();
}
/// <summary>
/// For rooms that have default presentation and calling routes
/// </summary>
public interface IRunDefaultCallRoute : IRunDefaultPresentRoute
{
bool RunDefaultCallRoute();
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
//***************************************************************************************************
public abstract class Room : Device, IHasFeedback
{
public abstract BoolFeedback RoomIsOnFeedback { get; protected set; }
public abstract BoolFeedback IsCoolingDownFeedback { get; protected set; }
public abstract BoolFeedback IsWarmingUpFeedback { get; protected set; }
// In concrete classes, these should be computed from the relevant devices
public virtual uint CooldownTime { get { return 10000; } }
public virtual uint WarmupTime { get { return 5000; } }
public string Description { get; set; }
public string HelpMessage { get; set; }
public Room(string key, string name)
: base(key, name)
{
Description = "";
HelpMessage = "";
}
public virtual void RoomOn() { }
public virtual void RoomOff() { }
#region IDeviceWithOutputs Members
public virtual FeedbackCollection<Feedback> Feedbacks
{
get
{
return new FeedbackCollection<Feedback>
{
RoomIsOnFeedback,
IsCoolingDownFeedback,
IsWarmingUpFeedback
};
}
}
#endregion
}
}

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Rooms.Config;
using PepperDash_Essentials_Core.Devices;
namespace PepperDash.Essentials
{
public class EssentialsDualDisplayRoom : EssentialsRoomBase
{
public const string DefaultDestinationListKey = "default";
private const string LeftDestinationKey = "leftDisplay";
private const string RightDestinationKey = "rightDisplay";
private readonly EssentialsDualDisplayRoomPropertiesConfig _config;
private string _destinationListKey;
public EssentialsDualDisplayRoom(DeviceConfig config) : base(config)
{
_config = config.Properties.ToObject<EssentialsDualDisplayRoomPropertiesConfig>();
DefaultDisplay = DeviceManager.GetDeviceForKey(_config.DefaultDisplayKey) as IRoutingSinkWithSwitching;
DefaultAudioDevice = DeviceManager.GetDeviceForKey(_config.DefaultAudioKey) as IRoutingSinkWithSwitching;
Initialize();
}
public Dictionary<string, DestinationListItem> DestinationList { get; private set; }
public BoolFeedback LeftDisplayIsWarmingUpFeedback { get; private set; }
public BoolFeedback RightDisplayIsWarmingUpFeedback { get; private set; }
public BoolFeedback LeftDisplayIsCoolingDownFeedback { get; private set; }
public BoolFeedback RightDisplayIsCoolingDownFeedback { get; private set; }
public IRoutingSinkWithSwitching LeftDisplay { get; private set; }
public IRoutingSinkWithSwitching RightDisplay { 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;
_destinationListKey = String.IsNullOrEmpty(_config.DestinationListKey)
? DefaultDestinationListKey
: _config.DestinationListKey;
SourceListKey = String.IsNullOrEmpty(_config.SourceListKey) ? DefaultSourceListKey : _config.SourceListKey;
InitializeDestinations();
}
private void InitializeDestinations()
{
DestinationList = ConfigReader.ConfigObject.GetDestinationListForKey(_destinationListKey);
if (DestinationList == null)
{
Debug.Console(0, Debug.ErrorLogLevel.Error, "No destination list with key {0} found",
_destinationListKey);
return;
}
//left destination is defined as the display on the 0 surface, at location 0,0 (h, v)
var leftDest = GetDestinationForKey(LeftDestinationKey);
//not found by key, check by expected location
if (leftDest == null)
{
DestinationList.Values.FirstOrDefault(
(li) => li.SurfaceLocation == 0 && li.HorizontalLocation == 0 && li.VerticalLocation == 0);
}
//right destination is defined as the display on the 0 surface, at location 0,0 (h, v)
var rightDest = GetDestinationForKey(RightDestinationKey);
//not found by key, check by expected location
if (rightDest == null)
{
DestinationList.Values.FirstOrDefault(
(li) => li.SurfaceLocation == 0 && li.HorizontalLocation == 1 && li.VerticalLocation == 0);
}
if (leftDest == null || rightDest == null)
{
Debug.Console(0, Debug.ErrorLogLevel.Error, "Dual destinations not defined. Please check configuration");
return;
}
var leftDisplay = leftDest.SinkDevice as DisplayBase;
var rightDisplay = rightDest.SinkDevice as DisplayBase;
if (leftDisplay == null || rightDisplay == null)
{
Debug.Console(0, Debug.ErrorLogLevel.Error,
"Display for key {0} && key {1} not found. Please check configurattion");
Debug.Console(0, Debug.ErrorLogLevel.Error, "LeftDisplay: {0}\r\nRightDisplay: {1}", leftDest.SinkKey,
rightDest.SinkKey);
return;
}
//need displays as DisplayBase later instead of IRoutingSinkWithSwtich
LeftDisplay = leftDisplay;
RightDisplay = rightDisplay;
//TODO: Check this definition for on for dual display rooms
OnFeedbackFunc = () => CurrentSourceInfo != null && CurrentSourceInfo.Type == eSourceListItemType.Route;
IsWarmingFeedbackFunc =
() => leftDisplay.IsWarmingUpFeedback.BoolValue || rightDisplay.IsWarmingUpFeedback.BoolValue;
IsCoolingFeedbackFunc = () => leftDisplay.IsWarmingUpFeedback.BoolValue ||
rightDisplay.IsCoolingDownFeedback.BoolValue;
LeftDisplayIsWarmingUpFeedback = new BoolFeedback(() => leftDisplay.IsWarmingUpFeedback.BoolValue);
LeftDisplayIsCoolingDownFeedback = new BoolFeedback(() => leftDisplay.IsCoolingDownFeedback.BoolValue);
RightDisplayIsWarmingUpFeedback = new BoolFeedback(() => rightDisplay.IsWarmingUpFeedback.BoolValue);
RightDisplayIsCoolingDownFeedback = new BoolFeedback(() => rightDisplay.IsCoolingDownFeedback.BoolValue);
InitializeDisplay(leftDisplay);
InitializeDisplay(rightDisplay);
}
private DestinationListItem GetDestinationForKey(string key)
{
DestinationListItem returnValue;
DestinationList.TryGetValue(key, out returnValue);
return returnValue;
}
protected override void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
{
IsCoolingDownFeedback.FireUpdate();
LeftDisplayIsCoolingDownFeedback.FireUpdate();
RightDisplayIsCoolingDownFeedback.FireUpdate();
}
public override void RoomVacatedForTimeoutPeriod(object o)
{
throw new NotImplementedException();
}
protected override void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
{
IsWarmingUpFeedback.FireUpdate();
LeftDisplayIsWarmingUpFeedback.FireUpdate();
RightDisplayIsWarmingUpFeedback.FireUpdate();
}
protected override void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs args)
{
var ld = LeftDisplay as DisplayBase;
var rd = RightDisplay as DisplayBase;
if (ld == null || rd == null)
{
return;
}
//if room is already on and either display is still on, no need to fire update
if (OnFeedback.BoolValue && (ld.PowerIsOnFeedback.BoolValue || rd.PowerIsOnFeedback.BoolValue))
{
return;
}
//if both displays are off, room is off, clear the current source
if (!ld.PowerIsOnFeedback.BoolValue && !rd.PowerIsOnFeedback.BoolValue)
{
CurrentSourceInfo = null;
}
OnFeedback.FireUpdate();
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Rooms;
using PepperDash.Essentials.Core.Rooms.Config;
namespace PepperDash.Essentials
{
public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IRunRouteAction,
IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay, IHasCurrentSourceInfoChange
{
public EssentialsHuddleSpaceRoom(DeviceConfig config)
: base(config)
{
try
{
PropertiesConfig = config.Properties.ToObject<EssentialsHuddleRoomPropertiesConfig>();
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)
? DefaultSourceListKey
: 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<EssentialsHuddleRoomPropertiesConfig>(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
}
}
}

View File

@@ -0,0 +1,588 @@
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.Core.Devices.AudioCodec;
using PepperDash.Essentials.Core.Devices.Codec;
using PepperDash.Essentials.Core.Devices.VideoCodec;
using PepperDash.Essentials.Core.Rooms;
using PepperDash.Essentials.Core.Rooms.Config;
namespace PepperDash.Essentials
{
public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange,
IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback
{
public event EventHandler<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
public event SourceInfoChangeHandler CurrentSourceChange;
//************************
// Call-related stuff
public BoolFeedback InCallFeedback { get; private set; }
///// <summary>
///// Make this more specific
///// </summary>
//public List<CodecActiveCallItem> ActiveCalls { get; private set; }
/// <summary>
/// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis
/// </summary>
public IntFeedback CallTypeFeedback { get; private set; }
/// <summary>
///
/// </summary>
public BoolFeedback PrivacyModeIsOnFeedback { get; private set; }
/// <summary>
/// When something in the room is sharing with the far end or through other means
/// </summary>
public BoolFeedback IsSharingFeedback { get; private set; }
//************************
public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; }
public VideoCodecBase VideoCodec { get; private set; }
public AudioCodecBase AudioCodec { get; private set; }
/// <summary>
/// If room is off, enables power on to last source. Default true
/// </summary>
private string _lastSourceKey;
/// <summary>
/// "codecOsd"
/// </summary>
public string DefaultCodecRouteString { get { return "codecOsd"; } }
/// <summary>
/// Temporary implementation. Returns the schedule-ready object or null if none. Fow now,
/// always returns the VideoCodec if it is capable
/// </summary>
public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } }
private readonly CCriticalSection _sourceSelectLock = new CCriticalSection();
public EssentialsHuddleVtc1Room(DeviceConfig config)
: base(config)
{
try
{
PropertiesConfig = JsonConvert.DeserializeObject<EssentialsHuddleVtc1PropertiesConfig>
(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 IRoutingSink;
Initialize();
}
catch (Exception e)
{
Debug.Console(1, this, "Error building room: \n{0}", e);
}
}
void Initialize()
{
try
{
if (DefaultAudioDevice != null)
DefaultVolumeControls = DefaultAudioDevice as IBasicVolumeControls;
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<EssentialsHuddleVtc1PropertiesConfig>(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();
}
/// <summary>
///
/// </summary>
protected override void EndShutdown()
{
VideoCodec.EndAllCalls();
SetDefaultLevels();
RunDefaultPresentRoute();
CrestronEnvironment.Sleep(1000);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room");
RunRouteAction("roomOff");
}
/// <summary>
/// Routes the default source item, if any. Returns true when default route exists
/// </summary>
public override bool RunDefaultPresentRoute()
{
if (DefaultSourceItem != null)
RunRouteAction(DefaultSourceItem);
return DefaultSourceItem != null;
}
/// <summary>
/// Sets up the room when started into call mode without presenting a source
/// </summary>
/// <returns></returns>
public bool RunDefaultCallRoute()
{
RunRouteAction(DefaultCodecRouteString);
return true;
}
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
public override void RunRouteAction(string routeKey)
{
RunRouteAction(routeKey, () => { });
}
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
/// <param name="souceListKey"></param>
public void RunRouteAction(string routeKey, string souceListKey)
{
throw new NotImplementedException();
}
/// <summary>
///
/// </summary>
/// <param name="routeKey"></param>
/// <param name="souceListKey"></param>
/// <param name="successCallback"></param>
public void RunRouteAction(string routeKey, string souceListKey, Action successCallback)
{
throw new NotImplementedException();
}
/// <summary>
/// Gets a source from config list SourceListKey and dynamically build and executes the
/// route or commands
/// </summary>
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
}
/// <summary>
///
/// </summary>
/// <param name="route"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="route"></param>
/// <returns></returns>
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
}
/// <summary>
/// Does what it says
/// </summary>
public override void SetDefaultLevels()
{
Debug.Console(1, this, "Restoring default levels");
var vc = CurrentVolumeControls as IBasicVolumeWithFeedback;
if (vc != null)
vc.SetVolume(DefaultVolume);
}
/// <summary>
/// Will power the room on with the last-used source
/// </summary>
public override void PowerOnToDefaultOrLastSource()
{
if (!EnablePowerOnToLastSource || _lastSourceKey == null)
return;
RunRouteAction(_lastSourceKey);
}
/// <summary>
/// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions
/// </summary>
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
}
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Rooms;
namespace PepperDash.Essentials
{
/// <summary>
/// Base class for rooms with more than a single display
/// </summary>
public abstract class EssentialsNDisplayRoomBase : EssentialsRoomBase, IHasMultipleDisplays
{
//public event SourceInfoChangeHandler CurrentSingleSourceChange;
public Dictionary<eSourceListItemDestinationTypes, IRoutingSinkWithSwitching> Displays { get; protected set;}
protected EssentialsNDisplayRoomBase(DeviceConfig config)
: base (config)
{
Displays = new Dictionary<eSourceListItemDestinationTypes, IRoutingSinkWithSwitching>();
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Core
{
public interface IOccupancyStatusProvider
{
BoolFeedback RoomIsOccupiedFeedback { get; }
}
}