Compare commits

..

84 Commits

Author SHA1 Message Date
jkdevito
3b6f1907c6 ci(force-patch): increment patch by force 2025-11-20 21:54:52 -06:00
jkdevito
54da30a650 ci(force-patch): increment patch by force 2025-11-20 17:20:35 -06:00
jkdevito
475c16b9c2 ci(force-patch): increment patch by force 2025-11-20 16:57:11 -06:00
jkdevito
81aab0b8c1 ci(force-patch): increment patch by force 2025-11-20 16:47:43 -06:00
jkdevito
770d7fa0ea ci(force-patch): increment patch by force 2025-11-20 16:40:02 -06:00
jkdevito
5f292cacd9 fix: update build step to use reordered workflow for 4Series builds 2025-11-20 16:31:25 -06:00
Neil Dorin
2fbc32947c Merge pull request #1357 from PepperDash/url-parsing
fix: check for multiple URL patterns for both template & system URLS
2025-11-18 14:14:16 -05:00
Andrew Welker
c06b57a5f9 fix: check for multiple URL patterns for both template & system URLS 2025-11-18 12:30:54 -06:00
Neil Dorin
6d64fffc50 Merge pull request #1356 from PepperDash/mc-subscription-logging
Multiple Fixes
2025-11-14 15:44:58 -05:00
Andrew Welker
0c4ebdaf1d fix: change how subscription state is logged to reduce traffic to console 2025-11-13 09:56:29 -06:00
Andrew Welker
2c49fb9321 fix: parse current Portal URLS for system and template UUIDs correctly 2025-11-12 16:58:23 -06:00
Neil Dorin
c55de61da9 fix: enhance COM port registration logging and update GenericSink class for input switching 2025-11-05 14:43:42 -07:00
Andrew Welker
42444ede0a Merge pull request #1354 from PepperDash/comport-controller-updates
fix: improve logging for COM port registration and configuration
2025-11-04 10:55:44 -05:00
jkdevito
3d50f5f5ac fix: improve logging for COM port registration and configuration 2025-11-04 09:37:14 -06:00
Nick Genovese
11d62aebe1 Merge pull request #1353 from PepperDash/mc-subscription-concurrency
fix: make subscriber functionality thread-safe
2025-11-03 17:26:31 -04:00
Andrew Welker
edc10a9c2a fix: make subscriberIds private & check for add failure 2025-11-03 15:19:17 -06:00
Andrew Welker
9be5823956 Merge branch 'main' into mc-subscription-concurrency 2025-11-03 15:18:45 -06:00
Andrew Welker
35371dde22 fix: make subscriber functionality thread-safe 2025-11-03 15:13:48 -06:00
Jason DeVito
d3ceb4d7e7 Merge pull request #1352 from PepperDash/theme-saving
fix: use correct overload for PostStatusMessage
2025-11-03 15:08:28 -06:00
Andrew Welker
a782b57100 fix: use correct overload for PostStatusMessage 2025-11-03 14:00:51 -06:00
Neil Dorin
1360de599f Merge pull request #1351 from PepperDash/stream-debugging
fix: centralize debug printing into extension methods
2025-11-03 12:19:05 -05:00
Andrew Welker
fd95f5fed1 docs: update XML docs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-03 11:11:00 -06:00
Andrew Welker
9426dff5df fix: copilot suggestions from PR review 2025-11-03 11:02:39 -06:00
Andrew Welker
0d083e63c6 Merge commit '314570d6c3d78c7a92a362f3ec3a4a06bdbebd28' into stream-debugging 2025-11-03 10:56:53 -06:00
Andrew Welker
6ed7c96ec7 fix: centralize debug printing into extension methods
Stream debugging now uses CrestronConsole instead of debug methods, so that the debug statements will be printed regardless of console debug level. This also means that comm debug statements will NOT be in the Crestron Error log or in the files created by the logging system
2025-11-03 10:53:21 -06:00
Andrew Welker
314570d6c3 fix: change number of retained files to 7 instead of 30 for processors 2025-10-31 13:11:50 -05:00
Andrew Welker
ff609dfecd fix: add config option to turn echo off for SSH
In addition, removed CTimer in favor of System.Threading.Timers Timer in the SSH class, and modified the class to be thread-safe.
2025-10-31 09:01:54 -05:00
Andrew Welker
666be5ae59 Merge pull request #1345 from PepperDash/mc-touchpanel-key
mc touchpanel key
2025-10-30 17:11:15 -04:00
Andrew Welker
bfc9b7e7fa fix: logging & ternary changes 2025-10-30 16:07:26 -05:00
Andrew Welker
b02e952765 Merge remote-tracking branch 'origin/com102-comspec' into mc-touchpanel-key 2025-10-30 15:58:36 -05:00
Andrew Welker
72861a5097 Merge remote-tracking branch 'origin/feature/fusion-help-request' into mc-touchpanel-key 2025-10-30 15:58:26 -05:00
Andrew Welker
44ed067f4d chore: update local build version 2025-10-30 15:57:37 -05:00
Andrew Welker
faa2169baf fix: send correct URL to panel 2025-10-30 15:57:28 -05:00
Andrew Welker
fd40b0c6d1 fix: send all status messages with ClientId 2025-10-30 15:57:15 -05:00
Andrew Welker
afcddad1cc fix: remove async on task, as it's unnecessary 2025-10-30 15:56:50 -05:00
Andrew Welker
c4cf8f13e9 fix: register panel in post phase rather than activation cycle 2025-10-30 15:56:28 -05:00
Neil Dorin
c852f87a27 refactor: Comment out logging statements in help request handling 2025-10-30 14:33:58 -06:00
Neil Dorin
071174fa7d feat: Implement help request status tracking in Fusion system 2025-10-30 14:13:02 -06:00
Neil Dorin
da0f28a10c feat: Enhance IEssentialsRoomFusionController with additional properties and logging 2025-10-29 16:47:18 -06:00
Andrew Welker
0538a304ed Merge branch 'main' into mc-touchpanel-key 2025-10-29 15:13:53 -05:00
Andrew Welker
705c5db237 Merge pull request #1347 from PepperDash/json-console
fix: print json response to console using \r\n instead of \n
2025-10-29 16:12:49 -04:00
Neil Dorin
2e95f5337e feat: Add IEssentialsRoomFusionController and related configurations
- Introduced IEssentialsRoomFusionControllerFactory for creating Fusion Room Controller devices.
- Added IEssentialsRoomFusionControllerPropertiesConfig to define configuration properties for the Fusion Room Controller.
- Updated IFusionHelpRequest interface to include methods for cancelling and toggling help requests.
- Refactored RoomOnToDefaultSourceWhenOccupied to use IEssentialsRoomFusionController instead of EssentialsHuddleSpaceFusionSystemControllerBase.
- Modified EssentialsRoomBase to check for IEssentialsRoomFusionController in occupancy status provider.
2025-10-28 16:49:29 -06:00
jkdevito
ba576180a7 refactor: remove unused using directives in ComPortController 2025-10-28 09:05:56 -05:00
jkdevito
92c9db8237 refactor: improve logging messages in ComPort registration and configuration 2025-10-28 09:04:15 -05:00
jkdevito
c4d064965f refactor: enhance ComPortController with additional event documentation and logging improvements 2025-10-28 08:40:29 -05:00
Neil Dorin
f27965ac29 feat: Add help request functionality to Fusion system controller
Introduce `IFusionHelpRequest` interface for managing help requests, including `HelpRequestResponseFeedback` property and `SendHelpRequest` method. Enhance `EssentialsHuddleSpaceFusionSystemControllerBase` with new properties and methods to support help request tracking and management. Improve code organization and documentation throughout the affected files.
2025-10-27 17:35:38 -06:00
jkdevito
3ce282e2db refactor: comment out debug logging for ComPort registration 2025-10-27 17:33:06 -05:00
jkdevito
32a332a6e2 refactor: update TODO comment for Port registration verification 2025-10-27 17:32:40 -05:00
jkdevito
151a90c9be refactor: streamline ComPort registration and configuration logic with enhanced logging 2025-10-27 17:13:43 -05:00
jkdevito
89e893b3b7 fix: improve formatting and logging in ComPortController 2025-10-27 16:46:09 -05:00
jkdevito
d01eb03890 feat: enhance ComPortController with detailed logging on configuration changes 2025-10-27 14:06:20 -05:00
Andrew Welker
16c92afabb fix: print json response to console using \r\n instead of \n 2025-10-27 09:21:51 -05:00
Andrew Welker
dff5d2d32e Merge pull request #1346 from PepperDash/feature/add-infinetEx-comm-method 2025-10-23 18:50:06 -04:00
Neil Dorin
317bde3814 fix: add InfinetEx to eControlMethods enum 2025-10-23 15:22:46 -06:00
Andrew Welker
8bab3dc966 fix: send touchpanelKey message with all room combiner checks 2025-10-23 11:21:17 -05:00
Andrew Welker
514ac850ca fix: send touchpanel key correctly 2025-10-23 10:01:05 -05:00
Andrew Welker
44432f7a41 fix: send touchpanel key to client when client joins direct server 2025-10-23 09:54:03 -05:00
Andrew Welker
99253b30c2 fix: send touchpanel key to client when client joins 2025-10-23 09:49:45 -05:00
Andrew Welker
bf248fe33e Merge pull request #1343 from PepperDash/device-interface-system
device interface system
2025-10-23 10:40:56 -04:00
cdenig
2f44040e4f Merge pull request #1344 from PepperDash/feature/allow-config-tool-v2-structure
Feature/allow config tool v2 structure
2025-10-23 10:15:03 -04:00
Neil Dorin
10399a1be8 Update src/PepperDash.Core/Config/PortalConfigReader.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:54:08 -06:00
Neil Dorin
5409db193c Update src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 13:53:23 -06:00
Neil Dorin
f1ce54a524 Merge branch 'main' into feature/allow-config-tool-v2-structure 2025-10-22 13:48:22 -06:00
Andrew Welker
7c72a0d905 fix: send device interface list on client join
In order to avoid updating the MC Edge API to send device interfaces, one of the responses to the clientJoined message will now be a message with the type `/system/deviceInterfaces`. This has a corresponding handler in the core library to put the interfaces in the correct location.
2025-10-21 18:19:09 -05:00
Andrew Welker
5d5e78629e chore: update local build version to 2.18.2-local 2025-10-21 18:17:25 -05:00
Neil Dorin
dab5484d6e Merge pull request #1342 from PepperDash/wsdebug-persistence
Unique Client IDs
2025-10-15 15:16:46 -04:00
Andrew Welker
5c35a3be45 fix: catch exceptions in handlers directly
Previously, any exceptions that were occuring in a hander's action were being swalled due to being off on another thread. Now, those exceptions are caught and printed out.
2025-10-15 14:03:17 -05:00
Andrew Welker
6cb98e12fa fix: use correct collection for program stop 2025-10-15 14:02:06 -05:00
Andrew Welker
608601990b docs: fix Copilot comments 2025-10-15 12:54:14 -05:00
Andrew Welker
3e0f318f7f fix: update log methods to all be consistent 2025-10-15 12:36:42 -05:00
Andrew Welker
98d0cc8fdc docs: add missing XML comments for Mobile Control Project 2025-10-15 12:26:57 -05:00
Andrew Welker
c557c6cdd6 fix: mobileinfo & CWS info call report the correct data 2025-10-15 11:49:06 -05:00
Andrew Welker
8525134ae7 fix: direct server clients now have unique client IDs
Using the generated security token as an ID was presenting problems with duplicate connections using the same ID and trying to figure out where to send the messages. Now, the clients have a unique ID that's an increasing integer that restarts at 1 when the program restarts.
2025-10-15 09:50:12 -05:00
Andrew Welker
1197b15a33 Merge pull request #1340 from PepperDash/display-bridging
Display bridging, Docs, Appdebug
2025-10-10 10:21:35 -04:00
Andrew Welker
ea6a7568fc Merge remote-tracking branch 'origin/feature/add-IKeyed-to-interfaces' into display-bridging 2025-10-09 16:08:17 -05:00
Andrew Welker
3a2a059ce1 fix: appdebug now has option for setting all sink levels 2025-10-09 16:07:25 -05:00
Andrew Welker
f9d9df9d5c docs: XML comments for Devices.Common 2025-10-09 13:18:36 -05:00
Neil Dorin
c284c4275f feat: Enhance UDPServer initialization in Connect method
Updated the `Connect` method in `GenericUdpServer.cs` to include error handling for hostname parsing. The method now attempts to create a `UDPServer` instance with specified parameters and falls back to default initialization if an error occurs. This improves flexibility and robustness in server setup.
2025-10-09 11:31:38 -06:00
Neil Dorin
0418f8a7cc fix: Fix typos and enhance item selection handling
Corrected parameter name in GenericUdpServer constructor.
Added new action for item selection in ISelectableItemsMessenger
with error handling for missing or invalid keys.
Updated SetItems method to improve clarity and ensure proper
clearing and re-adding of item actions.
2025-10-09 09:40:46 -06:00
Andrew Welker
a5d409e93a fix: analog input check uese correct comparison
input selection coming from SIMPL is 1-based, not 0-based. Comparison to input port array length needs to be <= instead of <
2025-10-09 08:36:26 -05:00
Andrew Welker
59944c0e2f refactor: split up classes to separate files 2025-10-09 08:33:22 -05:00
Neil Dorin
9de94bd65f fix: Update v2 config detection criteria
Changed the logic for identifying v2 configuration files. The check now looks for the presence of a "versions" node instead of the absence of "system" or "template" nodes, reflecting an update in the configuration file structure.
2025-09-22 15:05:06 -06:00
Neil Dorin
ff46fb8f29 feat: Add versioning support to EssentialsConfig
Introduce `Versions` property in `EssentialsConfig` to hold version information.
Add `VersionData` class for Essentials and package versions, and `NugetVersion` class for individual package details.
Retain and document `SystemTemplateConfigs` class.
2025-09-22 14:55:58 -06:00
Neil Dorin
d9243def30 feat: Adds ability to read configs generated from v2 config tool that are pre-merged don't have system or template objects
Refactor config handling and improve documentation

- Updated `PortalConfigReader.cs` to use constants for configuration keys, enhancing maintainability and readability. Improved error logging with `Debug.LogError`.
- Modified `ConfigReader.cs` to handle v2 configuration format, streamlining the loading process and avoiding redundant parsing.
- Added XML documentation comments to properties in `EssentialsConfig.cs`, improving code documentation. Initialized `Rooms` property in the constructor.
- Enhanced `SystemTemplateConfigs` class with XML documentation for better clarity on its properties.
2025-09-22 14:22:57 -06:00
185 changed files with 9139 additions and 5923 deletions

View File

@@ -10,7 +10,8 @@ jobs:
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
secrets: inherit secrets: inherit
build-4Series: build-4Series:
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main # uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@reorder-steps
secrets: inherit secrets: inherit
needs: getVersion needs: getVersion
if: needs.getVersion.outputs.newVersion == 'true' if: needs.getVersion.outputs.newVersion == 'true'

1
.gitignore vendored
View File

@@ -396,3 +396,4 @@ _site/
api/ api/
*.DS_Store *.DS_Store
/._PepperDash.Essentials.4Series.sln /._PepperDash.Essentials.4Series.sln
dotnet

View File

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

View File

@@ -0,0 +1,43 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace PepperDash.Core
{
/// <summary>
/// Helper class for formatting communication text and byte data for debugging purposes.
/// </summary>
public class ComTextHelper
{
/// <summary>
/// Gets escaped text for a byte array
/// </summary>
/// <param name="bytes"></param>
/// <returns>string with all bytes escaped</returns>
public static string GetEscapedText(byte[] bytes)
{
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets escaped text for a string
/// </summary>
/// <param name="text"></param>
/// <returns>string with all bytes escaped</returns>
public static string GetEscapedText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets debug text for a string
/// </summary>
/// <param name="text"></param>
/// <returns>string with all non-printable characters escaped</returns>
public static string GetDebugText(string text)
{
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
}
}
}

View File

@@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
@@ -37,14 +36,14 @@ namespace PepperDash.Core
{ {
get get
{ {
return _DebugTimeoutInMs/60000; return _DebugTimeoutInMs / 60000;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the RxStreamDebuggingIsEnabled /// Gets or sets the RxStreamDebuggingIsEnabled
/// </summary> /// </summary>
public bool RxStreamDebuggingIsEnabled{ get; private set; } public bool RxStreamDebuggingIsEnabled { get; private set; }
/// <summary> /// <summary>
/// Indicates that transmit stream debugging is enabled /// Indicates that transmit stream debugging is enabled
@@ -108,7 +107,7 @@ namespace PepperDash.Core
TxStreamDebuggingIsEnabled = true; TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting); Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
} }
/// <summary> /// <summary>
@@ -136,51 +135,4 @@ namespace PepperDash.Core
DebugExpiryPeriod = null; DebugExpiryPeriod = null;
} }
} }
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
/// <summary>
/// Enumeration of eStreamDebuggingSetting values
/// </summary>
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}
/// <summary>
/// The available settings for stream debugging response types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text,
}
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Crestron.SimplSharp; using Crestron.SimplSharp;
@@ -11,11 +12,12 @@ using Renci.SshNet.Common;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary> /// <summary>
/// /// SSH Client
/// </summary> /// </summary>
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
{ {
private const string SPlusKey = "Uninitialized SshClient"; private const string SPlusKey = "Uninitialized SshClient";
/// <summary> /// <summary>
/// Object to enable stream debugging /// Object to enable stream debugging
/// </summary> /// </summary>
@@ -36,11 +38,6 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary>
/////
///// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
/// <summary> /// <summary>
/// Gets or sets the Hostname /// Gets or sets the Hostname
/// </summary> /// </summary>
@@ -67,7 +64,7 @@ namespace PepperDash.Core
public bool IsConnected public bool IsConnected
{ {
// returns false if no client or not connected // returns false if no client or not connected
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
/// <summary> /// <summary>
@@ -83,16 +80,26 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public SocketStatus ClientStatus public SocketStatus ClientStatus
{ {
get { return _ClientStatus; } get { lock (_stateLock) { return _ClientStatus; } }
private set private set
{ {
if (_ClientStatus == value) bool shouldFireEvent = false;
return; lock (_stateLock)
_ClientStatus = value; {
OnConnectionChange(); if (_ClientStatus != value)
{
_ClientStatus = value;
shouldFireEvent = true;
}
}
// Fire event outside lock to avoid deadlock
if (shouldFireEvent)
OnConnectionChange();
} }
} }
SocketStatus _ClientStatus;
private SocketStatus _ClientStatus;
private bool _ConnectEnabled;
/// <summary> /// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
@@ -100,7 +107,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public ushort UStatus public ushort UStatus
{ {
get { return (ushort)_ClientStatus; } get { lock (_stateLock) { return (ushort)_ClientStatus; } }
} }
/// <summary> /// <summary>
@@ -111,7 +118,11 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Will be set and unset by connect and disconnect only /// Will be set and unset by connect and disconnect only
/// </summary> /// </summary>
public bool ConnectEnabled { get; private set; } public bool ConnectEnabled
{
get { lock (_stateLock) { return _ConnectEnabled; } }
private set { lock (_stateLock) { _ConnectEnabled = value; } }
}
/// <summary> /// <summary>
/// S+ helper for AutoReconnect /// S+ helper for AutoReconnect
@@ -127,17 +138,25 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
SshClient Client; private SshClient client;
ShellStream TheStream; private ShellStream shellStream;
CTimer ReconnectTimer; private readonly Timer reconnectTimer;
//Lock object to prevent simulatneous connect/disconnect operations //Lock object to prevent simulatneous connect/disconnect operations
//private CCriticalSection connectLock = new CCriticalSection(); //private CCriticalSection connectLock = new CCriticalSection();
private SemaphoreSlim connectLock = new SemaphoreSlim(1); private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1);
private bool DisconnectLogged = false; // Thread-safety lock for state changes
private readonly object _stateLock = new object();
private bool disconnectLogged = false;
/// <summary>
/// When true, turns off echo for the SSH session
/// </summary>
public bool DisableEcho { get; set; }
/// <summary> /// <summary>
/// Typical constructor. /// Typical constructor.
@@ -154,13 +173,13 @@ namespace PepperDash.Core
Password = password; Password = password;
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o => reconnectTimer = new Timer(o =>
{ {
if (ConnectEnabled) if (ConnectEnabled) // Now thread-safe property access
{ {
Connect(); Connect();
} }
}, System.Threading.Timeout.Infinite); }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
} }
/// <summary> /// <summary>
@@ -172,23 +191,23 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o => reconnectTimer = new Timer(o =>
{ {
if (ConnectEnabled) if (ConnectEnabled) // Now thread-safe property access
{ {
Connect(); Connect();
} }
}, System.Threading.Timeout.Infinite); }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
} }
/// <summary> /// <summary>
/// Handles closing this up when the program shuts down /// Handles closing this up when the program shuts down
/// </summary> /// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
if (programEventType == eProgramStatusEventType.Stopping) if (programEventType == eProgramStatusEventType.Stopping)
{ {
if (Client != null) if (client != null)
{ {
this.LogDebug("Program stopping. Closing connection"); this.LogDebug("Program stopping. Closing connection");
Disconnect(); Disconnect();
@@ -223,10 +242,10 @@ namespace PepperDash.Core
this.LogDebug("Attempting connect"); this.LogDebug("Attempting connect");
// Cancel reconnect if running. // Cancel reconnect if running.
ReconnectTimer?.Stop(); StopReconnectTimer();
// Cleanup the old client if it already exists // Cleanup the old client if it already exists
if (Client != null) if (client != null)
{ {
this.LogDebug("Cleaning up disconnected client"); this.LogDebug("Cleaning up disconnected client");
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
@@ -239,29 +258,36 @@ namespace PepperDash.Core
this.LogDebug("Creating new SshClient"); this.LogDebug("Creating new SshClient");
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
Client = new SshClient(connectionInfo); client = new SshClient(connectionInfo);
Client.ErrorOccurred += Client_ErrorOccurred; client.ErrorOccurred += Client_ErrorOccurred;
//Attempt to connect //Attempt to connect
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
try try
{ {
Client.Connect(); client.Connect();
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
if (TheStream.DataAvailable) var modes = new Dictionary<TerminalModes, uint>();
if (DisableEcho)
{
modes.Add(TerminalModes.ECHO, 0);
}
shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes);
if (shellStream.DataAvailable)
{ {
// empty the buffer if there is data // empty the buffer if there is data
string str = TheStream.Read(); shellStream.Read();
} }
TheStream.DataReceived += Stream_DataReceived; shellStream.DataReceived += Stream_DataReceived;
this.LogInformation("Connected"); this.LogInformation("Connected");
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
DisconnectLogged = false; disconnectLogged = false;
} }
catch (SshConnectionException e) catch (SshConnectionException e)
{ {
var ie = e.InnerException; // The details are inside!! var ie = e.InnerException; // The details are inside!!
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException) if (ie is SocketException)
{ {
@@ -286,37 +312,36 @@ namespace PepperDash.Core
this.LogVerbose(ie, "Exception details: "); this.LogVerbose(ie, "Exception details: ");
} }
DisconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs); StartReconnectTimer();
} }
} }
catch (SshOperationTimeoutException ex) catch (SshOperationTimeoutException ex)
{ {
this.LogWarning("Connection attempt timed out: {message}", ex.Message); this.LogWarning("Connection attempt timed out: {message}", ex.Message);
DisconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs); StartReconnectTimer();
} }
} }
catch (Exception e) catch (Exception e)
{ {
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogError("Unhandled exception on connect: {error}", e.Message); this.LogError("Unhandled exception on connect: {error}", e.Message);
this.LogVerbose(e, "Exception details: "); this.LogVerbose(e, "Exception details: ");
DisconnectLogged = true; disconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect) if (AutoReconnect)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs); StartReconnectTimer();
} }
} }
} }
@@ -334,11 +359,7 @@ namespace PepperDash.Core
{ {
ConnectEnabled = false; ConnectEnabled = false;
// Stop trying reconnects, if we are // Stop trying reconnects, if we are
if (ReconnectTimer != null) StopReconnectTimer();
{
ReconnectTimer.Stop();
// ReconnectTimer = null;
}
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
} }
@@ -352,12 +373,12 @@ namespace PepperDash.Core
try try
{ {
if (Client != null) if (client != null)
{ {
Client.ErrorOccurred -= Client_ErrorOccurred; client.ErrorOccurred -= Client_ErrorOccurred;
Client.Disconnect(); client.Disconnect();
Client.Dispose(); client.Dispose();
Client = null; client = null;
ClientStatus = status; ClientStatus = status;
this.LogDebug("Disconnected"); this.LogDebug("Disconnected");
} }
@@ -371,16 +392,16 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Kills the stream /// Kills the stream
/// </summary> /// </summary>
void KillStream() private void KillStream()
{ {
try try
{ {
if (TheStream != null) if (shellStream != null)
{ {
TheStream.DataReceived -= Stream_DataReceived; shellStream.DataReceived -= Stream_DataReceived;
TheStream.Close(); shellStream.Close();
TheStream.Dispose(); shellStream.Dispose();
TheStream = null; shellStream = null;
this.LogDebug("Disconnected stream"); this.LogDebug("Disconnected stream");
} }
} }
@@ -393,7 +414,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Handles the keyboard interactive authentication, should it be required. /// Handles the keyboard interactive authentication, should it be required.
/// </summary> /// </summary>
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
{ {
foreach (AuthenticationPrompt prompt in e.Prompts) foreach (AuthenticationPrompt prompt in e.Prompts)
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
@@ -403,7 +424,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing. /// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
/// </summary> /// </summary>
void Stream_DataReceived(object sender, ShellDataEventArgs e) private void Stream_DataReceived(object sender, ShellDataEventArgs e)
{ {
if (((ShellStream)sender).Length <= 0L) if (((ShellStream)sender).Length <= 0L)
{ {
@@ -416,18 +437,14 @@ namespace PepperDash.Core
if (bytesHandler != null) if (bytesHandler != null)
{ {
var bytes = Encoding.UTF8.GetBytes(response); var bytes = Encoding.UTF8.GetBytes(response);
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedBytes(bytes);
{
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedText(response);
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
textHandler(this, new GenericCommMethodReceiveTextArgs(response)); textHandler(this, new GenericCommMethodReceiveTextArgs(response));
} }
@@ -439,7 +456,7 @@ namespace PepperDash.Core
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
/// event /// event
/// </summary> /// </summary>
void Client_ErrorOccurred(object sender, ExceptionEventArgs e) private void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
{ {
CrestronInvoke.BeginInvoke(o => CrestronInvoke.BeginInvoke(o =>
{ {
@@ -459,7 +476,7 @@ namespace PepperDash.Core
if (AutoReconnect && ConnectEnabled) if (AutoReconnect && ConnectEnabled)
{ {
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs); StartReconnectTimer();
} }
}); });
} }
@@ -467,7 +484,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Helper for ConnectionChange event /// Helper for ConnectionChange event
/// </summary> /// </summary>
void OnConnectionChange() private void OnConnectionChange()
{ {
ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this));
} }
@@ -482,16 +499,12 @@ namespace PepperDash.Core
{ {
try try
{ {
if (Client != null && TheStream != null && IsConnected) if (client != null && shellStream != null && IsConnected)
{ {
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentText(text);
this.LogInformation(
"Sending {length} characters of text: '{text}'",
text.Length,
ComTextHelper.GetDebugText(text));
TheStream.Write(text); shellStream.Write(text);
TheStream.Flush(); shellStream.Flush();
} }
else else
{ {
@@ -503,7 +516,7 @@ namespace PepperDash.Core
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset(); StartReconnectTimer();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -519,13 +532,12 @@ namespace PepperDash.Core
{ {
try try
{ {
if (Client != null && TheStream != null && IsConnected) if (client != null && shellStream != null && IsConnected)
{ {
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentBytes(bytes);
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
TheStream.Write(bytes, 0, bytes.Length); shellStream.Write(bytes, 0, bytes.Length);
TheStream.Flush(); shellStream.Flush();
} }
else else
{ {
@@ -537,7 +549,7 @@ namespace PepperDash.Core
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset(); StartReconnectTimer();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -546,6 +558,83 @@ namespace PepperDash.Core
} }
#endregion #endregion
/// <summary>
/// Safely starts the reconnect timer with exception handling
/// </summary>
private void StartReconnectTimer()
{
try
{
reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// Timer was disposed, ignore
this.LogDebug("Attempted to start timer but it was already disposed");
}
}
/// <summary>
/// Safely stops the reconnect timer with exception handling
/// </summary>
private void StopReconnectTimer()
{
try
{
reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// Timer was disposed, ignore
this.LogDebug("Attempted to stop timer but it was already disposed");
}
}
/// <summary>
/// Deactivate method - properly dispose of resources
/// </summary>
public override bool Deactivate()
{
try
{
this.LogDebug("Deactivating SSH client - disposing resources");
// Stop trying reconnects
ConnectEnabled = false;
StopReconnectTimer();
// Disconnect and cleanup client
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
// Dispose timer
try
{
reconnectTimer?.Dispose();
}
catch (ObjectDisposedException)
{
// Already disposed, ignore
}
// Dispose semaphore
try
{
connectLock?.Dispose();
}
catch (ObjectDisposedException)
{
// Already disposed, ignore
}
return base.Deactivate();
}
catch (Exception ex)
{
this.LogException(ex, "Error during SSH client deactivation");
return false;
}
}
} }
//***************************************************************************************************** //*****************************************************************************************************

View File

@@ -19,44 +19,44 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary> /// <summary>
/// Fires when data is received from the server and returns it as a Byte array /// Fires when data is received from the server and returns it as a Byte array
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary> /// <summary>
/// Fires when data is received from the server and returns it as text /// Fires when data is received from the server and returns it as text
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange; //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
private string _hostname; private string _hostname;
/// <summary> /// <summary>
/// Address of server /// Address of server
/// </summary> /// </summary>
public string Hostname public string Hostname
{ {
get get
{ {
return _hostname; return _hostname;
} }
set set
{ {
_hostname = value; _hostname = value;
if (_client != null) if (_client != null)
{ {
_client.AddressClientConnectedTo = _hostname; _client.AddressClientConnectedTo = _hostname;
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the Port /// Gets or sets the Port
@@ -78,19 +78,19 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary> /// <summary>
/// The actual client class /// The actual client class
/// </summary> /// </summary>
private TCPClient _client; private TCPClient _client;
/// <summary> /// <summary>
/// Bool showing if socket is connected /// Bool showing if socket is connected
/// </summary> /// </summary>
public bool IsConnected public bool IsConnected
{ {
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
/// <summary> /// <summary>
/// S+ helper for IsConnected /// S+ helper for IsConnected
/// </summary> /// </summary>
@@ -99,15 +99,15 @@ namespace PepperDash.Core
get { return (ushort)(IsConnected ? 1 : 0); } get { return (ushort)(IsConnected ? 1 : 0); }
} }
/// <summary> /// <summary>
/// _client socket status Read only /// _client socket status Read only
/// </summary> /// </summary>
public SocketStatus ClientStatus public SocketStatus ClientStatus
{ {
get get
{ {
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
} }
} }
/// <summary> /// <summary>
@@ -119,26 +119,26 @@ namespace PepperDash.Core
get { return (ushort)ClientStatus; } get { return (ushort)ClientStatus; }
} }
/// <summary> /// <summary>
/// Status text shows the message associated with socket status /// Status text shows the message associated with socket status
/// </summary> /// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } } public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// Ushort representation of client status /// Ushort representation of client status
/// </summary> /// </summary>
[Obsolete] [Obsolete]
public ushort UClientStatus { get { return (ushort)ClientStatus; } } public ushort UClientStatus { get { return (ushort)ClientStatus; } }
/// <summary> /// <summary>
/// Connection failure reason /// Connection failure reason
/// </summary> /// </summary>
public string ConnectionFailure { get { return ClientStatus.ToString(); } } public string ConnectionFailure { get { return ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnect /// Gets or sets the AutoReconnect
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// S+ helper for AutoReconnect /// S+ helper for AutoReconnect
@@ -149,29 +149,29 @@ namespace PepperDash.Core
set { AutoReconnect = value == 1; } set { AutoReconnect = value == 1; }
} }
/// <summary> /// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000 /// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
/// <summary> /// <summary>
/// Set only when the disconnect method is called /// Set only when the disconnect method is called
/// </summary> /// </summary>
bool DisconnectCalledByUser; bool DisconnectCalledByUser;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool Connected public bool Connected
{ {
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
//Lock object to prevent simulatneous connect/disconnect operations //Lock object to prevent simulatneous connect/disconnect operations
private CCriticalSection connectLock = new CCriticalSection(); private CCriticalSection connectLock = new CCriticalSection();
// private Timer for auto reconnect // private Timer for auto reconnect
private CTimer RetryTimer; private CTimer RetryTimer;
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -181,8 +181,8 @@ namespace PepperDash.Core
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
public GenericTcpIpClient(string key, string address, int port, int bufferSize) public GenericTcpIpClient(string key, string address, int port, int bufferSize)
: base(key) : base(key)
{ {
StreamDebugging = new CommunicationStreamDebugging(key); StreamDebugging = new CommunicationStreamDebugging(key);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
@@ -218,18 +218,18 @@ namespace PepperDash.Core
/// Default constructor for S+ /// Default constructor for S+
/// </summary> /// </summary>
public GenericTcpIpClient() public GenericTcpIpClient()
: base(SplusKey) : base(SplusKey)
{ {
StreamDebugging = new CommunicationStreamDebugging(SplusKey); StreamDebugging = new CommunicationStreamDebugging(SplusKey);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
BufferSize = 2000; BufferSize = 2000;
RetryTimer = new CTimer(o => RetryTimer = new CTimer(o =>
{ {
Reconnect(); Reconnect();
}, Timeout.Infinite); }, Timeout.Infinite);
} }
/// <summary> /// <summary>
/// Initialize method /// Initialize method
@@ -255,26 +255,26 @@ namespace PepperDash.Core
/// ///
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
/// <summary> /// <summary>
/// Deactivate method /// Deactivate method
/// </summary> /// </summary>
public override bool Deactivate() public override bool Deactivate()
{ {
RetryTimer.Stop(); RetryTimer.Stop();
RetryTimer.Dispose(); RetryTimer.Dispose();
if (_client != null) if (_client != null)
{ {
_client.SocketStatusChange -= this.Client_SocketStatusChange; _client.SocketStatusChange -= this.Client_SocketStatusChange;
DisconnectClient(); DisconnectClient();
} }
return true; return true;
} }
/// <summary> /// <summary>
/// Connect method /// Connect method
/// </summary> /// </summary>
public void Connect() public void Connect()
{ {
if (string.IsNullOrEmpty(Hostname)) if (string.IsNullOrEmpty(Hostname))
{ {
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
@@ -310,7 +310,7 @@ namespace PepperDash.Core
{ {
connectLock.Leave(); connectLock.Leave();
} }
} }
private void Reconnect() private void Reconnect()
{ {
@@ -337,11 +337,11 @@ namespace PepperDash.Core
} }
} }
/// <summary> /// <summary>
/// Disconnect method /// Disconnect method
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
try try
{ {
connectLock.Enter(); connectLock.Enter();
@@ -355,7 +355,7 @@ namespace PepperDash.Core
{ {
connectLock.Leave(); connectLock.Leave();
} }
} }
/// <summary> /// <summary>
/// DisconnectClient method /// DisconnectClient method
@@ -375,7 +375,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
/// <param name="c"></param> /// <param name="c"></param>
void ConnectToServerCallback(TCPClient c) void ConnectToServerCallback(TCPClient c)
{ {
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{ {
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
@@ -385,13 +385,13 @@ namespace PepperDash.Core
{ {
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
} }
} }
/// <summary> /// <summary>
/// Disconnects, waits and attemtps to connect again /// Disconnects, waits and attemtps to connect again
/// </summary> /// </summary>
void WaitAndTryReconnect() void WaitAndTryReconnect()
{ {
CrestronInvoke.BeginInvoke(o => CrestronInvoke.BeginInvoke(o =>
{ {
try try
@@ -409,7 +409,7 @@ namespace PepperDash.Core
connectLock.Leave(); connectLock.Leave();
} }
}); });
} }
/// <summary> /// <summary>
/// Recieves incoming data /// Recieves incoming data
@@ -417,7 +417,7 @@ namespace PepperDash.Core
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="numBytes"></param> /// <param name="numBytes"></param>
void Receive(TCPClient client, int numBytes) void Receive(TCPClient client, int numBytes)
{ {
if (client != null) if (client != null)
{ {
if (numBytes > 0) if (numBytes > 0)
@@ -426,10 +426,7 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedBytes(bytes);
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
@@ -437,58 +434,53 @@ namespace PepperDash.Core
{ {
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedText(str);
{
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
}
textHandler(this, new GenericCommMethodReceiveTextArgs(str)); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
} }
} }
client.ReceiveDataAsync(Receive); client.ReceiveDataAsync(Receive);
} }
} }
/// <summary> /// <summary>
/// SendText method /// SendText method
/// </summary> /// </summary>
public void SendText(string text) public void SendText(string text)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(text); var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array // Check debug level before processing byte array
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentText(text);
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
if (_client != null) if (_client != null)
_client.SendData(bytes, bytes.Length); _client.SendData(bytes, bytes.Length);
} }
/// <summary> /// <summary>
/// SendEscapedText method /// SendEscapedText method
/// </summary> /// </summary>
public void SendEscapedText(string text) public void SendEscapedText(string text)
{ {
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
{ {
var hex = s.Groups[1].Value; var hex = s.Groups[1].Value;
return ((char)Convert.ToByte(hex, 16)).ToString(); return ((char)Convert.ToByte(hex, 16)).ToString();
}); });
SendText(unescapedText); SendText(unescapedText);
} }
/// <summary> /// <summary>
/// Sends Bytes to the server /// Sends Bytes to the server
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
/// <summary> /// <summary>
/// SendBytes method /// SendBytes method
/// </summary> /// </summary>
public void SendBytes(byte[] bytes) public void SendBytes(byte[] bytes)
{ {
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentBytes(bytes);
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (_client != null) if (_client != null)
_client.SendData(bytes, bytes.Length); _client.SendData(bytes, bytes.Length);
} }
/// <summary> /// <summary>
/// Socket Status Change Handler /// Socket Status Change Handler
@@ -496,7 +488,7 @@ namespace PepperDash.Core
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="clientSocketStatus"></param> /// <param name="clientSocketStatus"></param>
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{ {
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{ {
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
@@ -505,68 +497,73 @@ namespace PepperDash.Core
else else
{ {
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
_client.ReceiveDataAsync(Receive); _client.ReceiveDataAsync(Receive);
} }
var handler = ConnectionChange; var handler = ConnectionChange;
if (handler != null) if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
} }
} }
/// <summary> /// <summary>
/// Represents a TcpSshPropertiesConfig /// Represents a TcpSshPropertiesConfig
/// </summary> /// </summary>
public class TcpSshPropertiesConfig public class TcpSshPropertiesConfig
{ {
/// <summary> /// <summary>
/// Address to connect to /// Address to connect to
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public string Address { get; set; } public string Address { get; set; }
/// <summary> /// <summary>
/// Port to connect to /// Port to connect to
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public int Port { get; set; } public int Port { get; set; }
/// <summary> /// <summary>
/// Username credential /// Username credential
/// </summary> /// </summary>
public string Username { get; set; } public string Username { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Password /// Gets or sets the Password
/// </summary> /// </summary>
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// Defaults to 32768 /// Defaults to 32768
/// </summary> /// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnect /// Gets or sets the AutoReconnect
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// Gets or sets the AutoReconnectIntervalMs /// Gets or sets the AutoReconnectIntervalMs
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// When true, turns off echo for the SSH session
/// </summary>
[JsonProperty("disableSshEcho")]
public bool DisableSshEcho { get; set; }
/// <summary> /// <summary>
/// Default constructor /// Default constructor
/// </summary> /// </summary>
public TcpSshPropertiesConfig() public TcpSshPropertiesConfig()
{ {
BufferSize = 32768; BufferSize = 32768;
AutoReconnect = true; AutoReconnect = true;
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
Username = ""; Username = "";
Password = ""; Password = "";
} DisableSshEcho = false;
}
} }
} }

View File

@@ -124,21 +124,21 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="address"></param> /// <param name="address"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="buffefSize"></param> /// <param name="bufferSize"></param>
public GenericUdpServer(string key, string address, int port, int buffefSize) public GenericUdpServer(string key, string address, int port, int bufferSize)
: base(key) : base(key)
{ {
StreamDebugging = new CommunicationStreamDebugging(key); StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address; Hostname = address;
Port = port; Port = port;
BufferSize = buffefSize; BufferSize = bufferSize;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
@@ -180,7 +180,7 @@ namespace PepperDash.Core
/// <param name="programEventType"></param> /// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
if (programEventType != eProgramStatusEventType.Stopping) if (programEventType != eProgramStatusEventType.Stopping)
return; return;
Debug.Console(1, this, "Program stopping. Disabling Server"); Debug.Console(1, this, "Program stopping. Disabling Server");
@@ -194,7 +194,21 @@ namespace PepperDash.Core
{ {
if (Server == null) if (Server == null)
{ {
Server = new UDPServer(); try
{
var address = IPAddress.Parse(Hostname);
Server = new UDPServer(address, Port, BufferSize);
}
catch (Exception ex)
{
this.LogError("Error parsing IP Address '{ipAddress}': message: {message}", Hostname, ex.Message);
this.LogInformation("Creating UDPServer with default buffersize");
Server = new UDPServer();
}
} }
if (string.IsNullOrEmpty(Hostname)) if (string.IsNullOrEmpty(Hostname))
@@ -229,7 +243,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
if(Server != null) if (Server != null)
Server.DisableUDPServer(); Server.DisableUDPServer();
IsConnected = false; IsConnected = false;
@@ -251,7 +265,7 @@ namespace PepperDash.Core
try try
{ {
if (numBytes <= 0) if (numBytes <= 0)
return; return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom; var sourceIp = Server.IPAddressLastMessageReceivedFrom;
@@ -267,17 +281,13 @@ namespace PepperDash.Core
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedBytes(bytes);
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedText(str);
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
textHandler(this, new GenericCommMethodReceiveTextArgs(str)); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
} }
} }
@@ -304,8 +314,7 @@ namespace PepperDash.Core
if (IsConnected && Server != null) if (IsConnected && Server != null)
{ {
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentText(text);
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
Server.SendData(bytes, bytes.Length); Server.SendData(bytes, bytes.Length);
} }
@@ -320,8 +329,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void SendBytes(byte[] bytes) public void SendBytes(byte[] bytes)
{ {
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentBytes(bytes);
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (IsConnected && Server != null) if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length); Server.SendData(bytes, bytes.Length);
@@ -329,11 +337,11 @@ namespace PepperDash.Core
} }
/// <summary> /// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs /// Represents a GenericUdpReceiveTextExtraArgs
/// </summary> /// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs public class GenericUdpReceiveTextExtraArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -345,7 +353,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int Port { get; private set; } public int Port { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -359,18 +367,18 @@ namespace PepperDash.Core
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="bytes"></param> /// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes) public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{ {
Text = text; Text = text;
IpAddress = ipAddress; IpAddress = ipAddress;
Port = port; Port = port;
Bytes = bytes; Bytes = bytes;
} }
/// <summary> /// <summary>
/// Stupid S+ Constructor /// Stupid S+ Constructor
/// </summary> /// </summary>
public GenericUdpReceiveTextExtraArgs() { } public GenericUdpReceiveTextExtraArgs() { }
} }
/// <summary> /// <summary>
/// ///

View File

@@ -0,0 +1,69 @@
using System;
using Crestron.SimplSharp;
namespace PepperDash.Core
{
/// <summary>
/// Extension methods for stream debugging
/// </summary>
public static class StreamDebuggingExtensions
{
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
/// <summary>
/// Print the sent bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the received bytes to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="bytes">bytes to print</param>
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
}
/// <summary>
/// Print the sent text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintSentText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
}
/// <summary>
/// Print the received text to the console
/// </summary>
/// <param name="comms">comms device</param>
/// <param name="text">text to print</param>
public static void PrintReceivedText(this IStreamDebugging comms, string text)
{
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
}
}
}

View File

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

View File

@@ -0,0 +1,24 @@
using System;
namespace PepperDash.Core
{
/// <summary>
/// The available settings for stream debugging data format types
/// </summary>
[Flags]
public enum eStreamDebuggingDataTypeSettings
{
/// <summary>
/// Debug data in byte format
/// </summary>
Bytes = 0,
/// <summary>
/// Debug data in text format
/// </summary>
Text = 1,
/// <summary>
/// Debug data in both byte and text formats
/// </summary>
Both = Bytes | Text
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace PepperDash.Core
{
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}
}

View File

@@ -1,10 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
using System.Text.RegularExpressions;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace PepperDash.Core namespace PepperDash.Core
@@ -42,7 +39,7 @@ namespace PepperDash.Core
/// Defines the contract for IBasicCommunication /// Defines the contract for IBasicCommunication
/// </summary> /// </summary>
public interface IBasicCommunication : ICommunicationReceiver public interface IBasicCommunication : ICommunicationReceiver
{ {
/// <summary> /// <summary>
/// Send text to the device /// Send text to the device
/// </summary> /// </summary>
@@ -54,7 +51,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
void SendBytes(byte[] bytes); void SendBytes(byte[] bytes);
} }
/// <summary> /// <summary>
/// Represents a device that implements IBasicCommunication and IStreamDebugging /// Represents a device that implements IBasicCommunication and IStreamDebugging
@@ -67,7 +64,7 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Represents a device with stream debugging capablities /// Represents a device with stream debugging capablities
/// </summary> /// </summary>
public interface IStreamDebugging public interface IStreamDebugging : IKeyed
{ {
/// <summary> /// <summary>
/// Object to enable stream debugging /// Object to enable stream debugging
@@ -76,12 +73,12 @@ namespace PepperDash.Core
CommunicationStreamDebugging StreamDebugging { get; } CommunicationStreamDebugging StreamDebugging { get; }
} }
/// <summary> /// <summary>
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient, /// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
/// GenericTcpIpClient /// GenericTcpIpClient
/// </summary> /// </summary>
public interface ISocketStatus : IBasicCommunication public interface ISocketStatus : IBasicCommunication
{ {
/// <summary> /// <summary>
/// Notifies of socket status changes /// Notifies of socket status changes
/// </summary> /// </summary>
@@ -93,7 +90,7 @@ namespace PepperDash.Core
[JsonProperty("clientStatus")] [JsonProperty("clientStatus")]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
SocketStatus ClientStatus { get; } SocketStatus ClientStatus { get; }
} }
/// <summary> /// <summary>
/// Describes a device that implements ISocketStatus and IStreamDebugging /// Describes a device that implements ISocketStatus and IStreamDebugging
@@ -107,24 +104,24 @@ namespace PepperDash.Core
/// Describes a device that can automatically attempt to reconnect /// Describes a device that can automatically attempt to reconnect
/// </summary> /// </summary>
public interface IAutoReconnect public interface IAutoReconnect
{ {
/// <summary> /// <summary>
/// Enable automatic recconnect /// Enable automatic recconnect
/// </summary> /// </summary>
[JsonProperty("autoReconnect")] [JsonProperty("autoReconnect")]
bool AutoReconnect { get; set; } bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// Interval in ms to attempt automatic recconnections /// Interval in ms to attempt automatic recconnections
/// </summary> /// </summary>
[JsonProperty("autoReconnectIntervalMs")] [JsonProperty("autoReconnectIntervalMs")]
int AutoReconnectIntervalMs { get; set; } int AutoReconnectIntervalMs { get; set; }
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public enum eGenericCommMethodStatusChangeType public enum eGenericCommMethodStatusChangeType
{ {
/// <summary> /// <summary>
/// Connected /// Connected
/// </summary> /// </summary>
@@ -133,45 +130,45 @@ namespace PepperDash.Core
/// Disconnected /// Disconnected
/// </summary> /// </summary>
Disconnected Disconnected
} }
/// <summary> /// <summary>
/// This delegate defines handler for IBasicCommunication status changes /// This delegate defines handler for IBasicCommunication status changes
/// </summary> /// </summary>
/// <param name="comm">Device firing the status change</param> /// <param name="comm">Device firing the status change</param>
/// <param name="status"></param> /// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class GenericCommMethodReceiveBytesArgs : EventArgs public class GenericCommMethodReceiveBytesArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Gets or sets the Bytes /// Gets or sets the Bytes
/// </summary> /// </summary>
public byte[] Bytes { get; private set; } public byte[] Bytes { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
public GenericCommMethodReceiveBytesArgs(byte[] bytes) public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{ {
Bytes = bytes; Bytes = bytes;
} }
/// <summary> /// <summary>
/// S+ Constructor /// S+ Constructor
/// </summary> /// </summary>
public GenericCommMethodReceiveBytesArgs() { } public GenericCommMethodReceiveBytesArgs() { }
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class GenericCommMethodReceiveTextArgs : EventArgs public class GenericCommMethodReceiveTextArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@@ -185,9 +182,9 @@ namespace PepperDash.Core
/// </summary> /// </summary>
/// <param name="text"></param> /// <param name="text"></param>
public GenericCommMethodReceiveTextArgs(string text) public GenericCommMethodReceiveTextArgs(string text)
{ {
Text = text; Text = text;
} }
/// <summary> /// <summary>
/// ///
@@ -195,59 +192,14 @@ namespace PepperDash.Core
/// <param name="text"></param> /// <param name="text"></param>
/// <param name="delimiter"></param> /// <param name="delimiter"></param>
public GenericCommMethodReceiveTextArgs(string text, string delimiter) public GenericCommMethodReceiveTextArgs(string text, string delimiter)
:this(text) : this(text)
{ {
Delimiter = delimiter; Delimiter = delimiter;
} }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveTextArgs() { }
}
/// <summary>
///
/// </summary>
public class ComTextHelper
{
/// <summary> /// <summary>
/// Gets escaped text for a byte array /// S+ Constructor
/// </summary> /// </summary>
/// <param name="bytes"></param> public GenericCommMethodReceiveTextArgs() { }
/// <returns></returns> }
public static string GetEscapedText(byte[] bytes)
{
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets escaped text for a string
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
/// <summary>
/// GetEscapedText method
/// </summary>
public static string GetEscapedText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
}
/// <summary>
/// Gets debug text for a string
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
/// <summary>
/// GetDebugText method
/// </summary>
public static string GetDebugText(string text)
{
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
}
}
} }

View File

@@ -9,40 +9,59 @@ using Serilog.Events;
namespace PepperDash.Core.Config namespace PepperDash.Core.Config
{ {
/// <summary> /// <summary>
/// Reads a Portal formatted config file /// Reads a Portal formatted config file
/// </summary> /// </summary>
public class PortalConfigReader public class PortalConfigReader
{ {
/// <summary> const string template = "template";
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object. const string system = "system";
/// </summary> const string systemUrl = "system_url";
/// <returns>JObject of config file</returns> const string templateUrl = "template_url";
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath) const string info = "info";
const string devices = "devices";
const string rooms = "rooms";
const string sourceLists = "sourceLists";
const string destinationLists = "destinationLists";
const string cameraLists = "cameraLists";
const string audioControlPointLists = "audioControlPointLists";
const string tieLines = "tieLines";
const string joinMaps = "joinMaps";
const string global = "global";
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
{ {
try try
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
Debug.Console(1, Debug.ErrorLogLevel.Error, Debug.LogError(
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath); "ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
} }
using (StreamReader fs = new StreamReader(filePath)) using (StreamReader fs = new StreamReader(filePath))
{ {
var jsonObj = JObject.Parse(fs.ReadToEnd()); var jsonObj = JObject.Parse(fs.ReadToEnd());
if(jsonObj["template"] != null && jsonObj["system"] != null) if(jsonObj[template] != null && jsonObj[system] != null)
{ {
// it's a double-config, merge it. // it's a double-config, merge it.
var merged = MergeConfigs(jsonObj); var merged = MergeConfigs(jsonObj);
if (jsonObj["system_url"] != null) if (jsonObj[systemUrl] != null)
{ {
merged["systemUrl"] = jsonObj["system_url"].Value<string>(); merged[systemUrl] = jsonObj[systemUrl].Value<string>();
} }
if (jsonObj["template_url"] != null) if (jsonObj[templateUrl] != null)
{ {
merged["templateUrl"] = jsonObj["template_url"].Value<string>(); merged[templateUrl] = jsonObj[templateUrl].Value<string>();
} }
jsonObj = merged; jsonObj = merged;
@@ -77,62 +96,62 @@ namespace PepperDash.Core.Config
var merged = new JObject(); var merged = new JObject();
// Put together top-level objects // Put together top-level objects
if (system["info"] != null) if (system[info] != null)
merged.Add("info", Merge(template["info"], system["info"], "infO")); merged.Add(info, Merge(template[info], system[info], info));
else else
merged.Add("info", template["info"]); merged.Add(info, template[info]);
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray, merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray,
system["devices"] as JArray, "key", "devices")); system[devices] as JArray, "key", devices));
if (system["rooms"] == null) if (system[rooms] == null)
merged.Add("rooms", template["rooms"]); merged.Add(rooms, template[rooms]);
else else
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray, merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray,
system["rooms"] as JArray, "key", "rooms")); system[rooms] as JArray, "key", rooms));
if (system["sourceLists"] == null) if (system[sourceLists] == null)
merged.Add("sourceLists", template["sourceLists"]); merged.Add(sourceLists, template[sourceLists]);
else else
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists")); merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists));
if (system["destinationLists"] == null) if (system[destinationLists] == null)
merged.Add("destinationLists", template["destinationLists"]); merged.Add(destinationLists, template[destinationLists]);
else else
merged.Add("destinationLists", merged.Add(destinationLists,
Merge(template["destinationLists"], system["destinationLists"], "destinationLists")); Merge(template[destinationLists], system[destinationLists], destinationLists));
if (system["cameraLists"] == null) if (system[cameraLists] == null)
merged.Add("cameraLists", template["cameraLists"]); merged.Add(cameraLists, template[cameraLists]);
else else
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists")); merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists));
if (system["audioControlPointLists"] == null) if (system[audioControlPointLists] == null)
merged.Add("audioControlPointLists", template["audioControlPointLists"]); merged.Add(audioControlPointLists, template[audioControlPointLists]);
else else
merged.Add("audioControlPointLists", merged.Add(audioControlPointLists,
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists")); Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists));
// Template tie lines take precedence. Config tool doesn't do them at system // Template tie lines take precedence. Config tool doesn't do them at system
// level anyway... // level anyway...
if (template["tieLines"] != null) if (template[tieLines] != null)
merged.Add("tieLines", template["tieLines"]); merged.Add(tieLines, template[tieLines]);
else if (system["tieLines"] != null) else if (system[tieLines] != null)
merged.Add("tieLines", system["tieLines"]); merged.Add(tieLines, system[tieLines]);
else else
merged.Add("tieLines", new JArray()); merged.Add(tieLines, new JArray());
if (template["joinMaps"] != null) if (template[joinMaps] != null)
merged.Add("joinMaps", template["joinMaps"]); merged.Add(joinMaps, template[joinMaps]);
else else
merged.Add("joinMaps", new JObject()); merged.Add(joinMaps, new JObject());
if (system["global"] != null) if (system[global] != null)
merged.Add("global", Merge(template["global"], system["global"], "global")); merged.Add(global, Merge(template[global], system[global], global));
else else
merged.Add("global", template["global"]); merged.Add(global, template[global]);
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged); //Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged; return merged;
@@ -228,7 +247,7 @@ namespace PepperDash.Core.Config
} }
catch (Exception e) catch (Exception e)
{ {
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e); Debug.LogError($"Cannot merge items at path {propPath}: \r{e}");
} }
} }
} }

View File

@@ -40,20 +40,20 @@ namespace PepperDash.Core
private static ILogger _logger; private static ILogger _logger;
private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch; private static readonly LoggingLevelSwitch _consoleLogLevelSwitch;
private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch; private static readonly LoggingLevelSwitch _websocketLogLevelSwitch;
private static readonly LoggingLevelSwitch _errorLogLevelSwitch; private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
private static readonly LoggingLevelSwitch _fileLevelSwitch; private static readonly LoggingLevelSwitch _fileLogLevelSwitch;
/// <summary> /// <summary>
/// Gets the minimum log level for the websocket sink. /// Gets the minimum log level for the websocket sink.
/// </summary> /// </summary>
public static LogEventLevel WebsocketMinimumLogLevel public static LogEventLevel WebsocketMinimumLogLevel
{ {
get { return _websocketLoggingLevelSwitch.MinimumLevel; } get { return _websocketLogLevelSwitch.MinimumLevel; }
} }
private static readonly DebugWebsocketSink _websocketSink; private static readonly DebugWebsocketSink _websocketSink;
@@ -138,13 +138,13 @@ namespace PepperDash.Core
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); _consoleLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); _websocketLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); _fileLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
@@ -162,14 +162,14 @@ namespace PepperDash.Core
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher()) .Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch) .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLogLevelSwitch)
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch) .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLogLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug, restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60, retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14,
levelSwitch: _fileLevelSwitch levelSwitch: _fileLogLevelSwitch
); );
try try
@@ -237,10 +237,13 @@ namespace PepperDash.Core
if (DoNotLoadConfigOnNextBoot) if (DoNotLoadConfigOnNextBoot)
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) => _errorLogLevelSwitch.MinimumLevelChanged += (sender, args) =>
{ {
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel); LogMessage(LogEventLevel.Information, "Error log debug level set to {minimumLevel}", _errorLogLevelSwitch.MinimumLevel);
}; };
// Set initial error log level based on platform && stored level. If appliance, use stored level, otherwise default to verbose
SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? _errorLogLevelSwitch.MinimumLevel : LogEventLevel.Verbose);
} }
/// <summary> /// <summary>
@@ -273,9 +276,9 @@ namespace PepperDash.Core
{ {
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n"); CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, (int)LogEventLevel.Information); CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, levelStoreKey == ErrorLogLevelStoreKey ? (int)LogEventLevel.Warning : (int)LogEventLevel.Information);
return LogEventLevel.Information; return levelStoreKey == ErrorLogLevelStoreKey ? LogEventLevel.Warning : LogEventLevel.Information;
} }
if (logLevel < 0 || logLevel > 5) if (logLevel < 0 || logLevel > 5)
@@ -284,6 +287,8 @@ namespace PepperDash.Core
return LogEventLevel.Information; return LogEventLevel.Information;
} }
CrestronConsole.PrintLine($"Stored log level for {levelStoreKey} is {logLevel}");
return (LogEventLevel)logLevel; return (LogEventLevel)logLevel;
} }
catch (Exception ex) catch (Exception ex)
@@ -349,7 +354,11 @@ namespace PepperDash.Core
if (levelString.Trim() == "?") if (levelString.Trim() == "?")
{ {
CrestronConsole.ConsoleCommandResponse( CrestronConsole.ConsoleCommandResponse(
"Used to set the minimum level of debug messages to be printed to the console:\r\n" + "Used to set the minimum level of debug messages:\r\n" +
"Usage: appdebug:P [sink] [level]\r\n" +
" sink: console (default), errorlog, file, all\r\n" +
" all: sets all sinks to the specified level\r\n" +
" level: 0-5 or LogEventLevel name\r\n" +
$"{_logLevels[0]} = 0\r\n" + $"{_logLevels[0]} = 0\r\n" +
$"{_logLevels[1]} = 1\r\n" + $"{_logLevels[1]} = 1\r\n" +
$"{_logLevels[2]} = 2\r\n" + $"{_logLevels[2]} = 2\r\n" +
@@ -361,32 +370,88 @@ namespace PepperDash.Core
if (string.IsNullOrEmpty(levelString.Trim())) if (string.IsNullOrEmpty(levelString.Trim()))
{ {
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel); CrestronConsole.ConsoleCommandResponse("Console log level = {0}\r\n", _consoleLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("File log level = {0}\r\n", _fileLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("Error log level = {0}\r\n", _errorLogLevelSwitch.MinimumLevel);
return; return;
} }
if (int.TryParse(levelString, out var levelInt)) // Parse tokens: first token is sink (defaults to console), second token is level
var tokens = levelString.Trim().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
string sinkName;
string levelToken;
if (tokens.Length == 1)
{
// Single token - assume it's a level for console sink
sinkName = "console";
levelToken = tokens[0];
}
else if (tokens.Length == 2)
{
// Two tokens - first is sink, second is level
sinkName = tokens[0].ToLowerInvariant();
levelToken = tokens[1];
}
else
{
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]");
return;
}
// Parse the level using the same logic as before
LogEventLevel level;
if (int.TryParse(levelToken, out var levelInt))
{ {
if (levelInt < 0 || levelInt > 5) if (levelInt < 0 || levelInt > 5)
{ {
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5"); CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level. If using a number, value must be between 0-5");
return; return;
} }
SetDebugLevel((uint)levelInt);
return;
}
if (Enum.TryParse<LogEventLevel>(levelString, true, out var levelEnum)) if (!_logLevels.TryGetValue((uint)levelInt, out level))
{
level = LogEventLevel.Information;
CrestronConsole.ConsoleCommandResponse($"{levelInt} not valid. Setting level to {level}");
}
}
else if (Enum.TryParse(levelToken, true, out level))
{ {
SetDebugLevel(levelEnum); // Successfully parsed as LogEventLevel enum
}
else
{
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level");
return; return;
} }
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level"); // Set the level for the specified sink
switch (sinkName)
{
case "console":
SetDebugLevel(level);
break;
case "errorlog":
SetErrorLogMinimumDebugLevel(level);
break;
case "file":
SetFileMinimumDebugLevel(level);
break;
case "all":
SetDebugLevel(level);
SetErrorLogMinimumDebugLevel(level);
SetFileMinimumDebugLevel(level);
break;
default:
CrestronConsole.ConsoleCommandResponse($"Error: Unknown sink '{sinkName}'. Valid sinks: console, errorlog, file");
break;
}
} }
catch catch
{ {
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]"); CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]");
} }
} }
@@ -416,10 +481,10 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetDebugLevel(LogEventLevel level) public static void SetDebugLevel(LogEventLevel level)
{ {
_consoleLoggingLevelSwitch.MinimumLevel = level; _consoleLogLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n", CrestronConsole.ConsoleCommandResponse("[Application {0}] Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel); InitialParametersClass.ApplicationNumber, _consoleLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
@@ -436,14 +501,14 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{ {
_websocketLoggingLevelSwitch.MinimumLevel = level; _websocketLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel); LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLogLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -453,12 +518,17 @@ namespace PepperDash.Core
{ {
_errorLogLevelSwitch.MinimumLevel = level; _errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); CrestronConsole.ConsoleCommandResponse("[Application {0}] Error log level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _errorLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(ErrorLogLevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err); CrestronConsole.PrintLine($"Error saving error log debug level setting: {err}");
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -466,14 +536,19 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public static void SetFileMinimumDebugLevel(LogEventLevel level) public static void SetFileMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; _fileLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); CrestronConsole.ConsoleCommandResponse("[Application {0}] File log level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _fileLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
var err = CrestronDataStoreStatic.SetLocalIntValue(FileLevelStoreKey, (int)level);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err); CrestronConsole.PrintLine($"Error saving file debug level setting: {err}");
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
@@ -1006,9 +1081,6 @@ namespace PepperDash.Core
/// Logs to Console when at-level, and all messages to error log /// Logs to Console when at-level, and all messages to error log
/// </summary> /// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// Console method
/// </summary>
public static void Console(uint level, ErrorLogLevel errorLogLevel, public static void Console(uint level, ErrorLogLevel errorLogLevel,
string format, params object[] items) string format, params object[] items)
{ {
@@ -1021,9 +1093,6 @@ namespace PepperDash.Core
/// it will only be written to the log. /// it will only be written to the log.
/// </summary> /// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
/// <summary>
/// ConsoleWithLog method
/// </summary>
public static void ConsoleWithLog(uint level, string format, params object[] items) public static void ConsoleWithLog(uint level, string format, params object[] items)
{ {
LogMessage(level, format, items); LogMessage(level, format, items);

View File

@@ -12,15 +12,15 @@ using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
/// <summary> /// <summary>
/// Represents a CecPortController /// Represents a CecPortController
/// </summary> /// </summary>
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
{ {
/// <summary> /// <summary>
/// Gets or sets the StreamDebugging /// Gets or sets the StreamDebugging
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public CommunicationStreamDebugging StreamDebugging { get; private set; }
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
@@ -33,16 +33,16 @@ namespace PepperDash.Essentials.Core
ICec Port; ICec Port;
public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc, public CecPortController(string key, Func<EssentialsControlPropertiesConfig, ICec> postActivationFunc,
EssentialsControlPropertiesConfig config):base(key) EssentialsControlPropertiesConfig config) : base(key)
{ {
StreamDebugging = new CommunicationStreamDebugging(key); StreamDebugging = new CommunicationStreamDebugging(key);
AddPostActivationAction(() => AddPostActivationAction(() =>
{ {
Port = postActivationFunc(config); Port = postActivationFunc(config);
Port.StreamCec.CecChange += StreamCec_CecChange; Port.StreamCec.CecChange += StreamCec_CecChange;
}); });
} }
public CecPortController(string key, ICec port) public CecPortController(string key, ICec port)
@@ -58,27 +58,25 @@ namespace PepperDash.Essentials.Core
if (args.EventId == CecEventIds.CecMessageReceivedEventId) if (args.EventId == CecEventIds.CecMessageReceivedEventId)
OnDataReceived(cecDevice.Received.StringValue); OnDataReceived(cecDevice.Received.StringValue);
else if (args.EventId == CecEventIds.ErrorFeedbackEventId) else if (args.EventId == CecEventIds.ErrorFeedbackEventId)
if(cecDevice.ErrorFeedback.BoolValue) if (cecDevice.ErrorFeedback.BoolValue)
Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error"); Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error");
} }
void OnDataReceived(string s) void OnDataReceived(string s)
{ {
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(s); var bytes = Encoding.GetEncoding(28591).GetBytes(s);
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedText(s);
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); textHandler(this, new GenericCommMethodReceiveTextArgs(s));
textHandler(this, new GenericCommMethodReceiveTextArgs(s)); }
}
} }
#region IBasicCommunication Members #region IBasicCommunication Members
@@ -90,8 +88,7 @@ namespace PepperDash.Essentials.Core
{ {
if (Port == null) if (Port == null)
return; return;
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentText(text);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text);
Port.StreamCec.Send.StringValue = text; Port.StreamCec.Send.StringValue = text;
} }
@@ -103,8 +100,8 @@ namespace PepperDash.Essentials.Core
if (Port == null) if (Port == null)
return; return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.StreamCec.Send.StringValue = text; Port.StreamCec.Send.StringValue = text;
} }

View File

@@ -1,59 +1,78 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
/// <summary> /// <summary>
/// Represents a ComPortController /// Represents a ComPortController
/// </summary> /// </summary>
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
{ {
/// <summary> /// <summary>
/// Gets or sets the StreamDebugging /// Gets or sets the StreamDebugging
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
/// Event fired when bytes are received
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Event fired when text is received
/// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// Gets or sets the IsConnected /// Gets or sets the IsConnected
/// </summary> /// </summary>
public bool IsConnected { get { return true; } } public bool IsConnected { get { return true; } }
ComPort Port; ComPort Port;
ComPort.ComPortSpec Spec; ComPort.ComPortSpec Spec;
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc, /// <summary>
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) /// Constructor
{ /// </summary>
StreamDebugging = new CommunicationStreamDebugging(key); /// <param name="key"></param>
/// <param name="postActivationFunc"></param>
/// <param name="spec"></param>
/// <param name="config"></param>
public ComPortController(string key, Func<EssentialsControlPropertiesConfig, ComPort> postActivationFunc,
ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
Spec = spec; Spec = spec;
AddPostActivationAction(() => AddPostActivationAction(() =>
{ {
Port = postActivationFunc(config); Port = postActivationFunc(config);
RegisterAndConfigureComPort(); RegisterAndConfigureComPort();
}); });
} }
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Device key</param>
/// <param name="port">COM port instance</param>
/// <param name="spec">COM port specification</param>
public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec) public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec)
: base(key) : base(key)
{ {
if (port == null) if (port == null)
{ {
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function"); Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
return; return;
} }
@@ -64,71 +83,77 @@ namespace PepperDash.Essentials.Core
RegisterAndConfigureComPort(); RegisterAndConfigureComPort();
} }
private void RegisterAndConfigureComPort() private void RegisterAndConfigureComPort()
{ {
if (Port == null) if (Port == null)
{ {
Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist.");
return; return;
} }
if (Port.Parent is CrestronControlSystem)
{
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result);
return; // false
}
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
return;
}
Port.SerialDataReceived += Port_SerialDataReceived;
}
~ComPortController() if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
{
var result = Port.Register();
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
{
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
return;
}
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
}
var specResult = Port.SetComPortSpec(Spec);
if (specResult != 0)
{
this.LogError($"Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
return;
}
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
Port.SerialDataReceived += Port_SerialDataReceived;
}
/// <summary>
/// Destructor
/// </summary>
~ComPortController()
{ {
Port.SerialDataReceived -= Port_SerialDataReceived; Port.SerialDataReceived -= Port_SerialDataReceived;
} }
void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args) void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args)
{ {
OnDataReceived(args.SerialData); OnDataReceived(args.SerialData);
} }
void OnDataReceived(string s) void OnDataReceived(string s)
{ {
var eventSubscribed = false; var eventSubscribed = false;
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(s); var bytes = Encoding.GetEncoding(28591).GetBytes(s);
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
eventSubscribed = true; eventSubscribed = true;
} }
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
if (StreamDebugging.RxStreamDebuggingIsEnabled) this.PrintReceivedText(s);
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); textHandler(this, new GenericCommMethodReceiveTextArgs(s));
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
eventSubscribed = true; eventSubscribed = true;
} }
if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered"); if (!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
} }
/// <summary> /// <summary>
/// Deactivate method /// Deactivate method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override bool Deactivate() public override bool Deactivate()
{ {
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success; return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
@@ -136,70 +161,68 @@ namespace PepperDash.Essentials.Core
#region IBasicCommunication Members #region IBasicCommunication Members
/// <summary> /// <summary>
/// SendText method /// SendText method
/// </summary> /// </summary>
public void SendText(string text) public void SendText(string text)
{ {
if (Port == null) if (Port == null)
return; return;
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentText(text);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); Port.Send(text);
Port.Send(text);
} }
/// <summary> /// <summary>
/// SendBytes method /// SendBytes method
/// </summary> /// </summary>
public void SendBytes(byte[] bytes) public void SendBytes(byte[] bytes)
{ {
if (Port == null) if (Port == null)
return; return;
var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (StreamDebugging.TxStreamDebuggingIsEnabled) this.PrintSentBytes(bytes);
Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Port.Send(text); Port.Send(text);
} }
/// <summary> /// <summary>
/// Connect method /// Connect method
/// </summary> /// </summary>
public void Connect() public void Connect()
{ {
} }
/// <summary> /// <summary>
/// Disconnect method /// Disconnect method
/// </summary> /// </summary>
public void Disconnect() public void Disconnect()
{ {
} }
#endregion #endregion
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="s"></param> /// <param name="s"></param>
/// <summary> /// <summary>
/// SimulateReceive method /// SimulateReceive method
/// </summary> /// </summary>
public void SimulateReceive(string s) public void SimulateReceive(string s)
{ {
// split out hex chars and build string // split out hex chars and build string
var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])");
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
foreach (var t in split) foreach (var t in split)
{ {
if (t.StartsWith(@"\") && t.Length == 4) if (t.StartsWith(@"\") && t.Length == 4)
b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16)));
else else
b.Append(t); b.Append(t);
} }
OnDataReceived(b.ToString()); OnDataReceived(b.ToString());
} }
} }
} }

View File

@@ -64,8 +64,11 @@ namespace PepperDash.Essentials.Core
break; break;
case eControlMethod.Ssh: case eControlMethod.Ssh:
{ {
var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password); var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password)
ssh.AutoReconnect = c.AutoReconnect; {
AutoReconnect = c.AutoReconnect,
DisableEcho = c.DisableSshEcho
};
if (ssh.AutoReconnect) if (ssh.AutoReconnect)
ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = ssh; comm = ssh;
@@ -73,8 +76,10 @@ namespace PepperDash.Essentials.Core
} }
case eControlMethod.Tcpip: case eControlMethod.Tcpip:
{ {
var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize); var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize)
tcp.AutoReconnect = c.AutoReconnect; {
AutoReconnect = c.AutoReconnect
};
if (tcp.AutoReconnect) if (tcp.AutoReconnect)
tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = tcp; comm = tcp;
@@ -90,8 +95,10 @@ namespace PepperDash.Essentials.Core
break; break;
case eControlMethod.SecureTcpIp: case eControlMethod.SecureTcpIp:
{ {
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize); var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize)
secureTcp.AutoReconnect = c.AutoReconnect; {
AutoReconnect = c.AutoReconnect
};
if (secureTcp.AutoReconnect) if (secureTcp.AutoReconnect)
secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
comm = secureTcp; comm = secureTcp;

View File

@@ -124,22 +124,35 @@ namespace PepperDash.Essentials.Core.Config
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config"); Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config");
return true; return true;
} }
else else
{ {
var doubleObj = JObject.Parse(fs.ReadToEnd()); var parsedConfig = JObject.Parse(fs.ReadToEnd());
ConfigObject = PortalConfigReader.MergeConfigs(doubleObj).ToObject<EssentialsConfig>();
// Extract SystemUrl and TemplateUrl into final config output // Check if it's a v2 config (check for "version" node)
// this means it's already merged by the Portal API
if (doubleObj["system_url"] != null) // from the v2 config tool
var isV2Config = parsedConfig["versions"] != null;
if (isV2Config)
{ {
ConfigObject.SystemUrl = doubleObj["system_url"].Value<string>(); Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary.");
ConfigObject = parsedConfig.ToObject<EssentialsConfig>();
Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config");
return true;
} }
if (doubleObj["template_url"] != null) // Extract SystemUrl and TemplateUrl into final config output
ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject<EssentialsConfig>();
if (parsedConfig["system_url"] != null)
{ {
ConfigObject.TemplateUrl = doubleObj["template_url"].Value<string>(); ConfigObject.SystemUrl = parsedConfig["system_url"].Value<string>();
}
if (parsedConfig["template_url"] != null)
{
ConfigObject.TemplateUrl = parsedConfig["template_url"].Value<string>();
} }
} }

View File

@@ -11,86 +11,169 @@ using PepperDash.Core;
namespace PepperDash.Essentials.Core.Config namespace PepperDash.Essentials.Core.Config
{ {
/// <summary> /// <summary>
/// Loads the ConfigObject from the file /// Loads the ConfigObject from the file
/// </summary> /// </summary>
public class EssentialsConfig : BasicConfig public class EssentialsConfig : BasicConfig
{ {
[JsonProperty("system_url")] /// <summary>
/// Gets or sets the SystemUrl
/// </summary>
[JsonProperty("system_url")]
public string SystemUrl { get; set; } public string SystemUrl { get; set; }
[JsonProperty("template_url")] /// <summary>
/// Gets or sets the TemplateUrl
/// </summary>
[JsonProperty("template_url")]
public string TemplateUrl { get; set; } public string TemplateUrl { get; set; }
/// <summary>
/// Gets the SystemUuid extracted from the SystemUrl
/// </summary>
[JsonProperty("systemUuid")] [JsonProperty("systemUuid")]
public string SystemUuid public string SystemUuid
{ {
get get
{ {
if (string.IsNullOrEmpty(SystemUrl)) string uuid;
return "missing url";
if (SystemUrl.Contains("#")) if (string.IsNullOrEmpty(SystemUrl))
{ {
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); uuid = "missing url";
string uuid = result.Groups[1].Value; }
return uuid; else if (SystemUrl.Contains("#"))
} else {
{ var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (SystemUrl.Contains("detail"))
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*");
uuid = result.Groups[1].Value;
}
else
{
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*"); var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
string uuid = result.Groups[1].Value; uuid = result.Groups[1].Value;
return uuid;
} }
return uuid;
} }
} }
/// <summary>
/// Gets the TemplateUuid extracted from the TemplateUrl
/// </summary>
[JsonProperty("templateUuid")] [JsonProperty("templateUuid")]
public string TemplateUuid public string TemplateUuid
{ {
get get
{ {
if (string.IsNullOrEmpty(TemplateUrl)) string uuid;
return "missing template url";
if (TemplateUrl.Contains("#")) if (string.IsNullOrEmpty(TemplateUrl))
{ {
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*"); uuid = "missing template url";
string uuid = result.Groups[1].Value;
return uuid;
} else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
string uuid = result.Groups[2].Value;
return uuid;
} }
else if (TemplateUrl.Contains("#"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
uuid = result.Groups[1].Value;
}
else if (TemplateUrl.Contains("detail"))
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*");
uuid = result.Groups[2].Value;
}
else
{
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
uuid = result.Groups[2].Value;
}
return uuid;
} }
} }
[JsonProperty("rooms")]
/// <summary> /// <summary>
/// Gets or sets the Rooms /// Gets or sets the Rooms
/// </summary> /// </summary>
[JsonProperty("rooms")]
public List<DeviceConfig> Rooms { get; set; } public List<DeviceConfig> Rooms { get; set; }
/// <summary>
/// Gets or sets the Versions
/// </summary>
public VersionData Versions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="EssentialsConfig"/> class.
/// </summary>
public EssentialsConfig() public EssentialsConfig()
: base() : base()
{ {
Rooms = new List<DeviceConfig>(); Rooms = new List<DeviceConfig>();
} }
} }
/// <summary> /// <summary>
/// Represents a SystemTemplateConfigs /// Represents version data for Essentials and its packages
/// </summary> /// </summary>
public class SystemTemplateConfigs public class VersionData
{ {
/// <summary> /// <summary>
/// Gets or sets the System /// Gets or sets the Essentials version
/// </summary> /// </summary>
[JsonProperty("essentials")]
public NugetVersion Essentials { get; set; }
/// <summary>
/// Gets or sets the list of Packages
/// </summary>
[JsonProperty("packages")]
public List<NugetVersion> Packages { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VersionData"/> class.
/// </summary>
public VersionData()
{
Packages = new List<NugetVersion>();
}
}
/// <summary>
/// Represents a NugetVersion
/// </summary>
public class NugetVersion
{
/// <summary>
/// Gets or sets the Version
/// </summary>
[JsonProperty("version")]
public string Version { get; set; }
/// <summary>
/// Gets or sets the PackageId
/// </summary>
[JsonProperty("packageId")]
public string PackageId { get; set; }
}
/// <summary>
/// Represents a SystemTemplateConfigs
/// </summary>
public class SystemTemplateConfigs
{
/// <summary>
/// Gets or sets the System
/// </summary>
public EssentialsConfig System { get; set; } public EssentialsConfig System { get; set; }
/// <summary>
/// Gets or sets the Template
/// </summary>
public EssentialsConfig Template { get; set; } public EssentialsConfig Template { get; set; }
} }
} }

View File

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

View File

@@ -16,121 +16,201 @@ namespace PepperDash.Essentials.Core.Fusion
{ {
// Processor Attributes // Processor Attributes
/// <summary>
/// Processor IP 1
/// </summary>
[JoinName("ProcessorIp1")] [JoinName("ProcessorIp1")]
public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" }, public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" },
new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor IP 2
/// </summary>
[JoinName("ProcessorIp2")] [JoinName("ProcessorIp2")]
public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" }, public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" },
new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Gateway
/// </summary>
[JoinName("ProcessorGateway")] [JoinName("ProcessorGateway")]
public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" }, public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" },
new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Hostname
/// </summary>
[JoinName("ProcessorHostname")] [JoinName("ProcessorHostname")]
public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" }, public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" },
new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Domain
/// </summary>
[JoinName("ProcessorDomain")] [JoinName("ProcessorDomain")]
public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" }, public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" },
new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor DNS 1
/// </summary>
[JoinName("ProcessorDns1")] [JoinName("ProcessorDns1")]
public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" }, public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" },
new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor DNS 2
/// </summary>
[JoinName("ProcessorDns2")] [JoinName("ProcessorDns2")]
public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" }, public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" },
new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor MAC 1
/// </summary>
[JoinName("ProcessorMac1")] [JoinName("ProcessorMac1")]
public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" }, public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" },
new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor MAC 2
/// </summary>
[JoinName("ProcessorMac2")] [JoinName("ProcessorMac2")]
public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" }, public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" },
new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Net Mask 1
/// </summary>
[JoinName("ProcessorNetMask1")] [JoinName("ProcessorNetMask1")]
public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" }, public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" },
new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Net Mask 2
/// </summary>
[JoinName("ProcessorNetMask2")] [JoinName("ProcessorNetMask2")]
public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" }, public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" },
new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Firmware
/// </summary>
[JoinName("ProcessorFirmware")] [JoinName("ProcessorFirmware")]
public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" }, public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" },
new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Program Name Start
/// </summary>
[JoinName("ProgramNameStart")] [JoinName("ProgramNameStart")]
public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" }, public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" },
new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// Processor Reboot
/// </summary>
[JoinName("ProcessorReboot")] [JoinName("ProcessorReboot")]
public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" }, public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" },
new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital });
// Volume Controls // Volume Controls
/// <summary>
/// Volume Fader 1
/// </summary>
[JoinName("VolumeFader1")] [JoinName("VolumeFader1")]
public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" }, public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" },
new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog }); new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog });
// Codec Info // Codec Info
/// <summary>
/// VC Codec In Call
/// </summary>
[JoinName("VcCodecInCall")] [JoinName("VcCodecInCall")]
public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" }, public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" },
new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
/// <summary>
/// VC Codec Online
/// </summary>
[JoinName("VcCodecOnline")] [JoinName("VcCodecOnline")]
public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" }, public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" },
new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
/// <summary>
/// VC Codec IP Address
/// </summary>
[JoinName("VcCodecIpAddress")] [JoinName("VcCodecIpAddress")]
public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" }, public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" },
new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
/// <summary>
/// VC Codec IP Port
/// </summary>
[JoinName("VcCodecIpPort")] [JoinName("VcCodecIpPort")]
public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" }, public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" },
new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
// Source Attributes // Source Attributes
/// <summary>
/// Display 1 Current Source Name
/// </summary>
[JoinName("Display1CurrentSourceName")] [JoinName("Display1CurrentSourceName")]
public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" }, public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" },
new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial });
// Device Online Status // Device Online Status
/// <summary>
/// Touchpanel Online Start
/// </summary>
[JoinName("TouchpanelOnlineStart")] [JoinName("TouchpanelOnlineStart")]
public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" }, public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" },
new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
/// <summary>
/// Xpanel Online Start
/// </summary>
[JoinName("XpanelOnlineStart")] [JoinName("XpanelOnlineStart")]
public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" }, public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" },
new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
/// <summary>
/// Display Online Start
/// </summary>
[JoinName("DisplayOnlineStart")] [JoinName("DisplayOnlineStart")]
public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" }, public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" },
new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital });
/// <summary>
/// Display 1 Laptop Source Start
/// </summary>
[JoinName("Display1LaptopSourceStart")] [JoinName("Display1LaptopSourceStart")]
public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 166, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" }, public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 165, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" },
new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
/// <summary>
/// Display 1 Disc Player Source Start
/// </summary>
[JoinName("Display1DiscPlayerSourceStart")] [JoinName("Display1DiscPlayerSourceStart")]
public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 181, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" }, public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 180, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" },
new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
/// <summary>
/// Display 1 Set Top Box Source Start
/// </summary>
[JoinName("Display1SetTopBoxSourceStart")] [JoinName("Display1SetTopBoxSourceStart")]
public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 188, JoinSpan = 5, AttributeName = "Display 1 - Source TV" }, public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 185, JoinSpan = 5, AttributeName = "Display 1 - Source TV" },
new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
// Display 1 // Display 1
/// <summary>
/// Display 1 Start
/// </summary>
[JoinName("Display1Start")] [JoinName("Display1Start")]
public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 158, JoinSpan = 1 }, public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 190, JoinSpan = 1 },
new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital });
/// <summary> /// <summary>
/// Constructor to use when instantiating this Join Map without inheriting from it /// Constructor to use when instantiating this Join Map without inheriting from it
/// </summary> /// </summary>

View File

@@ -1,6 +1,4 @@
 using Crestron.SimplSharp;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronXml; using Crestron.SimplSharp.CrestronXml;
using Crestron.SimplSharp.CrestronXml.Serialization; using Crestron.SimplSharp.CrestronXml.Serialization;
@@ -8,6 +6,7 @@ using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.Fusion; using Crestron.SimplSharpPro.Fusion;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using Serilog.Events; using Serilog.Events;
@@ -21,26 +20,42 @@ namespace PepperDash.Essentials.Core.Fusion
/// <summary> /// <summary>
/// Represents a EssentialsHuddleSpaceFusionSystemControllerBase /// Represents a EssentialsHuddleSpaceFusionSystemControllerBase
/// </summary> /// </summary>
public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest
{ {
private readonly EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap; private IEssentialsRoomFusionControllerPropertiesConfig _config;
private EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap;
private const string RemoteOccupancyXml = "<Occupancy><Type>Local</Type><State>{0}</State></Occupancy>"; private const string RemoteOccupancyXml = "<Occupancy><Type>Local</Type><State>{0}</State></Occupancy>";
private readonly bool _guidFileExists; private bool _guidFileExists;
private readonly Dictionary<Device, BoolInputSig> _sourceToFeedbackSigs = private readonly Dictionary<Device, BoolInputSig> _sourceToFeedbackSigs =
new Dictionary<Device, BoolInputSig>(); new Dictionary<Device, BoolInputSig>();
/// <summary>
/// Gets or sets the CurrentRoomSourceNameSig
/// </summary>
protected StringSigData CurrentRoomSourceNameSig; protected StringSigData CurrentRoomSourceNameSig;
private readonly FusionCustomPropertiesBridge CustomPropertiesBridge = new FusionCustomPropertiesBridge(); private readonly FusionCustomPropertiesBridge CustomPropertiesBridge = new FusionCustomPropertiesBridge();
/// <summary>
/// Gets or sets the FusionOccSensor
/// </summary>
protected FusionOccupancySensorAsset FusionOccSensor; protected FusionOccupancySensorAsset FusionOccSensor;
private readonly FusionRemoteOccupancySensor FusionRemoteOccSensor; private readonly FusionRemoteOccupancySensor FusionRemoteOccSensor;
/// <summary>
/// Gets or sets the FusionRoom
/// </summary>
protected FusionRoom FusionRoom; protected FusionRoom FusionRoom;
/// <summary>
/// Gets or sets the FusionStaticAssets
/// </summary>
protected Dictionary<int, FusionAsset> FusionStaticAssets; protected Dictionary<int, FusionAsset> FusionStaticAssets;
private readonly long PushNotificationTimeout = 5000; private readonly long PushNotificationTimeout = 5000;
private readonly IEssentialsRoom Room; private IEssentialsRoom Room;
private readonly long SchedulePollInterval = 300000; private readonly long SchedulePollInterval = 300000;
private Event _currentMeeting; private Event _currentMeeting;
@@ -48,8 +63,7 @@ namespace PepperDash.Essentials.Core.Fusion
private CTimer _dailyTimeRequestTimer; private CTimer _dailyTimeRequestTimer;
private StatusMonitorCollection _errorMessageRollUp; private StatusMonitorCollection _errorMessageRollUp;
private FusionRoomGuids _guiDs; private FusionRoomGuids _guids;
private uint _ipId;
private bool _isRegisteredForSchedulePushNotifications; private bool _isRegisteredForSchedulePushNotifications;
private Event _nextMeeting; private Event _nextMeeting;
@@ -60,6 +74,20 @@ namespace PepperDash.Essentials.Core.Fusion
private string _roomOccupancyRemoteString; private string _roomOccupancyRemoteString;
private bool _helpRequestSent;
private eFusionHelpResponse _helpRequestStatus;
/// <inheritdoc />
public StringFeedback HelpRequestResponseFeedback { get; private set; }
/// <inheritdoc />
public BoolFeedback HelpRequestSentFeedback { get; private set; }
/// <inheritdoc />
public StringFeedback HelpRequestStatusFeedback { get; private set; }
#region System Info Sigs #region System Info Sigs
//StringSigData SystemName; //StringSigData SystemName;
@@ -93,71 +121,110 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion #endregion
public EssentialsHuddleSpaceFusionSystemControllerBase(IEssentialsRoom room, uint ipId, string joinMapKey) /// <summary>
/// Constructor
/// </summary>
public IEssentialsRoomFusionController(string key, string name, IEssentialsRoomFusionControllerPropertiesConfig config)
: base(key, name)
{
_config = config;
AddPostActivationAction(() =>
{
var room = DeviceManager.GetDeviceForKey<IEssentialsRoom>(_config.RoomKey);
if (room == null)
{
this.LogError("Error Creating Fusion Room Controller. No room found with key '{0}'", _config.RoomKey);
return;
}
this.LogInformation("Creating Fusion Room Controller for room '{0}' at IPID: {1:X2}", room.Key, _config.IpIdInt);
ConstructorHelper(room, _config.IpIdInt, _config.JoinMapKey);
});
}
/// <summary>
///
/// </summary>
/// <param name="room"></param>
/// <param name="ipId"></param>
/// <param name="joinMapKey"></param>
public IEssentialsRoomFusionController(IEssentialsRoom room, string ipId, string joinMapKey)
: base(room.Key + "-fusion") : base(room.Key + "-fusion")
{
_config = new IEssentialsRoomFusionControllerPropertiesConfig()
{
IpId = ipId,
RoomKey = room.Key,
JoinMapKey = joinMapKey
};
ConstructorHelper(room, _config.IpIdInt, joinMapKey);
}
private void ConstructorHelper(IEssentialsRoom room, uint ipId, string joinMapKey)
{ {
try try
{ {
this.LogDebug("ConstructorHelper called for Fusion Room Controller for room '{0}' with IPID {1:X2}", room.Key, ipId);
this.LogDebug("JoinMap Key: {0}", joinMapKey);
JoinMap = new EssentialsHuddleSpaceRoomFusionRoomJoinMap(1); JoinMap = new EssentialsHuddleSpaceRoomFusionRoomJoinMap(1);
CrestronConsole.AddNewConsoleCommand((o) => JoinMap.PrintJoinMapInfo(), string.Format("ptjnmp-{0}", Key), "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator); this.LogDebug("JoinMap created");
CrestronConsole.AddNewConsoleCommand((o) =>
{
if (o is string deviceKey)
{
if (string.IsNullOrEmpty(deviceKey) || deviceKey == "?")
{
CrestronConsole.ConsoleCommandResponse("Please provide a device key for a Fusion Room instance");
return;
}
else if (deviceKey != this.Key)
{
return;
}
}
else
{
CrestronConsole.ConsoleCommandResponse("Invalid parameter. Please provide a device key for a Fusion Room instance");
return;
}
JoinMap.PrintJoinMapInfo();
}, "printfusionjoinmap", "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator);
if (!string.IsNullOrEmpty(joinMapKey)) if (!string.IsNullOrEmpty(joinMapKey))
{ {
// this.LogDebug("Attempting to get custom join map for key: {0}", joinMapKey);
var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey);
if (customJoins != null) if (customJoins != null)
{ {
JoinMap.SetCustomJoinData(customJoins); JoinMap.SetCustomJoinData(customJoins);
} }
} }
Room = room; Room = room;
_ipId = ipId; this.LogDebug("Room found: {0}", Room.Key);
FusionStaticAssets = new Dictionary<int, FusionAsset>(); FusionStaticAssets = new Dictionary<int, FusionAsset>();
_guiDs = new FusionRoomGuids(); this.LogDebug("FusionStaticAssets dictionary created");
var mac = _guids = new FusionRoomGuids();
CrestronEthernetHelper.GetEthernetParameter(
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0);
var slot = Global.ControlSystem.ProgramNumber;
var guidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId);
var oldGuidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
if (File.Exists(oldGuidFilePath))
{
Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File");
File.Copy(oldGuidFilePath, guidFilePath);
File.Delete(oldGuidFilePath);
}
_guidFileExists = File.Exists(guidFilePath);
// Check if file exists
if (!_guidFileExists)
{
// Does not exist. Create GUIDs
_guiDs = new FusionRoomGuids(Room.Name, ipId, _guiDs.GenerateNewRoomGuid(slot, mac),
FusionStaticAssets);
}
else
{
// Exists. Read GUIDs
ReadGuidFile(guidFilePath);
}
this.LogDebug("FusionRoomGuids created");
if (Room is IRoomOccupancy occupancyRoom) if (Room is IRoomOccupancy occupancyRoom)
{ {
Debug.LogDebug(this, "Room '{0}' supports IRoomOccupancy", Room.Key);
if (occupancyRoom.RoomOccupancy != null) if (occupancyRoom.RoomOccupancy != null)
{ {
if (occupancyRoom.OccupancyStatusProviderIsRemote) if (occupancyRoom.OccupancyStatusProviderIsRemote)
@@ -171,8 +238,8 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
this.LogDebug("Occupancy setup complete");
AddPostActivationAction(() => PostActivate(guidFilePath));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -180,9 +247,54 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
private void PostActivate(string guidFilePath) private string GetGuidFilePath(uint ipId)
{ {
CreateSymbolAndBasicSigs(_ipId); var mac =
CrestronEthernetHelper.GetEthernetParameter(
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0);
var slot = Global.ControlSystem.ProgramNumber;
var guidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _config.IpIdInt);
var oldGuidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
if (File.Exists(oldGuidFilePath))
{
Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File");
File.Copy(oldGuidFilePath, guidFilePath);
File.Delete(oldGuidFilePath);
}
_guidFileExists = File.Exists(guidFilePath);
// Check if file exists
if (!_guidFileExists)
{
// Does not exist. Create GUIDs
_guids = new FusionRoomGuids(Room.Name, ipId, _guids.GenerateNewRoomGuid(slot, mac),
FusionStaticAssets);
}
else
{
// Exists. Read GUIDs
ReadGuidFile(guidFilePath);
}
return guidFilePath;
}
/// <inheritdoc />
public override void Initialize()
{
GenerateGuidFile(GetGuidFilePath(_config.IpIdInt));
CreateSymbolAndBasicSigs(_config.IpIdInt);
SetUpSources(); SetUpSources();
SetUpCommunitcationMonitors(); SetUpCommunitcationMonitors();
SetUpDisplay(); SetUpDisplay();
@@ -191,12 +303,18 @@ namespace PepperDash.Essentials.Core.Fusion
FusionRVI.GenerateFileForAllFusionDevices(); FusionRVI.GenerateFileForAllFusionDevices();
GenerateGuidFile(guidFilePath); HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue);
HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent);
HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString());
} }
/// <summary>
/// Gets the RoomGuid
/// </summary>
protected string RoomGuid protected string RoomGuid
{ {
get { return _guiDs.RoomGuid; } get { return _guids.RoomGuid; }
} }
/// <summary> /// <summary>
@@ -204,6 +322,9 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public StringFeedback RoomOccupancyRemoteStringFeedback { get; private set; } public StringFeedback RoomOccupancyRemoteStringFeedback { get; private set; }
/// <summary>
/// Gets the RoomIsOccupiedFeedbackFunc
/// </summary>
protected Func<bool> RoomIsOccupiedFeedbackFunc protected Func<bool> RoomIsOccupiedFeedbackFunc
{ {
get { return () => FusionRemoteOccSensor.RoomOccupied.OutputSig.BoolValue; } get { return () => FusionRemoteOccSensor.RoomOccupied.OutputSig.BoolValue; }
@@ -218,10 +339,16 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion #endregion
/// <summary>
/// ScheduleChange event
/// </summary>
public event EventHandler<ScheduleChangeEventArgs> ScheduleChange; public event EventHandler<ScheduleChangeEventArgs> ScheduleChange;
//public event EventHandler<MeetingChangeEventArgs> MeetingEndWarning; //public event EventHandler<MeetingChangeEventArgs> MeetingEndWarning;
//public event EventHandler<MeetingChangeEventArgs> NextMeetingBeginWarning; //public event EventHandler<MeetingChangeEventArgs> NextMeetingBeginWarning;
/// <summary>
/// RoomInfoChange event
/// </summary>
public event EventHandler<EventArgs> RoomInfoChange; public event EventHandler<EventArgs> RoomInfoChange;
//ScheduleResponseEvent NextMeeting; //ScheduleResponseEvent NextMeeting;
@@ -258,11 +385,11 @@ namespace PepperDash.Essentials.Core.Fusion
Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file"); Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file");
_guiDs = FusionOccSensor == null _guids = FusionOccSensor == null
? new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets) ? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets)
: new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets, FusionOccSensor); : new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor);
var json = JsonConvert.SerializeObject(_guiDs, Newtonsoft.Json.Formatting.Indented); var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented);
using (var sw = new StreamWriter(filePath)) using (var sw = new StreamWriter(filePath))
{ {
@@ -312,17 +439,17 @@ namespace PepperDash.Essentials.Core.Fusion
{ {
var json = File.ReadToEnd(filePath, Encoding.ASCII); var json = File.ReadToEnd(filePath, Encoding.ASCII);
_guiDs = JsonConvert.DeserializeObject<FusionRoomGuids>(json); _guids = JsonConvert.DeserializeObject<FusionRoomGuids>(json);
_ipId = _guiDs.IpId; // _config.IpId = _guids.IpId;
FusionStaticAssets = _guiDs.StaticAssets; FusionStaticAssets = _guids.StaticAssets;
} }
Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}", Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}",
filePath); filePath);
Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _ipId, RoomGuid); Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid);
foreach (var item in FusionStaticAssets) foreach (var item in FusionStaticAssets)
{ {
@@ -343,6 +470,10 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// CreateSymbolAndBasicSigs method
/// </summary>
/// <param name="ipId"></param>
protected virtual void CreateSymbolAndBasicSigs(uint ipId) protected virtual void CreateSymbolAndBasicSigs(uint ipId)
{ {
Debug.LogMessage(LogEventLevel.Information, this, "Creating Fusion Room symbol with GUID: {0} and IP-ID {1:X2}", RoomGuid, ipId); Debug.LogMessage(LogEventLevel.Information, this, "Creating Fusion Room symbol with GUID: {0} and IP-ID {1:X2}", RoomGuid, ipId);
@@ -405,6 +536,10 @@ namespace PepperDash.Essentials.Core.Fusion
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
} }
/// <summary>
/// CrestronEnvironment_EthernetEventHandler method
/// </summary>
/// <param name="ethernetEventArgs"></param>
protected void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) protected void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{ {
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp) if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp)
@@ -413,6 +548,9 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// GetSystemInfo method
/// </summary>
protected void GetSystemInfo() protected void GetSystemInfo()
{ {
//SystemName.InputSig.StringValue = Room.Name; //SystemName.InputSig.StringValue = Room.Name;
@@ -426,6 +564,9 @@ namespace PepperDash.Essentials.Core.Fusion
() => CrestronConsole.SendControlSystemCommand("reboot", ref response)); () => CrestronConsole.SendControlSystemCommand("reboot", ref response));
} }
/// <summary>
/// SetUpEthernetValues method
/// </summary>
protected void SetUpEthernetValues() protected void SetUpEthernetValues()
{ {
_ip1 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorIp1.JoinNumber, JoinMap.ProcessorIp1.AttributeName, eSigIoMask.InputSigOnly); _ip1 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorIp1.JoinNumber, JoinMap.ProcessorIp1.AttributeName, eSigIoMask.InputSigOnly);
@@ -441,6 +582,9 @@ namespace PepperDash.Essentials.Core.Fusion
_netMask2 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorNetMask2.JoinNumber, JoinMap.ProcessorNetMask2.AttributeName, eSigIoMask.InputSigOnly); _netMask2 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorNetMask2.JoinNumber, JoinMap.ProcessorNetMask2.AttributeName, eSigIoMask.InputSigOnly);
} }
/// <summary>
/// GetProcessorEthernetValues method
/// </summary>
protected void GetProcessorEthernetValues() protected void GetProcessorEthernetValues()
{ {
_ip1.InputSig.StringValue = _ip1.InputSig.StringValue =
@@ -475,7 +619,7 @@ namespace PepperDash.Essentials.Core.Fusion
// Interface 1 // Interface 1
if (InitialParametersClass.NumberOfEthernetInterfaces > 1) if (InitialParametersClass.NumberOfEthernetInterfaces > 1)
// Only get these values if the processor has more than 1 NIC // Only get these values if the processor has more than 1 NIC
{ {
_ip2.InputSig.StringValue = _ip2.InputSig.StringValue =
CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.GetEthernetParameter(
@@ -489,6 +633,9 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// GetProcessorInfo method
/// </summary>
protected void GetProcessorInfo() protected void GetProcessorInfo()
{ {
_firmware = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorFirmware.JoinNumber, JoinMap.ProcessorFirmware.AttributeName, eSigIoMask.InputSigOnly); _firmware = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorFirmware.JoinNumber, JoinMap.ProcessorFirmware.AttributeName, eSigIoMask.InputSigOnly);
@@ -499,7 +646,7 @@ namespace PepperDash.Essentials.Core.Fusion
{ {
var join = JoinMap.ProgramNameStart.JoinNumber + i; var join = JoinMap.ProgramNameStart.JoinNumber + i;
var progNum = i + 1; var progNum = i + 1;
_program[i] = FusionRoom.CreateOffsetStringSig((uint) join, _program[i] = FusionRoom.CreateOffsetStringSig((uint)join,
string.Format("{0} {1}", JoinMap.ProgramNameStart.AttributeName, progNum), eSigIoMask.InputSigOnly); string.Format("{0} {1}", JoinMap.ProgramNameStart.AttributeName, progNum), eSigIoMask.InputSigOnly);
} }
} }
@@ -507,6 +654,9 @@ namespace PepperDash.Essentials.Core.Fusion
_firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion; _firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion;
} }
/// <summary>
/// GetCustomProperties method
/// </summary>
protected void GetCustomProperties() protected void GetCustomProperties()
{ {
if (FusionRoom.IsOnline) if (FusionRoom.IsOnline)
@@ -524,11 +674,16 @@ namespace PepperDash.Essentials.Core.Fusion
// TODO: Get IP and Project Name from TP // TODO: Get IP and Project Name from TP
} }
/// <summary>
/// FusionRoom_OnlineStatusChange method
/// </summary>
/// <param name="currentDevice"></param>
/// <param name="args"></param>
protected void FusionRoom_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) protected void FusionRoom_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args)
{ {
if (args.DeviceOnLine) if (args.DeviceOnLine)
{ {
CrestronInvoke.BeginInvoke( (o) => CrestronInvoke.BeginInvoke((o) =>
{ {
CrestronEnvironment.Sleep(200); CrestronEnvironment.Sleep(200);
@@ -676,7 +831,7 @@ namespace PepperDash.Essentials.Core.Fusion
var extendTime = _currentMeeting.dtEnd - DateTime.Now; var extendTime = _currentMeeting.dtEnd - DateTime.Now;
var extendMinutesRaw = extendTime.TotalMinutes; var extendMinutesRaw = extendTime.TotalMinutes;
extendMinutes += (int) Math.Round(extendMinutesRaw); extendMinutes += (int)Math.Round(extendMinutesRaw);
} }
@@ -784,11 +939,11 @@ namespace PepperDash.Essentials.Core.Fusion
var parameters = actionResponse["Parameters"]; var parameters = actionResponse["Parameters"];
foreach (var isRegistered in from XmlElement parameter in parameters foreach (var isRegistered in from XmlElement parameter in parameters
where parameter.HasAttributes where parameter.HasAttributes
select parameter.Attributes select parameter.Attributes
into attributes into attributes
where attributes["ID"].Value == "Registered" where attributes["ID"].Value == "Registered"
select Int32.Parse(attributes["Value"].Value)) select Int32.Parse(attributes["Value"].Value))
{ {
switch (isRegistered) switch (isRegistered)
{ {
@@ -845,9 +1000,9 @@ namespace PepperDash.Essentials.Core.Fusion
Debug.LogMessage(LogEventLevel.Debug, this, "DateTime from Fusion Server: {0}", currentTime); Debug.LogMessage(LogEventLevel.Debug, this, "DateTime from Fusion Server: {0}", currentTime);
// Parse time and date from response and insert values // Parse time and date from response and insert values
CrestronEnvironment.SetTimeAndDate((ushort) currentTime.Hour, (ushort) currentTime.Minute, CrestronEnvironment.SetTimeAndDate((ushort)currentTime.Hour, (ushort)currentTime.Minute,
(ushort) currentTime.Second, (ushort) currentTime.Month, (ushort) currentTime.Day, (ushort)currentTime.Second, (ushort)currentTime.Month, (ushort)currentTime.Day,
(ushort) currentTime.Year); (ushort)currentTime.Year);
Debug.LogMessage(LogEventLevel.Debug, this, "Processor time set to {0}", CrestronEnvironment.GetLocalTime()); Debug.LogMessage(LogEventLevel.Debug, this, "Processor time set to {0}", CrestronEnvironment.GetLocalTime());
} }
@@ -1065,6 +1220,9 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// SetUpSources method
/// </summary>
protected virtual void SetUpSources() protected virtual void SetUpSources()
{ {
// Sources // Sources
@@ -1074,10 +1232,10 @@ namespace PepperDash.Essentials.Core.Fusion
// NEW PROCESS: // NEW PROCESS:
// Make these lists and insert the fusion attributes by iterating these // Make these lists and insert the fusion attributes by iterating these
var setTopBoxes = dict.Where(d => d.Value.SourceDevice is ISetTopBoxControls); var setTopBoxes = dict.Where(d => d.Value.SourceDevice is ISetTopBoxControls);
uint i = 1; uint i = 0;
foreach (var kvp in setTopBoxes) foreach (var kvp in setTopBoxes)
{ {
TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + i, JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++; i++;
if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots
{ {
@@ -1086,10 +1244,10 @@ namespace PepperDash.Essentials.Core.Fusion
} }
var discPlayers = dict.Where(d => d.Value.SourceDevice is IDiscPlayerControls); var discPlayers = dict.Where(d => d.Value.SourceDevice is IDiscPlayerControls);
i = 1; i = 0;
foreach (var kvp in discPlayers) foreach (var kvp in discPlayers)
{ {
TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + i, JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++; i++;
if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots
{ {
@@ -1098,10 +1256,10 @@ namespace PepperDash.Essentials.Core.Fusion
} }
var laptops = dict.Where(d => d.Value.SourceDevice is IRoutingSource); var laptops = dict.Where(d => d.Value.SourceDevice is IRoutingSource);
i = 1; i = 0;
foreach (var kvp in laptops) foreach (var kvp in laptops)
{ {
TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + i, JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice);
i++; i++;
if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots??? if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots???
{ {
@@ -1111,7 +1269,7 @@ namespace PepperDash.Essentials.Core.Fusion
foreach (var usageDevice in dict.Select(kvp => kvp.Value.SourceDevice).OfType<IUsageTracking>()) foreach (var usageDevice in dict.Select(kvp => kvp.Value.SourceDevice).OfType<IUsageTracking>())
{ {
usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) {UsageIsTracked = true}; usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) { UsageIsTracked = true };
usageDevice.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded; usageDevice.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded;
} }
} }
@@ -1157,17 +1315,31 @@ namespace PepperDash.Essentials.Core.Fusion
Debug.LogMessage(LogEventLevel.Debug, this, "Device usage string: {0}", deviceUsage); Debug.LogMessage(LogEventLevel.Debug, this, "Device usage string: {0}", deviceUsage);
} }
/// <summary>
/// Tries to add route action sigs for a source
/// </summary>
/// <param name="attrName"></param>
/// <param name="attrNum"></param>
/// <param name="routeKey"></param>
/// <param name="pSrc"></param>
protected void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc) protected void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc)
{ {
Debug.LogMessage(LogEventLevel.Verbose, this, "Creating attribute '{0}' with join {1} for source {2}", this.LogVerbose("Creating attribute '{0}' with join {1} for source {2}",
attrName, attrNum, pSrc.Key); attrName, attrNum, pSrc.Key);
try try
{ {
var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputOutputSig); var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputOutputSig);
// Need feedback when this source is selected // Need feedback when this source is selected
// Event handler, added below, will compare source changes with this sig dict // Event handler, added below, will compare source changes with this sig dict
_sourceToFeedbackSigs.Add(pSrc, sigD.InputSig); if (!_sourceToFeedbackSigs.ContainsKey(pSrc))
{
_sourceToFeedbackSigs.Add(pSrc, sigD.InputSig);
}
else
{
this.LogWarning("Source '{0}' already has a feedback sig mapped. Overwriting.", pSrc.Key);
_sourceToFeedbackSigs[pSrc] = sigD.InputSig;
}
// And respond to selection in Fusion // And respond to selection in Fusion
sigD.OutputSig.SetSigFalseAction(() => sigD.OutputSig.SetSigFalseAction(() =>
@@ -1180,14 +1352,12 @@ namespace PepperDash.Essentials.Core.Fusion
} }
catch (Exception) catch (Exception)
{ {
Debug.LogMessage(LogEventLevel.Verbose, this, "Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING", this.LogVerbose("Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING",
attrNum, attrName, pSrc.Key); attrNum, attrName, pSrc.Key);
} }
} }
/// <summary>
///
/// </summary>
private void SetUpCommunitcationMonitors() private void SetUpCommunitcationMonitors()
{ {
uint displayNum = 0; uint displayNum = 0;
@@ -1274,6 +1444,8 @@ namespace PepperDash.Essentials.Core.Fusion
if (attrName != null) if (attrName != null)
{ {
this.LogDebug("Linking communication monitor for device '{0}' to Fusion attribute '{1}' at join {2}",
dev.Key, attrName, attrNum);
// Link comm status to sig and update // Link comm status to sig and update
var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly); var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly);
var smd = dev as ICommunicationMonitor; var smd = dev as ICommunicationMonitor;
@@ -1285,6 +1457,9 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// SetUpDisplay method
/// </summary>
protected virtual void SetUpDisplay() protected virtual void SetUpDisplay()
{ {
try try
@@ -1297,7 +1472,7 @@ namespace PepperDash.Essentials.Core.Fusion
foreach (var display in displays.Cast<IDisplay>()) foreach (var display in displays.Cast<IDisplay>())
{ {
display.UsageTracker = new UsageTracking(display as Device) {UsageIsTracked = true}; display.UsageTracker = new UsageTracking(display as Device) { UsageIsTracked = true };
display.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded; display.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded;
} }
@@ -1410,7 +1585,7 @@ namespace PepperDash.Essentials.Core.Fusion
// Power on // Power on
var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint) joinOffset, displayName + "Power On", var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayName + "Power On",
eSigIoMask.InputOutputSig); eSigIoMask.InputOutputSig);
defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b => defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b =>
{ {
@@ -1421,7 +1596,7 @@ namespace PepperDash.Essentials.Core.Fusion
}); });
// Power Off // Power Off
var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 1, displayName + "Power Off", var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayName + "Power Off",
eSigIoMask.InputOutputSig); eSigIoMask.InputOutputSig);
defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b => defaultDisplayPowerOn.OutputSig.UserObject = new Action<bool>(b =>
{ {
@@ -1439,7 +1614,7 @@ namespace PepperDash.Essentials.Core.Fusion
} }
// Current Source // Current Source
var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 8, var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8,
displayName + "Source None", eSigIoMask.InputOutputSig); displayName + "Source None", eSigIoMask.InputOutputSig);
defaultDisplaySourceNone.OutputSig.UserObject = new Action<bool>(b => defaultDisplaySourceNone.OutputSig.UserObject = new Action<bool>(b =>
{ {
@@ -1507,7 +1682,7 @@ namespace PepperDash.Essentials.Core.Fusion
//if (Room.OccupancyObj != null) //if (Room.OccupancyObj != null)
//{ //{
var tempOccAsset = _guiDs.OccupancyAsset; var tempOccAsset = _guids.OccupancyAsset;
if (tempOccAsset == null) if (tempOccAsset == null)
{ {
@@ -1532,7 +1707,7 @@ namespace PepperDash.Essentials.Core.Fusion
occRoom.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; occRoom.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange;
} }
RoomOccupancyRemoteStringFeedback = new StringFeedback(() => _roomOccupancyRemoteString); RoomOccupancyRemoteStringFeedback = new StringFeedback(() => _roomOccupancyRemoteString);
RoomOccupancyRemoteStringFeedback.LinkInputSig(occSensorAsset.RoomOccupancyInfo.InputSig); RoomOccupancyRemoteStringFeedback.LinkInputSig(occSensorAsset.RoomOccupancyInfo.InputSig);
//} //}
@@ -1588,12 +1763,74 @@ namespace PepperDash.Essentials.Core.Fusion
} }
} }
/// <summary>
/// Event handler for Fusion state changes
/// </summary>
/// <param name="device"></param>
/// <param name="args"></param>
protected void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args) protected void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args)
{ {
if (args.EventId == FusionEventIds.HelpMessageReceivedEventId)
{
this.LogInformation( "Help message received from Fusion for room '{0}'",
Room.Name);
this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue);
// Fire help request event
HelpRequestResponseFeedback.FireUpdate();
if (!string.IsNullOrEmpty(FusionRoom.Help.OutputSig.StringValue))
{
switch (FusionRoom.Help.OutputSig.StringValue)
{
case "Please wait, a technician is on his / her way.":
// this.LogInformation("Please wait, a technician is on his / her way.",
// Room.Name);
_helpRequestStatus = eFusionHelpResponse.HelpOnTheWay;
break;
case "Please call the helpdesk.":
// this.LogInformation("Please call the helpdesk.");
// _helpRequestStatus = eFusionHelpResponse.CallHelpDesk;
break;
case "Please wait, I will reschedule your meeting to a different room.":
// this.LogInformation("Please wait, I will reschedule your meeting to a different room.",
// Room.Name);
_helpRequestStatus = eFusionHelpResponse.ReschedulingMeeting;
break;
case "I will be taking control of your system. Please be patient while I adjust the settings.":
// this.LogInformation("I will be taking control of your system. Please be patient while I adjust the settings.",
// Room.Name);
_helpRequestStatus = eFusionHelpResponse.TakingControl;
break;
default:
// this.LogInformation("Unknown help request code received from Fusion for room '{0}'",
// Room.Name);
_helpRequestStatus = eFusionHelpResponse.None;
break;
}
}
else
{
_helpRequestStatus = eFusionHelpResponse.None;
}
if(_helpRequestStatus == eFusionHelpResponse.None)
{
_helpRequestSent = false;
HelpRequestSentFeedback.FireUpdate();
}
HelpRequestStatusFeedback.FireUpdate();
}
// The sig/UO method: Need separate handlers for fixed and user sigs, all flavors, // The sig/UO method: Need separate handlers for fixed and user sigs, all flavors,
// even though they all contain sigs. // even though they all contain sigs.
BoolOutputSig outSig; BoolOutputSig outSig;
if (args.UserConfiguredSigDetail is BooleanSigDataFixedName sigData) if (args.UserConfiguredSigDetail is BooleanSigDataFixedName sigData)
{ {
@@ -1632,9 +1869,69 @@ namespace PepperDash.Essentials.Core.Fusion
(outSig.UserObject as Action<string>).Invoke(outSig.StringValue); (outSig.UserObject as Action<string>).Invoke(outSig.StringValue);
} }
} }
/// <inheritdoc />
public void SendHelpRequest()
{
var now = DateTime.Now;
var breakString = _config.UseHtmlFormatForHelpRequests ? "<BR>" : "\r\n";
var date = now.ToString("MMMM dd, yyyy");
var time = now.ToString("hh:mm tt");
if (_config.Use24HourTimeFormat)
{
time = now.ToString("HH:mm");
}
var requestString = $"HR00: {breakString} Assistance has been requested from room {Room.Name}{breakString}on {date} at {time}";
FusionRoom.Help.InputSig.StringValue = requestString;
this.LogInformation("Help request sent to Fusion from room '{0}'", Room.Name);
this.LogDebug("Help request content: {0}", FusionRoom.Help.InputSig.StringValue);
_helpRequestSent = true;
HelpRequestSentFeedback.FireUpdate();
_helpRequestStatus = eFusionHelpResponse.HelpRequested;
HelpRequestStatusFeedback.FireUpdate();
}
/// <inheritdoc />
public void CancelHelpRequest()
{
if (_helpRequestSent)
{
FusionRoom.Help.InputSig.StringValue = "";
_helpRequestSent = false;
HelpRequestSentFeedback.FireUpdate();
_helpRequestStatus = eFusionHelpResponse.None;
HelpRequestStatusFeedback.FireUpdate();
Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name);
}
}
/// <inheritdoc />
public void ToggleHelpRequest()
{
if (_helpRequestSent)
{
CancelHelpRequest();
}
else
{
SendHelpRequest();
}
}
} }
/// <summary>
/// Extensions to enhance Fusion room, asset and signal creation.
/// </summary>
public static class FusionRoomExtensions public static class FusionRoomExtensions
{ {
/// <summary> /// <summary>
@@ -1648,6 +1945,8 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
{ {
Debug.LogDebug("Creating Offset Bool Sig: {0} at Join {1}", name, number);
if (number < 50) if (number < 50)
{ {
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
@@ -1668,6 +1967,8 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
{ {
Debug.LogDebug("Creating Offset UShort Sig: {0} at Join {1}", name, number);
if (number < 50) if (number < 50)
{ {
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
@@ -1688,6 +1989,8 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask)
{ {
Debug.LogDebug("Creating Offset String Sig: {0} at Join {1}", name, number);
if (number < 50) if (number < 50)
{ {
throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); throw new ArgumentOutOfRangeException("number", "Cannot be less than 50");
@@ -1803,6 +2106,9 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public class RoomInformation public class RoomInformation
{ {
/// <summary>
/// Constructor
/// </summary>
public RoomInformation() public RoomInformation()
{ {
FusionCustomProperties = new List<FusionCustomProperty>(); FusionCustomProperties = new List<FusionCustomProperty>();
@@ -1855,10 +2161,17 @@ namespace PepperDash.Essentials.Core.Fusion
/// </summary> /// </summary>
public class FusionCustomProperty public class FusionCustomProperty
{ {
/// <summary>
/// Constructor
/// </summary>
public FusionCustomProperty() public FusionCustomProperty()
{ {
} }
/// <summary>
/// Constructor with id
/// </summary>
/// <param name="id"></param>
public FusionCustomProperty(string id) public FusionCustomProperty(string id)
{ {
ID = id; ID = id;

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Fusion;
/// <summary>
/// Factory for creating IEssentialsRoomFusionController devices
/// </summary>
public class IEssentialsRoomFusionControllerFactory : EssentialsDeviceFactory<IEssentialsRoomFusionController>
{
/// <summary>
/// Constructor
/// </summary>
public IEssentialsRoomFusionControllerFactory()
{
TypeNames = new List<string>() { "fusionRoom" };
}
/// <summary>
/// Builds the device
/// </summary>
/// <param name="dc"></param>
/// <returns></returns>
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
{
Debug.LogDebug("Factory Attempting to create new IEssentialsRoomFusionController Device");
var properties = dc.Properties.ToObject<IEssentialsRoomFusionControllerPropertiesConfig>();
return new IEssentialsRoomFusionController(dc.Key, dc.Name, properties);
}
}

View File

@@ -0,0 +1,59 @@
using Newtonsoft.Json;
using PepperDash.Core;
/// <summary>
/// Config properties for an IEssentialsRoomFusionController device
/// </summary>
public class IEssentialsRoomFusionControllerPropertiesConfig
{
/// <summary>
/// Gets or sets the IP ID of the Fusion Room Controller
/// </summary>
[JsonProperty("ipId")]
public string IpId { get; set; }
/// <summary>
/// Gets the IP ID as a UInt16
/// </summary>
[JsonIgnore]
public uint IpIdInt
{
get
{
// Try to parse the IpId string to UInt16 as hex
if (ushort.TryParse(IpId, System.Globalization.NumberStyles.HexNumber, null, out ushort result))
{
return result;
}
else
{
Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId);
return 0;
}
}
}
/// <summary>
/// Gets or sets the join map key
/// </summary>
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; }
/// <summary>
/// Gets or sets the room key associated with this Fusion Room Controller
/// </summary>
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
/// <summary>
/// Gets or sets whether to use HTML format for help requests
/// </summary>
[JsonProperty("useHtmlFormatForHelpRequests")]
public bool UseHtmlFormatForHelpRequests { get; set; } = false;
/// <summary>
/// Gets or sets whether to use 24-hour time format
/// </summary>
[JsonProperty("use24HourTimeFormat")]
public bool Use24HourTimeFormat { get; set; } = false;
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Core.Fusion
{
/// <summary>
/// Represents Fusion Help Request functionality
/// </summary>
public interface IFusionHelpRequest
{
/// <summary>
/// Feedback containing the response to a help request
/// </summary>
StringFeedback HelpRequestResponseFeedback { get; }
/// <summary>
/// Indicates whether a help request has been sent
/// </summary>
BoolFeedback HelpRequestSentFeedback { get; }
/// <summary>
/// Feedback containing the current status of the help request
/// </summary>
StringFeedback HelpRequestStatusFeedback { get; }
/// <summary>
/// Sends a help request
/// </summary>
void SendHelpRequest();
/// <summary>
/// Clears the current help request status
/// </summary>
void CancelHelpRequest();
/// <summary>
/// Toggles between sending and cancelling a help request
/// </summary>
void ToggleHelpRequest();
}
}

View File

@@ -0,0 +1,37 @@
namespace PepperDash.Essentials.Core.Fusion
{
/// <summary>
/// Enumeration of possible Fusion Help Responses based on the standard responses from Fusion
/// </summary>
public enum eFusionHelpResponse
{
/// <summary>
/// No help response
/// </summary>
None,
/// <summary>
/// Help has been requested
/// </summary>
HelpRequested,
/// <summary>
/// Help is on the way
/// </summary>
HelpOnTheWay,
/// <summary>
/// Please call the helpdesk.
/// </summary>
CallHelpDesk,
/// <summary>
/// Rescheduling meeting.
/// </summary>
ReschedulingMeeting,
/// <summary>
/// Technician taking control.
/// </summary>
TakingControl,
}
}

View File

@@ -49,7 +49,7 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
public IRoomOccupancy Room { get; private set; } public IRoomOccupancy Room { get; private set; }
private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom; private Fusion.IEssentialsRoomFusionController FusionRoom;
public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) : public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) :
base (config) base (config)
@@ -74,7 +74,7 @@ namespace PepperDash.Essentials.Core
var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion"; var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion";
FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase; FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.IEssentialsRoomFusionController;
if (FusionRoom == null) if (FusionRoom == null)
Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey); Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey);

View File

@@ -408,7 +408,7 @@ namespace PepperDash.Essentials.Core
Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes); Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes);
// If status provider is fusion, set flag to remote // If status provider is fusion, set flag to remote
if (statusProvider is Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase) if (statusProvider is Core.Fusion.IEssentialsRoomFusionController)
OccupancyStatusProviderIsRemote = true; OccupancyStatusProviderIsRemote = true;
if(timeoutMinutes > 0) if(timeoutMinutes > 0)

View File

@@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core.Routing
/// <param name="inputPort">The currently selected input port on the destination device.</param> /// <param name="inputPort">The currently selected input port on the destination device.</param>
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort) private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{ {
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key); Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this, destination?.Key, inputPort?.Key);
if(inputPort == null) if(inputPort == null)
{ {

View File

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

View File

@@ -6,9 +6,9 @@ using PepperDash.Core.Web.RequestHandlers;
namespace PepperDash.Essentials.Core.Web.RequestHandlers namespace PepperDash.Essentials.Core.Web.RequestHandlers
{ {
/// <summary> /// <summary>
/// Represents a SetDeviceStreamDebugRequestHandler /// Represents a SetDeviceStreamDebugRequestHandler
/// </summary> /// </summary>
public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler
{ {
/// <summary> /// <summary>
@@ -122,23 +122,23 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
return; return;
} }
if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device)) if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device))
{ {
context.Response.StatusCode = 404; context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found"; context.Response.StatusDescription = "Not Found";
context.Response.End(); context.Response.End();
return; return;
} }
eStreamDebuggingSetting debugSetting; eStreamDebuggingSetting debugSetting;
try try
{ {
debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true); debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Exception handling set debug request"); Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error"; context.Response.StatusDescription = "Internal Server Error";
context.Response.End(); context.Response.End();
@@ -164,7 +164,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogMessage(ex, "Exception handling set debug request"); Debug.LogMessage(ex, "Exception handling set debug request");
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error"; context.Response.StatusDescription = "Internal Server Error";
context.Response.End(); context.Response.End();
@@ -198,21 +198,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
public class SetDeviceStreamDebugConfig public class SetDeviceStreamDebugConfig
{ {
[JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)] [JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)]
/// <summary> /// <summary>
/// Gets or sets the DeviceKey /// Gets or sets the DeviceKey
/// </summary> /// </summary>
public string DeviceKey { get; set; } public string DeviceKey { get; set; }
[JsonProperty("setting", NullValueHandling = NullValueHandling.Include)] [JsonProperty("setting", NullValueHandling = NullValueHandling.Include)]
/// <summary> /// <summary>
/// Gets or sets the Setting /// Gets or sets the Setting
/// </summary> /// </summary>
public string Setting { get; set; } public string Setting { get; set; }
[JsonProperty("timeout")] [JsonProperty("timeout")]
/// <summary> /// <summary>
/// Gets or sets the Timeout /// Gets or sets the Timeout
/// </summary> /// </summary>
public int Timeout { get; set; } public int Timeout { get; set; }
public SetDeviceStreamDebugConfig() public SetDeviceStreamDebugConfig()

View File

@@ -1,8 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
@@ -17,48 +13,68 @@ namespace PepperDash.Essentials.Devices.Common
/// </summary> /// </summary>
public class GenericAudioOut : EssentialsDevice, IRoutingSink public class GenericAudioOut : EssentialsDevice, IRoutingSink
{ {
public RoutingInputPort CurrentInputPort => AnyAudioIn; /// <summary>
/// Gets the current input port
/// </summary>
public RoutingInputPort CurrentInputPort => AnyAudioIn;
public event SourceInfoChangeHandler CurrentSourceChange; /// <summary>
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; } /// <summary>
public SourceListItem CurrentSourceInfo /// Gets or sets the current source info key
{ /// </summary>
get public string CurrentSourceInfoKey { get; set; }
{ /// <summary>
return _CurrentSourceInfo; /// Gets or sets the current source info
} /// </summary>
set public SourceListItem CurrentSourceInfo
{ {
if (value == _CurrentSourceInfo) return; get
{
return _CurrentSourceInfo;
}
set
{
if (value == _CurrentSourceInfo) return;
var handler = CurrentSourceChange; var handler = CurrentSourceChange;
if (handler != null) if (handler != null)
handler(_CurrentSourceInfo, ChangeType.WillChange); handler(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value; _CurrentSourceInfo = value;
if (handler != null) if (handler != null)
handler(_CurrentSourceInfo, ChangeType.DidChange); handler(_CurrentSourceInfo, ChangeType.DidChange);
} }
} }
SourceListItem _CurrentSourceInfo; SourceListItem _CurrentSourceInfo;
/// <summary> /// <summary>
/// Gets or sets the AnyAudioIn /// Gets or sets the AnyAudioIn
/// </summary> /// </summary>
public RoutingInputPort AnyAudioIn { get; private set; } public RoutingInputPort AnyAudioIn { get; private set; }
/// <summary>
/// Constructor for GenericAudioOut
/// </summary>
/// <param name="key">Device key</param>
/// <param name="name">Device name</param>
public GenericAudioOut(string key, string name) public GenericAudioOut(string key, string name)
: base(key, name) : base(key, name)
{ {
AnyAudioIn = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio, AnyAudioIn = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio,
eRoutingPortConnectionType.LineAudio, null, this); eRoutingPortConnectionType.LineAudio, null, this);
} }
#region IRoutingInputs Members #region IRoutingInputs Members
/// <summary>
/// Gets the collection of input ports
/// </summary>
public RoutingPortCollection<RoutingInputPort> InputPorts public RoutingPortCollection<RoutingInputPort> InputPorts
{ {
get { return new RoutingPortCollection<RoutingInputPort> { AnyAudioIn }; } get { return new RoutingPortCollection<RoutingInputPort> { AnyAudioIn }; }
@@ -68,23 +84,32 @@ namespace PepperDash.Essentials.Devices.Common
} }
/// <summary> /// <summary>
/// Represents a GenericAudioOutWithVolume /// Represents a GenericAudioOutWithVolume
/// </summary> /// </summary>
public class GenericAudioOutWithVolume : GenericAudioOut, IHasVolumeDevice public class GenericAudioOutWithVolume : GenericAudioOut, IHasVolumeDevice
{ {
/// <summary>
/// Gets the volume device key
/// </summary>
public string VolumeDeviceKey { get; private set; } public string VolumeDeviceKey { get; private set; }
/// <summary>
/// Gets the volume zone
/// </summary>
public uint VolumeZone { get; private set; } public uint VolumeZone { get; private set; }
/// <summary>
/// Gets the volume device
/// </summary>
public IBasicVolumeControls VolumeDevice public IBasicVolumeControls VolumeDevice
{ {
get get
{ {
var dev = DeviceManager.GetDeviceForKey(VolumeDeviceKey); var dev = DeviceManager.GetDeviceForKey(VolumeDeviceKey);
if (dev is IAudioZones) if (dev is IAudioZones)
return (dev as IAudioZones).Zone[VolumeZone]; return (dev as IAudioZones).Zone[VolumeZone];
else return dev as IBasicVolumeControls; else return dev as IBasicVolumeControls;
} }
} }
/// <summary> /// <summary>
@@ -103,24 +128,30 @@ namespace PepperDash.Essentials.Devices.Common
} }
public class GenericAudioOutWithVolumeFactory : EssentialsDeviceFactory<GenericAudioOutWithVolume> /// <summary>
{ /// Factory for creating GenericAudioOutWithVolume devices
public GenericAudioOutWithVolumeFactory() /// </summary>
{ public class GenericAudioOutWithVolumeFactory : EssentialsDeviceFactory<GenericAudioOutWithVolume>
TypeNames = new List<string>() { "genericaudiooutwithvolume" }; {
} /// <summary>
/// Constructor for GenericAudioOutWithVolumeFactory
/// </summary>
public GenericAudioOutWithVolumeFactory()
{
TypeNames = new List<string>() { "genericaudiooutwithvolume" };
}
/// <summary> /// <summary>
/// BuildDevice method /// BuildDevice method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) public override EssentialsDevice BuildDevice(DeviceConfig dc)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new GenericAudioOutWithVolumeFactory Device"); Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new GenericAudioOutWithVolumeFactory Device");
var zone = dc.Properties.Value<uint>("zone"); var zone = dc.Properties.Value<uint>("zone");
return new GenericAudioOutWithVolume(dc.Key, dc.Name, return new GenericAudioOutWithVolume(dc.Key, dc.Name,
dc.Properties.Value<string>("volumeDeviceKey"), zone); dc.Properties.Value<string>("volumeDeviceKey"), zone);
} }
} }
} }

View File

@@ -1,18 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.Codec;
namespace PepperDash.Essentials.Devices.Common.AudioCodec namespace PepperDash.Essentials.Devices.Common.AudioCodec
{ {
/// <summary>
/// Abstract base class for audio codec devices
/// </summary>
public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTracking, IAudioCodecInfo public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTracking, IAudioCodecInfo
{ {
/// <summary>
/// Event fired when call status changes
/// </summary>
public event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange; public event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange;
/// <summary> /// <summary>
@@ -52,6 +54,11 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
/// </summary> /// </summary>
public List<CodecActiveCallItem> ActiveCalls { get; set; } public List<CodecActiveCallItem> ActiveCalls { get; set; }
/// <summary>
/// Constructor for AudioCodecBase
/// </summary>
/// <param name="key">Device key</param>
/// <param name="name">Device name</param>
public AudioCodecBase(string key, string name) public AudioCodecBase(string key, string name)
: base(key, name) : base(key, name)
{ {
@@ -70,11 +77,9 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
} }
/// <summary> /// <summary>
/// /// Handles call status change events
/// </summary> /// </summary>
/// <param name="previousStatus"></param> /// <param name="item">The call item that changed status</param>
/// <param name="newStatus"></param>
/// <param name="item"></param>
protected void OnCallStatusChange(CodecActiveCallItem item) protected void OnCallStatusChange(CodecActiveCallItem item)
{ {
var handler = CallStatusChange; var handler = CallStatusChange;
@@ -92,16 +97,22 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
#region IHasDialer Members #region IHasDialer Members
/// <inheritdoc />
public abstract void Dial(string number); public abstract void Dial(string number);
/// <inheritdoc />
public abstract void EndCall(CodecActiveCallItem activeCall); public abstract void EndCall(CodecActiveCallItem activeCall);
/// <inheritdoc />
public abstract void EndAllCalls(); public abstract void EndAllCalls();
/// <inheritdoc />
public abstract void AcceptCall(CodecActiveCallItem item); public abstract void AcceptCall(CodecActiveCallItem item);
/// <inheritdoc />
public abstract void RejectCall(CodecActiveCallItem item); public abstract void RejectCall(CodecActiveCallItem item);
/// <inheritdoc />
public abstract void SendDtmf(string digit); public abstract void SendDtmf(string digit);
#endregion #endregion

View File

@@ -1,16 +1,13 @@
using System; namespace PepperDash.Essentials.Devices.Common.AudioCodec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.AudioCodec
{ {
/// <summary> /// <summary>
/// Implements a common set of data about a codec /// Implements a common set of data about a codec
/// </summary> /// </summary>
public interface IAudioCodecInfo public interface IAudioCodecInfo
{ {
/// <summary>
/// Gets the codec information
/// </summary>
AudioCodecInfo CodecInfo { get; } AudioCodecInfo CodecInfo { get; }
} }
@@ -19,6 +16,9 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
/// </summary> /// </summary>
public abstract class AudioCodecInfo public abstract class AudioCodecInfo
{ {
/// <summary>
/// Gets or sets the phone number
/// </summary>
public abstract string PhoneNumber { get; set; } public abstract string PhoneNumber { get; set; }
} }
} }

View File

@@ -1,23 +1,17 @@
using System; using PepperDash.Essentials.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.AudioCodec namespace PepperDash.Essentials.Devices.Common.AudioCodec
{ {
/// <summary> /// <summary>
/// For rooms that have audio codec /// For rooms that have audio codec
/// </summary> /// </summary>
public interface IHasAudioCodec:IHasInCallFeedback public interface IHasAudioCodec : IHasInCallFeedback
{ {
/// <summary>
/// Gets the audio codec device
/// </summary>
AudioCodecBase AudioCodec { get; } AudioCodecBase AudioCodec { get; }
/// <summary>
/// Make this more specific
/// </summary>
//List<PepperDash.Essentials.Devices.Common.Codec.CodecActiveCallItem> ActiveCalls { get; } //List<PepperDash.Essentials.Devices.Common.Codec.CodecActiveCallItem> ActiveCalls { get; }
} }
} }

View File

@@ -1,8 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
@@ -17,6 +13,12 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
/// </summary> /// </summary>
public class MockAC : AudioCodecBase public class MockAC : AudioCodecBase
{ {
/// <summary>
/// Constructor for MockAC
/// </summary>
/// <param name="key">Device key</param>
/// <param name="name">Device name</param>
/// <param name="props">MockAC properties configuration</param>
public MockAC(string key, string name, MockAcPropertiesConfig props) public MockAC(string key, string name, MockAcPropertiesConfig props)
: base(key, name) : base(key, name)
{ {
@@ -109,13 +111,10 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
Debug.LogMessage(LogEventLevel.Debug, this, "BEEP BOOP SendDTMF: {0}", s); Debug.LogMessage(LogEventLevel.Debug, this, "BEEP BOOP SendDTMF: {0}", s);
} }
/// <summary>
///
/// </summary>
/// <param name="url"></param>
/// <summary> /// <summary>
/// TestIncomingAudioCall method /// TestIncomingAudioCall method
/// </summary> /// </summary>
/// <param name="number">Phone number to call from</param>
public void TestIncomingAudioCall(string number) public void TestIncomingAudioCall(string number)
{ {
Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingAudioCall from {0}", number); Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingAudioCall from {0}", number);
@@ -133,6 +132,7 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
{ {
string _phoneNumber; string _phoneNumber;
/// <inheritdoc />
public override string PhoneNumber public override string PhoneNumber
{ {
get get
@@ -151,6 +151,9 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
/// </summary> /// </summary>
public class MockACFactory : EssentialsDeviceFactory<MockAC> public class MockACFactory : EssentialsDeviceFactory<MockAC>
{ {
/// <summary>
/// Constructor for MockACFactory
/// </summary>
public MockACFactory() public MockACFactory()
{ {
TypeNames = new List<string>() { "mockac" }; TypeNames = new List<string>() { "mockac" };

View File

@@ -1,12 +1,4 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.AudioCodec namespace PepperDash.Essentials.Devices.Common.AudioCodec
{ {
@@ -15,10 +7,10 @@ namespace PepperDash.Essentials.Devices.Common.AudioCodec
/// </summary> /// </summary>
public class MockAcPropertiesConfig public class MockAcPropertiesConfig
{ {
[JsonProperty("phoneNumber")]
/// <summary> /// <summary>
/// Gets or sets the PhoneNumber /// Gets or sets the PhoneNumber
/// </summary> /// </summary>
[JsonProperty("phoneNumber")]
public string PhoneNumber { get; set; } public string PhoneNumber { get; set; }
} }
} }

View File

@@ -3,20 +3,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using System.Reflection;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Essentials.Core.Presets; using PepperDash.Essentials.Core.Presets;
using PepperDash.Essentials.Devices.Common.Codec;
using Newtonsoft.Json;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Cameras namespace PepperDash.Essentials.Devices.Common.Cameras
@@ -26,31 +20,52 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// </summary> /// </summary>
public enum eCameraCapabilities public enum eCameraCapabilities
{ {
/// <summary>
/// No camera capabilities
/// </summary>
None = 0, None = 0,
/// <summary>
/// Camera supports pan movement
/// </summary>
Pan = 1, Pan = 1,
Tilt = 2, /// <summary>
/// Camera supports tilt movement
/// </summary>
Tilt = 2,
/// <summary>
/// Camera supports zoom functionality
/// </summary>
Zoom = 4, Zoom = 4,
/// <summary>
/// Camera supports focus adjustment
/// </summary>
Focus = 8 Focus = 8
} }
/// <summary>
/// Abstract base class for camera devices that provides common camera functionality and capabilities
/// </summary>
public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs
{ {
[JsonProperty("controlMode", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the ControlMode /// Gets or sets the ControlMode
/// </summary> /// </summary>
[JsonProperty("controlMode", NullValueHandling = NullValueHandling.Ignore)]
public eCameraControlMode ControlMode { get; protected set; } public eCameraControlMode ControlMode { get; protected set; }
#region IRoutingOutputs Members #region IRoutingOutputs Members
[JsonIgnore]
/// <summary> /// <summary>
/// Gets or sets the OutputPorts /// Gets or sets the OutputPorts
/// </summary> /// </summary>
[JsonIgnore]
public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; protected set; } public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; protected set; }
#endregion #endregion
/// <summary>
/// Gets a value indicating whether this camera supports pan movement
/// </summary>
[JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)]
public bool CanPan public bool CanPan
{ {
@@ -59,6 +74,10 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
return (Capabilities & eCameraCapabilities.Pan) == eCameraCapabilities.Pan; return (Capabilities & eCameraCapabilities.Pan) == eCameraCapabilities.Pan;
} }
} }
/// <summary>
/// Gets a value indicating whether this camera supports tilt movement
/// </summary>
[JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)]
public bool CanTilt public bool CanTilt
{ {
@@ -67,6 +86,10 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
return (Capabilities & eCameraCapabilities.Tilt) == eCameraCapabilities.Tilt; return (Capabilities & eCameraCapabilities.Tilt) == eCameraCapabilities.Tilt;
} }
} }
/// <summary>
/// Gets a value indicating whether this camera supports zoom functionality
/// </summary>
[JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)]
public bool CanZoom public bool CanZoom
{ {
@@ -75,6 +98,10 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
return (Capabilities & eCameraCapabilities.Zoom) == eCameraCapabilities.Zoom; return (Capabilities & eCameraCapabilities.Zoom) == eCameraCapabilities.Zoom;
} }
} }
/// <summary>
/// Gets a value indicating whether this camera supports focus adjustment
/// </summary>
[JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)]
public bool CanFocus public bool CanFocus
{ {
@@ -84,23 +111,42 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
} }
} }
// A bitmasked value to indicate the movement capabilites of this camera /// <summary>
/// Gets or sets a bitmasked value to indicate the movement capabilities of this camera
/// </summary>
protected eCameraCapabilities Capabilities { get; set; } protected eCameraCapabilities Capabilities { get; set; }
protected CameraBase(DeviceConfig config) : base(config) /// <summary>
{ /// Initializes a new instance of the CameraBase class with the specified device configuration
OutputPorts = new RoutingPortCollection<RoutingOutputPort>(); /// </summary>
/// <param name="config">The device configuration</param>
ControlMode = eCameraControlMode.Manual; protected CameraBase(DeviceConfig config) : base(config)
}
protected CameraBase(string key, string name) :
this (new DeviceConfig{Name = name, Key = key})
{ {
OutputPorts = new RoutingPortCollection<RoutingOutputPort>();
ControlMode = eCameraControlMode.Manual;
} }
/// <summary>
/// Initializes a new instance of the CameraBase class with the specified key and name
/// </summary>
/// <param name="key">The unique key for this camera device</param>
/// <param name="name">The friendly name for this camera device</param>
protected CameraBase(string key, string name) :
this(new DeviceConfig { Name = name, Key = key })
{
}
/// <summary>
/// Links the camera device to the API bridge for control and feedback
/// </summary>
/// <param name="cameraDevice">The camera device to link</param>
/// <param name="trilist">The trilist for communication</param>
/// <param name="joinStart">The starting join number for the camera controls</param>
/// <param name="joinMapKey">The join map key for custom join mappings</param>
/// <param name="bridge">The EiscApiAdvanced bridge for advanced join mapping</param>
protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey, protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge) EiscApiAdvanced bridge)
{ {
@@ -240,13 +286,13 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
int tempNum = i; int tempNum = i;
trilist.SetSigTrueAction((ushort) (joinMap.PresetRecallStart.JoinNumber + tempNum), () => trilist.SetSigTrueAction((ushort)(joinMap.PresetRecallStart.JoinNumber + tempNum), () =>
{ {
presetsCamera.PresetSelect(tempNum); presetsCamera.PresetSelect(tempNum);
}); });
trilist.SetSigTrueAction((ushort) (joinMap.PresetSaveStart.JoinNumber + tempNum), () => trilist.SetSigTrueAction((ushort)(joinMap.PresetSaveStart.JoinNumber + tempNum), () =>
{ {
var label = trilist.GetString((ushort) (joinMap.PresetLabelStart.JoinNumber + tempNum)); var label = trilist.GetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum));
presetsCamera.PresetStore(tempNum, label); presetsCamera.PresetStore(tempNum, label);
}); });
@@ -277,7 +323,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
trilist.SetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum), label); trilist.SetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum), label);
} }
} }
} }
/// <summary> /// <summary>
@@ -285,6 +331,13 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// </summary> /// </summary>
public class CameraPreset : PresetBase public class CameraPreset : PresetBase
{ {
/// <summary>
/// Initializes a new instance of the CameraPreset class
/// </summary>
/// <param name="id">The preset ID</param>
/// <param name="description">The preset description</param>
/// <param name="isDefined">Whether the preset is defined</param>
/// <param name="isDefinable">Whether the preset can be defined</param>
public CameraPreset(int id, string description, bool isDefined, bool isDefinable) public CameraPreset(int id, string description, bool isDefined, bool isDefinable)
: base(id, description, isDefined, isDefinable) : base(id, description, isDefined, isDefinable)
{ {
@@ -293,37 +346,37 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
} }
/// <summary> /// <summary>
/// Represents a CameraPropertiesConfig /// Represents a CameraPropertiesConfig
/// </summary> /// </summary>
public class CameraPropertiesConfig public class CameraPropertiesConfig
{ {
/// <summary> /// <summary>
/// Gets or sets the CommunicationMonitorProperties /// Gets or sets the CommunicationMonitorProperties
/// </summary> /// </summary>
public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; } public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Control /// Gets or sets the Control
/// </summary> /// </summary>
public ControlPropertiesConfig Control { get; set; } public ControlPropertiesConfig Control { get; set; }
[JsonProperty("supportsAutoMode")]
/// <summary> /// <summary>
/// Gets or sets the SupportsAutoMode /// Gets or sets the SupportsAutoMode
/// </summary> /// </summary>
[JsonProperty("supportsAutoMode")]
public bool SupportsAutoMode { get; set; } public bool SupportsAutoMode { get; set; }
[JsonProperty("supportsOffMode")]
/// <summary> /// <summary>
/// Gets or sets the SupportsOffMode /// Gets or sets the SupportsOffMode
/// </summary> /// </summary>
[JsonProperty("supportsOffMode")]
public bool SupportsOffMode { get; set; } public bool SupportsOffMode { get; set; }
[JsonProperty("presets")]
/// <summary> /// <summary>
/// Gets or sets the Presets /// Gets or sets the Presets
/// </summary> /// </summary>
[JsonProperty("presets")]
public List<CameraPreset> Presets { get; set; } public List<CameraPreset> Presets { get; set; }
} }
} }

View File

@@ -3,38 +3,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Devices.Common.Codec;
using System.Text.RegularExpressions;
using System.Reflection;
using Newtonsoft.Json;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Cameras namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
/// <summary> /// <summary>
/// Represents a CameraVisca /// Represents a CameraVisca
/// </summary> /// </summary>
public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IHasPowerControlWithFeedback, IBridgeAdvanced, IHasCameraFocusControl, IHasAutoFocusMode public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IHasPowerControlWithFeedback, IBridgeAdvanced, IHasCameraFocusControl, IHasAutoFocusMode
{ {
private readonly CameraViscaPropertiesConfig PropertiesConfig; private readonly CameraViscaPropertiesConfig PropertiesConfig;
/// <summary> /// <summary>
/// Gets or sets the Communication /// Gets or sets the Communication
/// </summary> /// </summary>
public IBasicCommunication Communication { get; private set; } public IBasicCommunication Communication { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the CommunicationMonitor /// Gets or sets the CommunicationMonitor
/// </summary> /// </summary>
public StatusMonitorBase CommunicationMonitor { get; private set; } public StatusMonitorBase CommunicationMonitor { get; private set; }
/// <summary> /// <summary>
/// Used to store the actions to parse inquiry responses as the inquiries are sent /// Used to store the actions to parse inquiry responses as the inquiries are sent
@@ -45,20 +40,41 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// Camera ID (Default 1) /// Camera ID (Default 1)
/// </summary> /// </summary>
public byte ID = 0x01; public byte ID = 0x01;
/// <summary>
/// Response ID used for VISCA communication
/// </summary>
public byte ResponseID; public byte ResponseID;
/// <summary>
/// Slow speed value for pan movement
/// </summary>
public byte PanSpeedSlow = 0x10;
public byte PanSpeedSlow = 0x10; /// <summary>
public byte TiltSpeedSlow = 0x10; /// Slow speed value for tilt movement
/// </summary>
public byte TiltSpeedSlow = 0x10;
/// <summary>
/// Fast speed value for pan movement
/// </summary>
public byte PanSpeedFast = 0x13; public byte PanSpeedFast = 0x13;
/// <summary>
/// Fast speed value for tilt movement
/// </summary>
public byte TiltSpeedFast = 0x13; public byte TiltSpeedFast = 0x13;
// private bool IsMoving; // private bool IsMoving;
private bool IsZooming; private bool IsZooming;
bool _powerIsOn; bool _powerIsOn;
public bool PowerIsOn
/// <summary>
/// Gets or sets a value indicating whether the camera power is on
/// </summary>
public bool PowerIsOn
{ {
get get
{ {
@@ -87,12 +103,23 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
long FastSpeedHoldTimeMs = 2000; long FastSpeedHoldTimeMs = 2000;
byte[] IncomingBuffer = new byte[] { }; byte[] IncomingBuffer = new byte[] { };
public BoolFeedback PowerIsOnFeedback { get; private set; }
/// <summary>
/// Feedback indicating whether the camera power is on
/// </summary>
public BoolFeedback PowerIsOnFeedback { get; private set; }
/// <summary>
/// Initializes a new instance of the CameraVisca class
/// </summary>
/// <param name="key">The unique key for this camera device</param>
/// <param name="name">The friendly name for this camera device</param>
/// <param name="comm">The communication interface for VISCA protocol</param>
/// <param name="props">The camera properties configuration</param>
public CameraVisca(string key, string name, IBasicCommunication comm, CameraViscaPropertiesConfig props) : public CameraVisca(string key, string name, IBasicCommunication comm, CameraViscaPropertiesConfig props) :
base(key, name) base(key, name)
{ {
InquiryResponseQueue = new CrestronQueue<Action<byte[]>>(15); InquiryResponseQueue = new CrestronQueue<Action<byte[]>>(15);
Presets = props.Presets; Presets = props.Presets;
@@ -107,8 +134,8 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
OutputPorts.Add(new RoutingOutputPort("videoOut", eRoutingSignalType.Video, eRoutingPortConnectionType.None, null, this, true)); OutputPorts.Add(new RoutingOutputPort("videoOut", eRoutingSignalType.Video, eRoutingPortConnectionType.None, null, this, true));
// Default to all capabilties // Default to all capabilties
Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus; Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus;
Communication = comm; Communication = comm;
if (comm is ISocketStatus socket) if (comm is ISocketStatus socket)
{ {
@@ -121,19 +148,19 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
} }
Communication.BytesReceived += new EventHandler<GenericCommMethodReceiveBytesArgs>(Communication_BytesReceived); Communication.BytesReceived += new EventHandler<GenericCommMethodReceiveBytesArgs>(Communication_BytesReceived);
PowerIsOnFeedback = new BoolFeedback(() => { return PowerIsOn; }); PowerIsOnFeedback = new BoolFeedback("powerIsOn", () => { return PowerIsOn; });
CameraIsOffFeedback = new BoolFeedback(() => { return !PowerIsOn; }); CameraIsOffFeedback = new BoolFeedback("cameraIsOff", () => { return !PowerIsOn; });
if (props.CommunicationMonitorProperties != null) if (props.CommunicationMonitorProperties != null)
{ {
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties);
} }
else else
{ {
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 20000, 120000, 300000, "\x81\x09\x04\x00\xFF"); CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 20000, 120000, 300000, "\x81\x09\x04\x00\xFF");
} }
DeviceManager.AddDevice(CommunicationMonitor); DeviceManager.AddDevice(CommunicationMonitor);
} }
/// <summary> /// <summary>
@@ -165,57 +192,57 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
} }
} }
/// <summary> /// <summary>
/// CustomActivate method /// CustomActivate method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override bool CustomActivate() public override bool CustomActivate()
{ {
Communication.Connect(); Communication.Connect();
CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
CommunicationMonitor.Start();
CrestronConsole.AddNewConsoleCommand(s => Communication.Connect(), "con" + Key, "", ConsoleAccessLevelEnum.AccessOperator); CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
return true; CommunicationMonitor.Start();
}
/// <summary>
/// LinkToApi method
/// </summary>
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge);
}
void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) CrestronConsole.AddNewConsoleCommand(s => Communication.Connect(), "con" + Key, "", ConsoleAccessLevelEnum.AccessOperator);
{ return true;
Debug.LogMessage(LogEventLevel.Verbose, this, "Socket Status Change: {0}", e.Client.ClientStatus.ToString()); }
if (e.Client.IsConnected) /// <summary>
{ /// LinkToApi method
/// </summary>
} public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
else {
{ LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge);
}
} void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e)
} {
Debug.LogMessage(LogEventLevel.Verbose, this, "Socket Status Change: {0}", e.Client.ClientStatus.ToString());
void SendBytes(byte[] b) if (e.Client.IsConnected)
{ {
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.LogMessage(LogEventLevel.Verbose, this, "Sending:{0}", ComTextHelper.GetEscapedText(b));
Communication.SendBytes(b); }
} else
{
void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) }
{ }
void SendBytes(byte[] b)
{
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.LogMessage(LogEventLevel.Verbose, this, "Sending:{0}", ComTextHelper.GetEscapedText(b));
Communication.SendBytes(b);
}
void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
{
var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length]; var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
try try
@@ -355,10 +382,10 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// <summary> /// <summary>
/// Sends a pan/tilt command. If the command is not for fastSpeed then it starts a timer to initiate fast speed. /// Sends a pan/tilt command. If the command is not for fastSpeed then it starts a timer to initiate fast speed.
/// </summary> /// </summary>
/// <param name="cmd"></param> /// <param name="cmd">The VISCA command to send</param>
/// <param name="fastSpeed"></param> /// <param name="fastSpeedEnabled">Whether fast speed is enabled for this command</param>
private void SendPanTiltCommand (byte[] cmd, bool fastSpeedEnabled) private void SendPanTiltCommand(byte[] cmd, bool fastSpeedEnabled)
{ {
SendBytes(GetPanTiltCommand(cmd, fastSpeedEnabled)); SendBytes(GetPanTiltCommand(cmd, fastSpeedEnabled));
if (!fastSpeedEnabled) if (!fastSpeedEnabled)
@@ -372,7 +399,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SpeedTimer = new CTimer((o) => SendPanTiltCommand(GetPanTiltCommand(cmd, true), true), FastSpeedHoldTimeMs); SpeedTimer = new CTimer((o) => SendPanTiltCommand(GetPanTiltCommand(cmd, true), true), FastSpeedHoldTimeMs);
} }
} }
private void StopSpeedTimer() private void StopSpeedTimer()
{ {
@@ -381,7 +408,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SpeedTimer.Stop(); SpeedTimer.Stop();
SpeedTimer.Dispose(); SpeedTimer.Dispose();
SpeedTimer = null; SpeedTimer = null;
} }
} }
/// <summary> /// <summary>
@@ -424,14 +451,14 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
InquiryResponseQueue.Enqueue(HandlePowerResponse); InquiryResponseQueue.Enqueue(HandlePowerResponse);
} }
/// <summary> /// <summary>
/// PowerOn method /// PowerOn method
/// </summary> /// </summary>
public void PowerOn() public void PowerOn()
{ {
SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x02, 0xFF }); SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x02, 0xFF });
SendPowerQuery(); SendPowerQuery();
} }
void HandlePowerResponse(byte[] response) void HandlePowerResponse(byte[] response)
{ {
@@ -450,12 +477,12 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
} }
} }
/// <summary> /// <summary>
/// PowerOff method /// PowerOff method
/// </summary> /// </summary>
public void PowerOff() public void PowerOff()
{ {
SendBytes(new byte[] {ID, 0x01, 0x04, 0x00, 0x03, 0xFF}); SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x03, 0xFF });
SendPowerQuery(); SendPowerQuery();
} }
@@ -470,22 +497,22 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
PowerOn(); PowerOn();
} }
/// <summary> /// <summary>
/// PanLeft method /// PanLeft method
/// </summary> /// </summary>
public void PanLeft() public void PanLeft()
{ {
SendPanTiltCommand(new byte[] {0x01, 0x03}, false); SendPanTiltCommand(new byte[] { 0x01, 0x03 }, false);
// IsMoving = true; // IsMoving = true;
} }
/// <summary> /// <summary>
/// PanRight method /// PanRight method
/// </summary> /// </summary>
public void PanRight() public void PanRight()
{ {
SendPanTiltCommand(new byte[] { 0x02, 0x03 }, false); SendPanTiltCommand(new byte[] { 0x02, 0x03 }, false);
// IsMoving = true; // IsMoving = true;
} }
/// <summary> /// <summary>
/// PanStop method /// PanStop method
/// </summary> /// </summary>
@@ -493,22 +520,22 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
Stop(); Stop();
} }
/// <summary> /// <summary>
/// TiltDown method /// TiltDown method
/// </summary> /// </summary>
public void TiltDown() public void TiltDown()
{ {
SendPanTiltCommand(new byte[] { 0x03, 0x02 }, false); SendPanTiltCommand(new byte[] { 0x03, 0x02 }, false);
// IsMoving = true; // IsMoving = true;
} }
/// <summary> /// <summary>
/// TiltUp method /// TiltUp method
/// </summary> /// </summary>
public void TiltUp() public void TiltUp()
{ {
SendPanTiltCommand(new byte[] { 0x03, 0x01 }, false); SendPanTiltCommand(new byte[] { 0x03, 0x01 }, false);
// IsMoving = true; // IsMoving = true;
} }
/// <summary> /// <summary>
/// TiltStop method /// TiltStop method
/// </summary> /// </summary>
@@ -517,28 +544,28 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
Stop(); Stop();
} }
private void SendZoomCommand (byte cmd) private void SendZoomCommand(byte cmd)
{ {
SendBytes(new byte[] {ID, 0x01, 0x04, 0x07, cmd, 0xFF} ); SendBytes(new byte[] { ID, 0x01, 0x04, 0x07, cmd, 0xFF });
} }
/// <summary> /// <summary>
/// ZoomIn method /// ZoomIn method
/// </summary> /// </summary>
public void ZoomIn() public void ZoomIn()
{ {
SendZoomCommand(ZoomInCmd); SendZoomCommand(ZoomInCmd);
IsZooming = true; IsZooming = true;
} }
/// <summary> /// <summary>
/// ZoomOut method /// ZoomOut method
/// </summary> /// </summary>
public void ZoomOut() public void ZoomOut()
{ {
SendZoomCommand(ZoomOutCmd); SendZoomCommand(ZoomOutCmd);
IsZooming = true; IsZooming = true;
} }
/// <summary> /// <summary>
/// ZoomStop method /// ZoomStop method
/// </summary> /// </summary>
@@ -547,23 +574,23 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
Stop(); Stop();
} }
/// <summary> /// <summary>
/// Stop method /// Stop method
/// </summary> /// </summary>
public void Stop() public void Stop()
{ {
if (IsZooming) if (IsZooming)
{ {
SendZoomCommand(ZoomStopCmd); SendZoomCommand(ZoomStopCmd);
IsZooming = false; IsZooming = false;
} }
else else
{ {
StopSpeedTimer(); StopSpeedTimer();
SendPanTiltCommand(new byte[] { 0x03, 0x03 }, false); SendPanTiltCommand(new byte[] { 0x03, 0x03 }, false);
// IsMoving = false; // IsMoving = false;
} }
} }
/// <summary> /// <summary>
/// PositionHome method /// PositionHome method
/// </summary> /// </summary>
@@ -572,33 +599,39 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SendBytes(new byte[] { ID, 0x01, 0x06, 0x02, PanSpeedFast, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF }); SendBytes(new byte[] { ID, 0x01, 0x06, 0x02, PanSpeedFast, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF });
SendBytes(new byte[] { ID, 0x01, 0x04, 0x47, 0x00, 0x00, 0x00, 0x00, 0xFF }); SendBytes(new byte[] { ID, 0x01, 0x04, 0x47, 0x00, 0x00, 0x00, 0x00, 0xFF });
} }
/// <summary> /// <summary>
/// RecallPreset method /// RecallPreset method
/// </summary> /// </summary>
public void RecallPreset(int presetNumber) public void RecallPreset(int presetNumber)
{ {
SendBytes(new byte[] {ID, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF} ); SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF });
} }
/// <summary> /// <summary>
/// SavePreset method /// SavePreset method
/// </summary> /// </summary>
public void SavePreset(int presetNumber) public void SavePreset(int presetNumber)
{ {
SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF }); SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF });
} }
#region IHasCameraPresets Members #region IHasCameraPresets Members
/// <summary>
/// Event that is raised when the presets list has changed
/// </summary>
public event EventHandler<EventArgs> PresetsListHasChanged; public event EventHandler<EventArgs> PresetsListHasChanged;
protected void OnPresetsListHasChanged() /// <summary>
{ /// Raises the PresetsListHasChanged event
var handler = PresetsListHasChanged; /// </summary>
if (handler == null) protected void OnPresetsListHasChanged()
return; {
var handler = PresetsListHasChanged;
if (handler == null)
return;
handler.Invoke(this, EventArgs.Empty); handler.Invoke(this, EventArgs.Empty);
} }
/// <summary> /// <summary>
/// Gets or sets the Presets /// Gets or sets the Presets
@@ -741,6 +774,9 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// </summary> /// </summary>
public class CameraViscaFactory : EssentialsDeviceFactory<CameraVisca> public class CameraViscaFactory : EssentialsDeviceFactory<CameraVisca>
{ {
/// <summary>
/// Initializes a new instance of the CameraViscaFactory class
/// </summary>
public CameraViscaFactory() public CameraViscaFactory()
{ {
TypeNames = new List<string>() { "cameravisca" }; TypeNames = new List<string>() { "cameravisca" };
@@ -768,11 +804,9 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
/// <summary> /// <summary>
/// Control ID of the camera (1-7) /// Control ID of the camera (1-7)
/// </summary>
[JsonProperty("id")]
/// <summary>
/// Gets or sets the Id /// Gets or sets the Id
/// </summary> /// </summary>
[JsonProperty("id")]
public uint Id { get; set; } public uint Id { get; set; }
/// <summary> /// <summary>
@@ -790,7 +824,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// <summary> /// <summary>
/// Slow tilt speed (0-18) /// Slow tilt speed (0-18)
/// </summary> /// </summary>
[JsonProperty("tiltSpeedSlow")] [JsonProperty("tiltSpeedSlow")]
public uint TiltSpeedSlow { get; set; } public uint TiltSpeedSlow { get; set; }
/// <summary> /// <summary>

View File

@@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Cameras namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
@@ -11,12 +8,27 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// </summary> /// </summary>
public interface IHasCameraPresets public interface IHasCameraPresets
{ {
/// <summary>
/// Event that is raised when the presets list has changed
/// </summary>
event EventHandler<EventArgs> PresetsListHasChanged; event EventHandler<EventArgs> PresetsListHasChanged;
/// <summary>
/// Gets the list of camera presets
/// </summary>
List<CameraPreset> Presets { get; } List<CameraPreset> Presets { get; }
/// <summary>
/// Selects the specified preset
/// </summary>
/// <param name="preset">The preset number to select</param>
void PresetSelect(int preset); void PresetSelect(int preset);
/// <summary>
/// Stores a preset at the specified location with the given description
/// </summary>
/// <param name="preset">The preset number to store</param>
/// <param name="description">The description for the preset</param>
void PresetStore(int preset, string description); void PresetStore(int preset, string description);
} }
} }

View File

@@ -0,0 +1,26 @@
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a Call
/// </summary>
public class Call
{
/// <summary>
/// Gets or sets the Number
/// </summary>
public string Number { get; set; }
/// <summary>
/// Gets or sets the Protocol
/// </summary>
public string Protocol { get; set; }
/// <summary>
/// Gets or sets the CallRate
/// </summary>
public string CallRate { get; set; }
/// <summary>
/// Gets or sets the CallType
/// </summary>
public string CallType { get; set; }
}
}

View File

@@ -1,10 +1,5 @@
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
{ {

View File

@@ -1,10 +1,5 @@
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
{ {

View File

@@ -1,10 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@@ -17,55 +13,55 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public class CodecActiveCallItem public class CodecActiveCallItem
{ {
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the Name /// Gets or sets the Name
/// </summary> /// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the Number /// Gets or sets the Number
/// </summary> /// </summary>
[JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)]
public string Number { get; set; } public string Number { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary> /// <summary>
/// Gets or sets the Type /// Gets or sets the Type
/// </summary> /// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eCodecCallType Type { get; set; } public eCodecCallType Type { get; set; }
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary> /// <summary>
/// Gets or sets the Status /// Gets or sets the Status
/// </summary> /// </summary>
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eCodecCallStatus Status { get; set; } public eCodecCallStatus Status { get; set; }
[JsonProperty("direction", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary> /// <summary>
/// Gets or sets the Direction /// Gets or sets the Direction
/// </summary> /// </summary>
[JsonProperty("direction", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public eCodecCallDirection Direction { get; set; } public eCodecCallDirection Direction { get; set; }
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the Id /// Gets or sets the Id
/// </summary> /// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("isOnHold", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the IsOnHold /// Gets or sets the IsOnHold
/// </summary> /// </summary>
[JsonProperty("isOnHold", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOnHold { get; set; } public bool IsOnHold { get; set; }
[JsonProperty("duration", NullValueHandling = NullValueHandling.Ignore)]
/// <summary> /// <summary>
/// Gets or sets the Duration /// Gets or sets the Duration
/// </summary> /// </summary>
[JsonProperty("duration", NullValueHandling = NullValueHandling.Ignore)]
public TimeSpan Duration { get; set; } public TimeSpan Duration { get; set; }
//public object CallMetaData { get; set; } //public object CallMetaData { get; set; }
@@ -81,7 +77,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec
{ {
return !(Status == eCodecCallStatus.Disconnected return !(Status == eCodecCallStatus.Disconnected
|| Status == eCodecCallStatus.Disconnecting || Status == eCodecCallStatus.Disconnecting
|| Status == eCodecCallStatus.Idle || Status == eCodecCallStatus.Idle
|| Status == eCodecCallStatus.Unknown); || Status == eCodecCallStatus.Unknown);
} }
} }
@@ -97,6 +93,10 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public CodecActiveCallItem CallItem { get; private set; } public CodecActiveCallItem CallItem { get; private set; }
/// <summary>
/// Initializes a new instance of the CodecCallStatusItemChangeEventArgs class
/// </summary>
/// <param name="item">The call item that changed</param>
public CodecCallStatusItemChangeEventArgs(CodecActiveCallItem item) public CodecCallStatusItemChangeEventArgs(CodecActiveCallItem item)
{ {
CallItem = item; CallItem = item;

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a codec directory
/// </summary>
public class CodecDirectory
{
/// <summary>
/// Represents the contents of the directory
/// We don't want to serialize this for messages to MobileControl. MC can combine Contacts and Folders to get the same data
/// </summary>
[JsonIgnore]
public List<DirectoryItem> CurrentDirectoryResults { get; private set; }
/// <summary>
/// Gets the Contacts in the CurrentDirectoryResults
/// </summary>
[JsonProperty("contacts")]
public List<DirectoryItem> Contacts
{
get
{
return CurrentDirectoryResults.OfType<DirectoryContact>().Cast<DirectoryItem>().ToList();
}
}
/// <summary>
/// Gets the Folders in the CurrentDirectoryResults
/// </summary>
[JsonProperty("folders")]
public List<DirectoryItem> Folders
{
get
{
return CurrentDirectoryResults.OfType<DirectoryFolder>().Cast<DirectoryItem>().ToList();
}
}
/// <summary>
/// Used to store the ID of the current folder for CurrentDirectoryResults
/// Gets or sets the ResultsFolderId
/// </summary>
[JsonProperty("resultsFolderId")]
public string ResultsFolderId { get; set; }
/// <summary>
/// Constructor for <see cref="CodecDirectory"/>
/// </summary>
public CodecDirectory()
{
CurrentDirectoryResults = new List<DirectoryItem>();
}
/// <summary>
/// Adds folders to the directory
/// </summary>
/// <param name="folders"></param>
public void AddFoldersToDirectory(List<DirectoryItem> folders)
{
if (folders != null)
CurrentDirectoryResults.AddRange(folders);
SortDirectory();
}
/// <summary>
/// Adds contacts to the directory
/// </summary>
/// <param name="contacts"></param>
public void AddContactsToDirectory(List<DirectoryItem> contacts)
{
if (contacts != null)
CurrentDirectoryResults.AddRange(contacts);
SortDirectory();
}
/// <summary>
/// Filters the CurrentDirectoryResults by the predicate
/// </summary>
/// <param name="predicate"></param>
public void FilterContacts(Func<DirectoryItem, bool> predicate)
{
CurrentDirectoryResults = CurrentDirectoryResults.Where(predicate).ToList();
}
/// <summary>
/// Sorts the DirectoryResults list to display all folders alphabetically, then all contacts alphabetically
/// </summary>
private void SortDirectory()
{
var sortedFolders = new List<DirectoryItem>();
sortedFolders.AddRange(CurrentDirectoryResults.Where(f => f is DirectoryFolder));
sortedFolders.OrderBy(f => f.Name);
var sortedContacts = new List<DirectoryItem>();
sortedContacts.AddRange(CurrentDirectoryResults.Where(c => c is DirectoryContact));
sortedFolders.OrderBy(c => c.Name);
CurrentDirectoryResults.Clear();
CurrentDirectoryResults.AddRange(sortedFolders);
CurrentDirectoryResults.AddRange(sortedContacts);
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a CodecScheduleAwareness
/// </summary>
public class CodecScheduleAwareness
{
List<Meeting> _meetings;
/// <summary>
/// Event that is raised when a meeting event changes
/// </summary>
public event EventHandler<MeetingEventArgs> MeetingEventChange;
/// <summary>
/// Event that is raised when the meetings list has changed
/// </summary>
public event EventHandler<EventArgs> MeetingsListHasChanged;
private int _meetingWarningMinutes = 5;
//private Meeting _previousChangedMeeting;
//private eMeetingEventChangeType _previousChangeType = eMeetingEventChangeType.Unknown;
/// <summary>
/// Gets or sets the number of minutes before a meeting to issue a warning
/// </summary>
public int MeetingWarningMinutes
{
get { return _meetingWarningMinutes; }
set { _meetingWarningMinutes = value; }
}
/// <summary>
/// Setter triggers MeetingsListHasChanged event
/// </summary>
public List<Meeting> Meetings
{
get
{
return _meetings;
}
set
{
_meetings = value;
MeetingsListHasChanged?.Invoke(this, new EventArgs());
}
}
private readonly CTimer _scheduleChecker;
/// <summary>
/// Initializes a new instance of the CodecScheduleAwareness class with default poll time
/// </summary>
public CodecScheduleAwareness()
{
Meetings = new List<Meeting>();
_scheduleChecker = new CTimer(CheckSchedule, null, 1000, 1000);
}
/// <summary>
/// Initializes a new instance of the CodecScheduleAwareness class with specified poll time
/// </summary>
/// <param name="pollTime">The poll time in milliseconds for checking schedule changes</param>
public CodecScheduleAwareness(long pollTime)
{
Meetings = new List<Meeting>();
_scheduleChecker = new CTimer(CheckSchedule, null, pollTime, pollTime);
}
/// <summary>
/// Helper method to fire MeetingEventChange. Should only fire once for each changeType on each meeting
/// </summary>
/// <param name="changeType"></param>
/// <param name="meeting"></param>
private void OnMeetingChange(eMeetingEventChangeType changeType, Meeting meeting)
{
Debug.LogMessage(LogEventLevel.Verbose, "*****************OnMeetingChange. id: {0} changeType: {1}**********************", meeting.Id, changeType);
if (changeType != (changeType & meeting.NotifiedChangeTypes))
{
// Add this change type to the NotifiedChangeTypes
meeting.NotifiedChangeTypes |= changeType;
MeetingEventChange?.Invoke(this, new MeetingEventArgs() { ChangeType = changeType, Meeting = meeting });
}
else
{
Debug.LogMessage(LogEventLevel.Verbose, "Meeting: {0} already notified of changeType: {1}", meeting.Id, changeType);
}
}
/// <summary>
/// Checks the schedule to see if any MeetingEventChange updates should be fired
/// </summary>
/// <param name="o"></param>
private void CheckSchedule(object o)
{
// Iterate the meeting list and check if any meeting need to do anything
const double meetingTimeEpsilon = 0.05;
foreach (var m in Meetings)
{
var changeType = eMeetingEventChangeType.Unknown;
if (eMeetingEventChangeType.MeetingStartWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStartWarning) && m.TimeToMeetingStart.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingStart.Seconds > 0) // Meeting is about to start
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingStartWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingStart.TotalMinutes, m.TimeToMeetingStart.Seconds);
changeType = eMeetingEventChangeType.MeetingStartWarning;
}
else if (eMeetingEventChangeType.MeetingStart != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStart) && Math.Abs(m.TimeToMeetingStart.TotalMinutes) < meetingTimeEpsilon) // Meeting Start
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingStart");
changeType = eMeetingEventChangeType.MeetingStart;
}
else if (eMeetingEventChangeType.MeetingEndWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEndWarning) && m.TimeToMeetingEnd.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingEnd.Seconds > 0) // Meeting is about to end
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingEndWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingEnd.TotalMinutes, m.TimeToMeetingEnd.Seconds);
changeType = eMeetingEventChangeType.MeetingEndWarning;
}
else if (eMeetingEventChangeType.MeetingEnd != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEnd) && Math.Abs(m.TimeToMeetingEnd.TotalMinutes) < meetingTimeEpsilon) // Meeting has ended
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingEnd");
changeType = eMeetingEventChangeType.MeetingEnd;
}
if (changeType != eMeetingEventChangeType.Unknown)
{
OnMeetingChange(changeType, m);
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a ContactMethod
/// </summary>
public class ContactMethod
{
/// <summary>
/// Gets or sets the ContactMethodId
/// </summary>
[JsonProperty("contactMethodId")]
public string ContactMethodId { get; set; }
/// <summary>
/// Gets or sets the Number
/// </summary>
[JsonProperty("number")]
public string Number { get; set; }
/// <summary>
/// Gets or sets the Device
/// </summary>
[JsonProperty("device")]
[JsonConverter(typeof(StringEnumConverter))]
public eContactMethodDevice Device { get; set; }
/// <summary>
/// Gets or sets the CallType
/// </summary>
[JsonProperty("callType")]
[JsonConverter(typeof(StringEnumConverter))]
public eContactMethodCallType CallType { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a DirectoryContact
/// </summary>
public class DirectoryContact : DirectoryItem
{
/// <summary>
/// Gets or sets the ContactId
/// </summary>
[JsonProperty("contactId")]
public string ContactId { get; set; }
/// <summary>
/// Gets or sets the Title
/// </summary>
[JsonProperty("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets the ContactMethods
/// </summary>
[JsonProperty("contactMethods")]
public List<ContactMethod> ContactMethods { get; set; }
/// <summary>
/// Constructor for <see cref="DirectoryContact"/>
/// </summary>
public DirectoryContact()
{
ContactMethods = new List<ContactMethod>();
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a DirectoryEventArgs
/// </summary>
public class DirectoryEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the Directory
/// </summary>
public CodecDirectory Directory { get; set; }
/// <summary>
/// Gets or sets the DirectoryIsOnRoot
/// </summary>
public bool DirectoryIsOnRoot { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a DirectoryFolder
/// </summary>
public class DirectoryFolder : DirectoryItem
{
/// <summary>
/// Gets or sets the Contacts
/// </summary>
[JsonProperty("contacts")]
public List<DirectoryContact> Contacts { get; set; }
/// <summary>
/// Constructor for <see cref="DirectoryFolder"/>
/// </summary>
public DirectoryFolder()
{
Contacts = new List<DirectoryContact>();
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a DirectoryItem
/// </summary>
public class DirectoryItem : ICloneable
{
/// <summary>
/// Clone method
/// </summary>
public object Clone()
{
return MemberwiseClone();
}
/// <summary>
/// Gets or sets the FolderId
/// </summary>
[JsonProperty("folderId")]
public string FolderId { get; set; }
/// <summary>
/// Gets or sets the Name
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the ParentFolderId
/// </summary>
[JsonProperty("parentFolderId")]
public string ParentFolderId { get; set; }
}
}

View File

@@ -1,31 +1,65 @@
using Crestron.SimplSharpPro; namespace PepperDash.Essentials.Devices.Common.Codec
using System; {
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary> /// <summary>
/// Describes a cisco codec device that can allow configuration of cameras /// Describes a cisco codec device that can allow configuration of cameras
/// </summary> /// </summary>
public interface ICiscoCodecCameraConfig public interface ICiscoCodecCameraConfig
{ {
/// <summary>
/// Sets the assigned serial number for the specified camera
/// </summary>
/// <param name="cameraId">The camera identifier</param>
/// <param name="serialNumber">The serial number to assign</param>
void SetCameraAssignedSerialNumber(uint cameraId, string serialNumber); void SetCameraAssignedSerialNumber(uint cameraId, string serialNumber);
/// <summary>
/// Sets the name for the camera on the specified video connector
/// </summary>
/// <param name="videoConnectorId">The video connector identifier</param>
/// <param name="name">The name to assign</param>
void SetCameraName(uint videoConnectorId, string name); void SetCameraName(uint videoConnectorId, string name);
/// <summary>
/// Sets the input source type for the specified video connector
/// </summary>
/// <param name="videoConnectorId">The video connector identifier</param>
/// <param name="sourceType">The source type to set</param>
void SetInputSourceType(uint videoConnectorId, eCiscoCodecInputSourceType sourceType); void SetInputSourceType(uint videoConnectorId, eCiscoCodecInputSourceType sourceType);
} }
/// <summary>
/// Enumeration of Cisco codec input source types
/// </summary>
public enum eCiscoCodecInputSourceType public enum eCiscoCodecInputSourceType
{ {
/// <summary>
/// PC source type
/// </summary>
PC, PC,
/// <summary>
/// Camera source type
/// </summary>
camera, camera,
/// <summary>
/// Document camera source type
/// </summary>
document_camera, document_camera,
/// <summary>
/// Media player source type
/// </summary>
mediaplayer, mediaplayer,
/// <summary>
/// Other source type
/// </summary>
other, other,
/// <summary>
/// Whiteboard source type
/// </summary>
whiteboard whiteboard
} }
} }

View File

@@ -1,10 +1,4 @@
using System; namespace PepperDash.Essentials.Devices.Common.Codec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
/// Defines the contract for IHasCallHold /// Defines the contract for IHasCallHold

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using PepperDash.Essentials.Devices.Common.Codec;
/// <summary>
/// Defines the contract for IHasDirectoryHistoryStack
/// </summary>
public interface IHasDirectoryHistoryStack : IHasDirectory
{
/// <summary>
/// Gets the DirectoryBrowseHistoryStack
/// </summary>
Stack<CodecDirectory> DirectoryBrowseHistoryStack { get; }
}

View File

@@ -1,10 +1,4 @@
using System; using PepperDash.Essentials.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
@@ -34,6 +28,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec
void ToggleDoNotDisturbMode(); void ToggleDoNotDisturbMode();
} }
/// <summary>
/// Defines the contract for devices that support Do Not Disturb mode with timeout functionality
/// </summary>
public interface IHasDoNotDisturbModeWithTimeout : IHasDoNotDisturbMode public interface IHasDoNotDisturbModeWithTimeout : IHasDoNotDisturbMode
{ {
/// <summary> /// <summary>

View File

@@ -1,25 +1,54 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
/// Defines the contract for IHasExternalSourceSwitching /// Defines the contract for IHasExternalSourceSwitching
/// </summary> /// </summary>
public interface IHasExternalSourceSwitching public interface IHasExternalSourceSwitching
{ {
/// <summary>
/// Gets a value indicating whether the external source list is enabled
/// </summary>
bool ExternalSourceListEnabled { get; } bool ExternalSourceListEnabled { get; }
string ExternalSourceInputPort { get; }
/// <summary>
/// Gets the external source input port identifier
/// </summary>
string ExternalSourceInputPort { get; }
/// <summary>
/// Adds an external source to the available sources
/// </summary>
/// <param name="connectorId">The connector identifier</param>
/// <param name="key">The unique key for the source</param>
/// <param name="name">The display name for the source</param>
/// <param name="type">The type of external source</param>
void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type); void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type);
/// <summary>
/// Sets the state of the specified external source
/// </summary>
/// <param name="key">The unique key of the external source</param>
/// <param name="mode">The mode to set for the source</param>
void SetExternalSourceState(string key, eExternalSourceMode mode); void SetExternalSourceState(string key, eExternalSourceMode mode);
/// <summary>
/// Clears all external sources from the list
/// </summary>
void ClearExternalSources(); void ClearExternalSources();
void SetSelectedSource(string key);
Action<string, string> RunRouteAction { set;} /// <summary>
/// Sets the selected source by its key
/// </summary>
/// <param name="key">The unique key of the source to select</param>
void SetSelectedSource(string key);
/// <summary>
/// Sets the action to run when routing between sources
/// </summary>
Action<string, string> RunRouteAction { set; }
} }
} }

View File

@@ -0,0 +1,13 @@
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Defines the contract for IInvitableContact
/// </summary>
public interface IInvitableContact
{
/// <summary>
/// Gets a value indicating whether this contact is invitable
/// </summary>
bool IsInvitableContact { get; }
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents an InvitableDirectoryContact
/// </summary>
public class InvitableDirectoryContact : DirectoryContact, IInvitableContact
{
/// <summary>
/// Gets a value indicating whether this contact is invitable
/// </summary>
[JsonProperty("isInvitableContact")]
public bool IsInvitableContact
{
get
{
return this is IInvitableContact;
}
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a Meeting
/// </summary>
public class Meeting
{
/// <summary>
/// Minutes before the meeting to show warning
/// </summary>
[JsonProperty("minutesBeforeMeeting")]
public int MinutesBeforeMeeting;
/// <summary>
/// Gets or sets the meeting ID
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets the meeting organizer
/// </summary>
[JsonProperty("organizer")]
public string Organizer { get; set; }
/// <summary>
/// Gets or sets the Title
/// </summary>
[JsonProperty("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets the Agenda
/// </summary>
[JsonProperty("agenda")]
public string Agenda { get; set; }
/// <summary>
/// Gets the meeting warning time span in minutes before the meeting starts
/// </summary>
[JsonProperty("meetingWarningMinutes")]
public TimeSpan MeetingWarningMinutes
{
get { return TimeSpan.FromMinutes(MinutesBeforeMeeting); }
}
/// <summary>
/// Gets the time remaining until the meeting starts
/// </summary>
[JsonProperty("timeToMeetingStart")]
public TimeSpan TimeToMeetingStart
{
get
{
return StartTime - DateTime.Now;
}
}
/// <summary>
/// Gets the time remaining until the meeting ends
/// </summary>
[JsonProperty("timeToMeetingEnd")]
public TimeSpan TimeToMeetingEnd
{
get
{
return EndTime - DateTime.Now;
}
}
/// <summary>
/// Gets or sets the StartTime
/// </summary>
[JsonProperty("startTime")]
public DateTime StartTime { get; set; }
/// <summary>
/// Gets or sets the EndTime
/// </summary>
[JsonProperty("endTime")]
public DateTime EndTime { get; set; }
/// <summary>
/// Gets the duration of the meeting
/// </summary>
[JsonProperty("duration")]
public TimeSpan Duration
{
get
{
return EndTime - StartTime;
}
}
/// <summary>
/// Gets or sets the Privacy
/// </summary>
[JsonProperty("privacy")]
public eMeetingPrivacy Privacy { get; set; }
/// <summary>
/// Gets a value indicating whether the meeting can be joined
/// </summary>
[JsonProperty("joinable")]
public bool Joinable
{
get
{
var joinable = StartTime.AddMinutes(-MinutesBeforeMeeting) <= DateTime.Now
&& DateTime.Now <= EndTime.AddSeconds(-_joinableCooldownSeconds);
//Debug.LogMessage(LogEventLevel.Verbose, "Meeting Id: {0} joinable: {1}", Id, joinable);
return joinable;
}
}
/// <summary>
/// Gets or sets the Dialable
/// </summary>
[JsonProperty("dialable")]
public bool Dialable { get; set; }
//public string ConferenceNumberToDial { get; set; }
/// <summary>
/// Gets or sets the ConferencePassword
/// </summary>
[JsonProperty("conferencePassword")]
public string ConferencePassword { get; set; }
/// <summary>
/// Gets or sets the IsOneButtonToPushMeeting
/// </summary>
[JsonProperty("isOneButtonToPushMeeting")]
public bool IsOneButtonToPushMeeting { get; set; }
/// <summary>
/// Gets or sets the Calls
/// </summary>
[JsonProperty("calls")]
public List<Call> Calls { get; private set; }
/// <summary>
/// Tracks the change types that have already been notified for
/// Gets or sets the NotifiedChangeTypes
/// </summary>
[JsonIgnore]
public eMeetingEventChangeType NotifiedChangeTypes { get; set; }
[JsonIgnore] private readonly int _joinableCooldownSeconds;
/// <summary>
/// Constructor for Meeting <see cref="Meeting"/>
/// </summary>
public Meeting()
{
Calls = new List<Call>();
_joinableCooldownSeconds = 300;
}
/// <summary>
/// Constructor for Meeting <see cref="Meeting"/>
/// </summary>
/// <param name="joinableCooldownSeconds">Number of seconds after meeting start when it is no longer joinable</param>
public Meeting(int joinableCooldownSeconds)
{
Calls = new List<Call>();
_joinableCooldownSeconds = joinableCooldownSeconds;
}
#region Overrides of Object
/// <summary>
/// ToString method
/// </summary>
/// <inheritdoc />
public override string ToString()
{
return string.Format("{0}:{1}: {2}-{3}", Title, Agenda, StartTime, EndTime);
}
#endregion
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Represents a MeetingEventArgs
/// </summary>
public class MeetingEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the ChangeType
/// </summary>
public eMeetingEventChangeType ChangeType { get; set; }
/// <summary>
/// Gets or sets the Meeting
/// </summary>
public Meeting Meeting { get; set; }
}
}

View File

@@ -1,10 +1,4 @@
using System; namespace PepperDash.Essentials.Devices.Common.Codec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
@@ -12,7 +6,20 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public enum eCodecCallDirection public enum eCodecCallDirection
{ {
Unknown = 0, Incoming, Outgoing /// <summary>
/// Unknown call direction
/// </summary>
Unknown = 0,
/// <summary>
/// Incoming call direction
/// </summary>
Incoming,
/// <summary>
/// Outgoing call direction
/// </summary>
Outgoing
} }
/// <summary> /// <summary>

View File

@@ -1,27 +1,68 @@
using System; namespace PepperDash.Essentials.Devices.Common.Codec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
/// Enumeration of eCodecCallStatus values /// Enumeration of eCodecCallStatus values
/// </summary> /// </summary>
public enum eCodecCallStatus public enum eCodecCallStatus
{ {
/// <summary>
/// Unknown call status
/// </summary>
Unknown = 0, Unknown = 0,
Connected,
Connecting, /// <summary>
Dialing, /// Call is connected
/// </summary>
Connected,
/// <summary>
/// Call is connecting
/// </summary>
Connecting,
/// <summary>
/// Call is dialing
/// </summary>
Dialing,
/// <summary>
/// Call is disconnected
/// </summary>
Disconnected, Disconnected,
Disconnecting,
EarlyMedia, /// <summary>
/// Call is disconnecting
/// </summary>
Disconnecting,
/// <summary>
/// Early media is being sent/received
/// </summary>
EarlyMedia,
/// <summary>
/// Call is idle
/// </summary>
Idle, Idle,
OnHold,
Ringing, /// <summary>
Preserved, /// Call is on hold
/// </summary>
OnHold,
/// <summary>
/// Call is ringing
/// </summary>
Ringing,
/// <summary>
/// Call is preserved
/// </summary>
Preserved,
/// <summary>
/// Call is remote preserved
/// </summary>
RemotePreserved, RemotePreserved,
} }

View File

@@ -1,10 +1,4 @@
using System; namespace PepperDash.Essentials.Devices.Common.Codec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
@@ -12,10 +6,29 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public enum eCodecCallType public enum eCodecCallType
{ {
Unknown = 0, /// <summary>
Audio, /// Unknown call type
Video, /// </summary>
AudioCanEscalate, Unknown = 0,
/// <summary>
/// Audio-only call type
/// </summary>
Audio,
/// <summary>
/// Video call type
/// </summary>
Video,
/// <summary>
/// Audio call that can be escalated to video
/// </summary>
AudioCanEscalate,
/// <summary>
/// Forward all call type
/// </summary>
ForwardAllCall ForwardAllCall
} }

View File

@@ -0,0 +1,21 @@
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Enumeration of eContactMethodCallType values
/// </summary>
public enum eContactMethodCallType
{
/// <summary>
/// Unknown call type
/// </summary>
Unknown = 0,
/// <summary>
/// Audio call type
/// </summary>
Audio,
/// <summary>
/// Video call type
/// </summary>
Video
}
}

View File

@@ -0,0 +1,29 @@
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Enumeration of eContactMethodDevice values
/// </summary>
public enum eContactMethodDevice
{
/// <summary>
/// Unknown contact method
/// </summary>
Unknown = 0,
/// <summary>
/// Mobile contact method
/// </summary>
Mobile,
/// <summary>
/// Other contact method
/// </summary>
Other,
/// <summary>
/// Telephone contact method
/// </summary>
Telephone,
/// <summary>
/// Video contact method
/// </summary>
Video
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace PepperDash.Essentials.Devices.Common.Codec
{
/// <summary>
/// Enumeration of eMeetingEventChangeType values
/// </summary>
[Flags]
public enum eMeetingEventChangeType
{
/// <summary>
/// Unknown change type
/// </summary>
Unknown = 0,
/// <summary>
/// Meeting start warning
/// </summary>
MeetingStartWarning = 1,
/// <summary>
/// Meeting start
/// </summary>
MeetingStart = 2,
/// <summary>
/// Meeting end warning
/// </summary>
MeetingEndWarning = 4,
/// <summary>
/// Meeting end
/// </summary>
MeetingEnd = 8
}
}

View File

@@ -1,18 +1,23 @@
using System; namespace PepperDash.Essentials.Devices.Common.Codec
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
/// Enumeration of eMeetingPrivacy values /// Enumeration of eMeetingPrivacy values
/// </summary> /// </summary>
public enum eMeetingPrivacy public enum eMeetingPrivacy
{ {
/// <summary>
/// Unknown meeting privacy level
/// </summary>
Unknown = 0, Unknown = 0,
/// <summary>
/// Public meeting
/// </summary>
Public, Public,
/// <summary>
/// Private meeting
/// </summary>
Private Private
} }

View File

@@ -1,10 +1,4 @@
using System; using PepperDash.Essentials.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {

View File

@@ -1,8 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
@@ -11,6 +7,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public interface IHasCallFavorites public interface IHasCallFavorites
{ {
/// <summary>
/// Gets the call favorites for this device
/// </summary>
CodecCallFavorites CallFavorites { get; } CodecCallFavorites CallFavorites { get; }
} }
@@ -24,6 +23,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public List<CodecActiveCallItem> Favorites { get; set; } public List<CodecActiveCallItem> Favorites { get; set; }
/// <summary>
/// Initializes a new instance of the CodecCallFavorites class
/// </summary>
public CodecCallFavorites() public CodecCallFavorites()
{ {
Favorites = new List<CodecActiveCallItem>(); Favorites = new List<CodecActiveCallItem>();

View File

@@ -2,10 +2,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using PepperDash.Essentials.Devices.Common.VideoCodec;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
@@ -15,8 +14,15 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public interface IHasCallHistory public interface IHasCallHistory
{ {
/// <summary>
/// Gets the call history for this device
/// </summary>
CodecCallHistory CallHistory { get; } CodecCallHistory CallHistory { get; }
/// <summary>
/// Removes the specified call history entry
/// </summary>
/// <param name="entry">The call history entry to remove</param>
void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry); void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry);
} }
@@ -25,9 +31,24 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public enum eCodecOccurrenceType public enum eCodecOccurrenceType
{ {
/// <summary>
/// Unknown occurrence type
/// </summary>
Unknown = 0, Unknown = 0,
/// <summary>
/// Call was placed (outgoing)
/// </summary>
Placed = 1, Placed = 1,
/// <summary>
/// Call was received (incoming)
/// </summary>
Received = 2, Received = 2,
/// <summary>
/// Call received no answer
/// </summary>
NoAnswer = 3, NoAnswer = 3,
} }
@@ -36,6 +57,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public class CodecCallHistory public class CodecCallHistory
{ {
/// <summary>
/// Event that is raised when the recent calls list has changed
/// </summary>
public event EventHandler<EventArgs> RecentCallsListHasChanged; public event EventHandler<EventArgs> RecentCallsListHasChanged;
/// <summary> /// <summary>
@@ -48,6 +72,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
CallHistoryEntry ListEmptyEntry; CallHistoryEntry ListEmptyEntry;
/// <summary>
/// Initializes a new instance of the CodecCallHistory class
/// </summary>
public CodecCallHistory() public CodecCallHistory()
{ {
ListEmptyEntry = new CallHistoryEntry() { Name = "No Recent Calls" }; ListEmptyEntry = new CallHistoryEntry() { Name = "No Recent Calls" };
@@ -80,15 +107,22 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public class CallHistoryEntry : CodecActiveCallItem public class CallHistoryEntry : CodecActiveCallItem
{ {
[JsonConverter(typeof(IsoDateTimeConverter))]
[JsonProperty("startTime")]
/// <summary> /// <summary>
/// Gets or sets the StartTime /// Gets or sets the StartTime
/// </summary> /// </summary>
[JsonConverter(typeof(IsoDateTimeConverter))]
[JsonProperty("startTime")]
public DateTime StartTime { get; set; } public DateTime StartTime { get; set; }
/// <summary>
/// Gets or sets the occurrence type for this call history entry
/// </summary>
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("occurrenceType")] [JsonProperty("occurrenceType")]
public eCodecOccurrenceType OccurrenceType { get; set; } public eCodecOccurrenceType OccurrenceType { get; set; }
/// <summary>
/// Gets or sets the occurrence history identifier
/// </summary>
[JsonProperty("occurrenceHistoryId")] [JsonProperty("occurrenceHistoryId")]
public string OccurrenceHistoryId { get; set; } public string OccurrenceHistoryId { get; set; }
} }
@@ -119,7 +153,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec
} }
// Check if list is empty and if so, add an item to display No Recent Calls // Check if list is empty and if so, add an item to display No Recent Calls
if(genericEntries.Count == 0) if (genericEntries.Count == 0)
genericEntries.Add(ListEmptyEntry); genericEntries.Add(ListEmptyEntry);
RecentCalls = genericEntries; RecentCalls = genericEntries;

View File

@@ -1,11 +1,4 @@
using System; using PepperDash.Essentials.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
@@ -15,12 +8,29 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
public interface IHasContentSharing public interface IHasContentSharing
{ {
/// <summary>
/// Gets feedback indicating whether content sharing is currently active
/// </summary>
BoolFeedback SharingContentIsOnFeedback { get; } BoolFeedback SharingContentIsOnFeedback { get; }
/// <summary>
/// Gets feedback about the current sharing source
/// </summary>
StringFeedback SharingSourceFeedback { get; } StringFeedback SharingSourceFeedback { get; }
/// <summary>
/// Gets a value indicating whether content should be automatically shared while in a call
/// </summary>
bool AutoShareContentWhileInCall { get; } bool AutoShareContentWhileInCall { get; }
/// <summary>
/// Starts content sharing
/// </summary>
void StartSharing(); void StartSharing();
/// <summary>
/// Stops content sharing
/// </summary>
void StopSharing(); void StopSharing();
} }

View File

@@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
@@ -15,15 +9,49 @@ namespace PepperDash.Essentials.Devices.Common.Codec
{ {
// Add requirements for Dialer functionality // Add requirements for Dialer functionality
/// <summary>
/// Event that is raised when call status changes
/// </summary>
event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange; event EventHandler<CodecCallStatusItemChangeEventArgs> CallStatusChange;
/// <summary>
/// Dials the specified number
/// </summary>
/// <param name="number">The number to dial</param>
void Dial(string number); void Dial(string number);
/// <summary>
/// Ends the specified active call
/// </summary>
/// <param name="activeCall">The active call to end</param>
void EndCall(CodecActiveCallItem activeCall); void EndCall(CodecActiveCallItem activeCall);
/// <summary>
/// Ends all active calls
/// </summary>
void EndAllCalls(); void EndAllCalls();
/// <summary>
/// Accepts the specified incoming call
/// </summary>
/// <param name="item">The call item to accept</param>
void AcceptCall(CodecActiveCallItem item); void AcceptCall(CodecActiveCallItem item);
/// <summary>
/// Rejects the specified incoming call
/// </summary>
/// <param name="item">The call item to reject</param>
void RejectCall(CodecActiveCallItem item); void RejectCall(CodecActiveCallItem item);
/// <summary>
/// Sends DTMF digits during a call
/// </summary>
/// <param name="digit">The DTMF digit(s) to send</param>
void SendDtmf(string digit); void SendDtmf(string digit);
/// <summary>
/// Gets a value indicating whether the device is currently in a call
/// </summary>
bool IsInCall { get; } bool IsInCall { get; }
} }

View File

@@ -1,319 +1,59 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Devices.Common.VideoCodec; using PepperDash.Essentials.Devices.Common.VideoCodec;
namespace PepperDash.Essentials.Devices.Common.Codec namespace PepperDash.Essentials.Devices.Common.Codec
{ {
/// <summary> /// <summary>
/// Defines the API for codecs with a directory /// Defines the API for codecs with a directory
/// </summary> /// </summary>
public interface IHasDirectory public interface IHasDirectory
{ {
/// <summary>
/// Event that fires when a directory result is returned from the codec
/// </summary>
event EventHandler<DirectoryEventArgs> DirectoryResultReturned; event EventHandler<DirectoryEventArgs> DirectoryResultReturned;
/// <summary>
/// Gets the DirectoryRoot
/// </summary>
CodecDirectory DirectoryRoot { get; } CodecDirectory DirectoryRoot { get; }
/// <summary>
/// Gets the CurrentDirectoryResult
/// </summary>
CodecDirectory CurrentDirectoryResult { get; } CodecDirectory CurrentDirectoryResult { get; }
/// <summary>
/// Gets the PhonebookSyncState
/// </summary>
CodecPhonebookSyncState PhonebookSyncState { get; } CodecPhonebookSyncState PhonebookSyncState { get; }
/// <summary>
/// Method to initiate a search of the directory on the server
/// </summary>
void SearchDirectory(string searchString); void SearchDirectory(string searchString);
/// <summary>
/// Method to get the contents of a specific folder in the directory on the server
/// </summary>
void GetDirectoryFolderContents(string folderId); void GetDirectoryFolderContents(string folderId);
/// <summary>
/// Method to set the current directory to the root folder
/// </summary>
void SetCurrentDirectoryToRoot(); void SetCurrentDirectoryToRoot();
/// <summary>
/// Method to get the contents of the parent folder in the directory on the server
/// </summary>
void GetDirectoryParentFolderContents(); void GetDirectoryParentFolderContents();
/// <summary>
/// Gets the CurrentDirectoryResultIsNotDirectoryRoot
/// </summary>
BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; } BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; }
} }
/// <summary>
/// Defines the contract for IHasDirectoryHistoryStack
/// </summary>
public interface IHasDirectoryHistoryStack : IHasDirectory
{
Stack<CodecDirectory> DirectoryBrowseHistoryStack { get; }
}
/// <summary>
/// Represents a DirectoryEventArgs
/// </summary>
public class DirectoryEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the Directory
/// </summary>
public CodecDirectory Directory { get; set; }
/// <summary>
/// Gets or sets the DirectoryIsOnRoot
/// </summary>
public bool DirectoryIsOnRoot { get; set; }
}
/// <summary>
/// Represents a codec directory
/// </summary>
public class CodecDirectory
{
/// <summary>
/// Represents the contents of the directory
/// We don't want to serialize this for messages to MobileControl. MC can combine Contacts and Folders to get the same data
/// </summary>
[JsonIgnore]
public List<DirectoryItem> CurrentDirectoryResults { get; private set; }
[JsonProperty("contacts")]
public List<DirectoryItem> Contacts
{
get
{
return CurrentDirectoryResults.OfType<DirectoryContact>().Cast<DirectoryItem>().ToList();
}
}
[JsonProperty("folders")]
public List<DirectoryItem> Folders
{
get
{
return CurrentDirectoryResults.OfType<DirectoryFolder>().Cast<DirectoryItem>().ToList();
}
}
/// <summary>
/// Used to store the ID of the current folder for CurrentDirectoryResults
/// </summary>
[JsonProperty("resultsFolderId")]
/// <summary>
/// Gets or sets the ResultsFolderId
/// </summary>
public string ResultsFolderId { get; set; }
public CodecDirectory()
{
CurrentDirectoryResults = new List<DirectoryItem>();
}
/// <summary>
/// Adds folders to the directory
/// </summary>
/// <param name="folders"></param>
/// <summary>
/// AddFoldersToDirectory method
/// </summary>
public void AddFoldersToDirectory(List<DirectoryItem> folders)
{
if(folders != null)
CurrentDirectoryResults.AddRange(folders);
SortDirectory();
}
/// <summary>
/// Adds contacts to the directory
/// </summary>
/// <param name="contacts"></param>
/// <summary>
/// AddContactsToDirectory method
/// </summary>
public void AddContactsToDirectory(List<DirectoryItem> contacts)
{
if(contacts != null)
CurrentDirectoryResults.AddRange(contacts);
SortDirectory();
}
/// <summary>
/// Filters the CurrentDirectoryResults by the predicate
/// </summary>
/// <param name="predicate"></param>
/// <summary>
/// FilterContacts method
/// </summary>
public void FilterContacts(Func<DirectoryItem, bool> predicate)
{
CurrentDirectoryResults = CurrentDirectoryResults.Where(predicate).ToList();
}
/// <summary>
/// Sorts the DirectoryResults list to display all folders alphabetically, then all contacts alphabetically
/// </summary>
private void SortDirectory()
{
var sortedFolders = new List<DirectoryItem>();
sortedFolders.AddRange(CurrentDirectoryResults.Where(f => f is DirectoryFolder));
sortedFolders.OrderBy(f => f.Name);
var sortedContacts = new List<DirectoryItem>();
sortedContacts.AddRange(CurrentDirectoryResults.Where(c => c is DirectoryContact));
sortedFolders.OrderBy(c => c.Name);
CurrentDirectoryResults.Clear();
CurrentDirectoryResults.AddRange(sortedFolders);
CurrentDirectoryResults.AddRange(sortedContacts);
}
}
/// <summary>
/// Defines the contract for IInvitableContact
/// </summary>
public interface IInvitableContact
{
bool IsInvitableContact { get; }
}
public class InvitableDirectoryContact : DirectoryContact, IInvitableContact
{
[JsonProperty("isInvitableContact")]
public bool IsInvitableContact
{
get
{
return this is IInvitableContact;
}
}
}
/// <summary>
/// Represents a DirectoryItem
/// </summary>
public class DirectoryItem : ICloneable
{
/// <summary>
/// Clone method
/// </summary>
public object Clone()
{
return this.MemberwiseClone();
}
[JsonProperty("folderId")]
public string FolderId { get; set; }
[JsonProperty("name")]
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; set; }
[JsonProperty("parentFolderId")]
/// <summary>
/// Gets or sets the ParentFolderId
/// </summary>
public string ParentFolderId { get; set; }
}
/// <summary>
/// Represents a DirectoryFolder
/// </summary>
public class DirectoryFolder : DirectoryItem
{
[JsonProperty("contacts")]
/// <summary>
/// Gets or sets the Contacts
/// </summary>
public List<DirectoryContact> Contacts { get; set; }
public DirectoryFolder()
{
Contacts = new List<DirectoryContact>();
}
}
/// <summary>
/// Represents a DirectoryContact
/// </summary>
public class DirectoryContact : DirectoryItem
{
[JsonProperty("contactId")]
/// <summary>
/// Gets or sets the ContactId
/// </summary>
public string ContactId { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("contactMethods")]
public List<ContactMethod> ContactMethods { get; set; }
public DirectoryContact()
{
ContactMethods = new List<ContactMethod>();
}
}
/// <summary>
/// Represents a ContactMethod
/// </summary>
public class ContactMethod
{
[JsonProperty("contactMethodId")]
/// <summary>
/// Gets or sets the ContactMethodId
/// </summary>
public string ContactMethodId { get; set; }
[JsonProperty("number")]
public string Number { get; set; }
[JsonProperty("device")]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary>
/// Gets or sets the Device
/// </summary>
public eContactMethodDevice Device { get; set; }
[JsonProperty("callType")]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary>
/// Gets or sets the CallType
/// </summary>
public eContactMethodCallType CallType { get; set; }
}
/// <summary>
/// Enumeration of eContactMethodDevice values
/// </summary>
public enum eContactMethodDevice
{
Unknown = 0,
Mobile,
Other,
Telephone,
Video
}
/// <summary>
/// Enumeration of eContactMethodCallType values
/// </summary>
public enum eContactMethodCallType
{
Unknown = 0,
Audio,
Video
}
} }

View File

@@ -1,339 +1,20 @@
namespace PepperDash.Essentials.Devices.Common.Codec
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using Newtonsoft.Json;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Codec
{ {
[Flags]
/// <summary>
/// Enumeration of eMeetingEventChangeType values
/// </summary>
public enum eMeetingEventChangeType
{
Unknown = 0,
MeetingStartWarning = 1,
MeetingStart = 2,
MeetingEndWarning = 4,
MeetingEnd = 8
}
/// <summary> /// <summary>
/// Defines the contract for IHasScheduleAwareness /// Defines the contract for IHasScheduleAwareness
/// </summary> /// </summary>
public interface IHasScheduleAwareness public interface IHasScheduleAwareness
{ {
/// <summary>
/// Gets the CodecScheduleAwareness instance
/// </summary>
CodecScheduleAwareness CodecSchedule { get; } CodecScheduleAwareness CodecSchedule { get; }
/// <summary>
/// Method to initiate getting the schedule from the server
/// </summary>
void GetSchedule(); void GetSchedule();
} }
/// <summary>
/// Represents a CodecScheduleAwareness
/// </summary>
public class CodecScheduleAwareness
{
List<Meeting> _meetings;
public event EventHandler<MeetingEventArgs> MeetingEventChange;
public event EventHandler<EventArgs> MeetingsListHasChanged;
private int _meetingWarningMinutes = 5;
//private Meeting _previousChangedMeeting;
//private eMeetingEventChangeType _previousChangeType = eMeetingEventChangeType.Unknown;
public int MeetingWarningMinutes
{
get { return _meetingWarningMinutes; }
set { _meetingWarningMinutes = value; }
}
/// <summary>
/// Setter triggers MeetingsListHasChanged event
/// </summary>
public List<Meeting> Meetings
{
get
{
return _meetings;
}
set
{
_meetings = value;
MeetingsListHasChanged?.Invoke(this, new EventArgs());
}
}
private readonly CTimer _scheduleChecker;
public CodecScheduleAwareness()
{
Meetings = new List<Meeting>();
_scheduleChecker = new CTimer(CheckSchedule, null, 1000, 1000);
}
public CodecScheduleAwareness(long pollTime)
{
Meetings = new List<Meeting>();
_scheduleChecker = new CTimer(CheckSchedule, null, pollTime, pollTime);
}
/// <summary>
/// Helper method to fire MeetingEventChange. Should only fire once for each changeType on each meeting
/// </summary>
/// <param name="changeType"></param>
/// <param name="meeting"></param>
private void OnMeetingChange(eMeetingEventChangeType changeType, Meeting meeting)
{
Debug.LogMessage(LogEventLevel.Verbose, "*****************OnMeetingChange. id: {0} changeType: {1}**********************", meeting.Id, changeType);
if (changeType != (changeType & meeting.NotifiedChangeTypes))
{
// Add this change type to the NotifiedChangeTypes
meeting.NotifiedChangeTypes |= changeType;
MeetingEventChange?.Invoke(this, new MeetingEventArgs() { ChangeType = changeType, Meeting = meeting });
}
else
{
Debug.LogMessage(LogEventLevel.Verbose, "Meeting: {0} already notified of changeType: {1}", meeting.Id, changeType);
}
}
/// <summary>
/// Checks the schedule to see if any MeetingEventChange updates should be fired
/// </summary>
/// <param name="o"></param>
private void CheckSchedule(object o)
{
// Iterate the meeting list and check if any meeting need to do anything
const double meetingTimeEpsilon = 0.05;
foreach (var m in Meetings)
{
var changeType = eMeetingEventChangeType.Unknown;
if (eMeetingEventChangeType.MeetingStartWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStartWarning) && m.TimeToMeetingStart.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingStart.Seconds > 0) // Meeting is about to start
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingStartWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingStart.TotalMinutes, m.TimeToMeetingStart.Seconds);
changeType = eMeetingEventChangeType.MeetingStartWarning;
}
else if (eMeetingEventChangeType.MeetingStart != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStart) && Math.Abs(m.TimeToMeetingStart.TotalMinutes) < meetingTimeEpsilon) // Meeting Start
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingStart");
changeType = eMeetingEventChangeType.MeetingStart;
}
else if (eMeetingEventChangeType.MeetingEndWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEndWarning) && m.TimeToMeetingEnd.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingEnd.Seconds > 0) // Meeting is about to end
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingEndWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingEnd.TotalMinutes, m.TimeToMeetingEnd.Seconds);
changeType = eMeetingEventChangeType.MeetingEndWarning;
}
else if (eMeetingEventChangeType.MeetingEnd != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEnd) && Math.Abs(m.TimeToMeetingEnd.TotalMinutes) < meetingTimeEpsilon) // Meeting has ended
{
Debug.LogMessage(LogEventLevel.Verbose, "********************* MeetingEnd");
changeType = eMeetingEventChangeType.MeetingEnd;
}
if (changeType != eMeetingEventChangeType.Unknown)
{
OnMeetingChange(changeType, m);
}
}
}
}
/// <summary>
/// Represents a Meeting
/// </summary>
public class Meeting
{
[JsonProperty("minutesBeforeMeeting")]
public int MinutesBeforeMeeting;
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("organizer")]
public string Organizer { get; set; }
[JsonProperty("title")]
/// <summary>
/// Gets or sets the Title
/// </summary>
public string Title { get; set; }
[JsonProperty("agenda")]
/// <summary>
/// Gets or sets the Agenda
/// </summary>
public string Agenda { get; set; }
[JsonProperty("meetingWarningMinutes")]
public TimeSpan MeetingWarningMinutes
{
get { return TimeSpan.FromMinutes(MinutesBeforeMeeting); }
}
[JsonProperty("timeToMeetingStart")]
public TimeSpan TimeToMeetingStart
{
get
{
return StartTime - DateTime.Now;
}
}
[JsonProperty("timeToMeetingEnd")]
public TimeSpan TimeToMeetingEnd
{
get
{
return EndTime - DateTime.Now;
}
}
[JsonProperty("startTime")]
/// <summary>
/// Gets or sets the StartTime
/// </summary>
public DateTime StartTime { get; set; }
[JsonProperty("endTime")]
/// <summary>
/// Gets or sets the EndTime
/// </summary>
public DateTime EndTime { get; set; }
[JsonProperty("duration")]
public TimeSpan Duration
{
get
{
return EndTime - StartTime;
}
}
[JsonProperty("privacy")]
/// <summary>
/// Gets or sets the Privacy
/// </summary>
public eMeetingPrivacy Privacy { get; set; }
[JsonProperty("joinable")]
public bool Joinable
{
get
{
var joinable = StartTime.AddMinutes(-MinutesBeforeMeeting) <= DateTime.Now
&& DateTime.Now <= EndTime.AddSeconds(-_joinableCooldownSeconds);
//Debug.LogMessage(LogEventLevel.Verbose, "Meeting Id: {0} joinable: {1}", Id, joinable);
return joinable;
}
}
[JsonProperty("dialable")]
/// <summary>
/// Gets or sets the Dialable
/// </summary>
public bool Dialable { get; set; }
//public string ConferenceNumberToDial { get; set; }
[JsonProperty("conferencePassword")]
/// <summary>
/// Gets or sets the ConferencePassword
/// </summary>
public string ConferencePassword { get; set; }
[JsonProperty("isOneButtonToPushMeeting")]
/// <summary>
/// Gets or sets the IsOneButtonToPushMeeting
/// </summary>
public bool IsOneButtonToPushMeeting { get; set; }
[JsonProperty("calls")]
/// <summary>
/// Gets or sets the Calls
/// </summary>
public List<Call> Calls { get; private set; }
/// <summary>
/// Tracks the change types that have already been notified for
/// </summary>
[JsonIgnore]
/// <summary>
/// Gets or sets the NotifiedChangeTypes
/// </summary>
public eMeetingEventChangeType NotifiedChangeTypes { get; set; }
[JsonIgnore] private readonly int _joinableCooldownSeconds;
public Meeting()
{
Calls = new List<Call>();
_joinableCooldownSeconds = 300;
}
public Meeting(int joinableCooldownSeconds)
{
Calls = new List<Call>();
_joinableCooldownSeconds = joinableCooldownSeconds;
}
#region Overrides of Object
/// <summary>
/// ToString method
/// </summary>
/// <inheritdoc />
public override string ToString()
{
return String.Format("{0}:{1}: {2}-{3}", Title, Agenda, StartTime, EndTime);
}
#endregion
}
/// <summary>
/// Represents a Call
/// </summary>
public class Call
{
/// <summary>
/// Gets or sets the Number
/// </summary>
public string Number { get; set; }
/// <summary>
/// Gets or sets the Protocol
/// </summary>
public string Protocol { get; set; }
/// <summary>
/// Gets or sets the CallRate
/// </summary>
public string CallRate { get; set; }
/// <summary>
/// Gets or sets the CallType
/// </summary>
public string CallType { get; set; }
}
/// <summary>
/// Represents a MeetingEventArgs
/// </summary>
public class MeetingEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the ChangeType
/// </summary>
public eMeetingEventChangeType ChangeType { get; set; }
/// <summary>
/// Gets or sets the Meeting
/// </summary>
public Meeting Meeting { get; set; }
}
} }

View File

@@ -1,61 +1,84 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
namespace PepperDash.Essentials.Devices.Common.DSP namespace PepperDash.Essentials.Devices.Common.DSP
{ {
public abstract class DspBase : EssentialsDevice, ILevelControls /// <summary>
{ /// Base class for DSP devices
public Dictionary<string,IBasicVolumeWithFeedback> LevelControlPoints { get; private set; } /// </summary>
public abstract class DspBase : EssentialsDevice, ILevelControls
{
/// <summary>
/// Gets the collection of level control points
/// </summary>
public Dictionary<string, IBasicVolumeWithFeedback> LevelControlPoints { get; private set; }
/// <summary>
/// Gets the collection of dialer control points
/// </summary>
public Dictionary<string, DspControlPoint> DialerControlPoints { get; private set; } public Dictionary<string, DspControlPoint> DialerControlPoints { get; private set; }
/// <summary>
/// Gets the collection of switcher control points
/// </summary>
public Dictionary<string, DspControlPoint> SwitcherControlPoints { get; private set; } public Dictionary<string, DspControlPoint> SwitcherControlPoints { get; private set; }
public DspBase(string key, string name) : /// <summary>
base(key, name) /// Initializes a new instance of the DspBase class
{ /// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
public DspBase(string key, string name) :
base(key, name)
{
LevelControlPoints = new Dictionary<string, IBasicVolumeWithFeedback>(); LevelControlPoints = new Dictionary<string, IBasicVolumeWithFeedback>();
DialerControlPoints = new Dictionary<string, DspControlPoint>(); DialerControlPoints = new Dictionary<string, DspControlPoint>();
SwitcherControlPoints = new Dictionary<string, DspControlPoint>(); SwitcherControlPoints = new Dictionary<string, DspControlPoint>();
} }
// in audio call feedback // in audio call feedback
// VOIP // VOIP
// Phone dialer // Phone dialer
} }
// Fusion // Fusion
// Privacy state // Privacy state
// Online state // Online state
// level/mutes ? // level/mutes ?
// AC Log call stats
// Typical presets:
// call default preset to restore levels and mutes
public abstract class DspControlPoint :IKeyed // AC Log call stats
{
// Typical presets:
// call default preset to restore levels and mutes
/// <summary>
/// Base class for DSP control points
/// </summary>
public abstract class DspControlPoint : IKeyed
{
/// <summary> /// <summary>
/// Gets or sets the Key /// Gets or sets the Key
/// </summary> /// </summary>
public string Key { get; } public string Key { get; }
/// <summary>
/// Initializes a new instance of the DspControlPoint class
/// </summary>
/// <param name="key">The control point key</param>
protected DspControlPoint(string key) => Key = key; protected DspControlPoint(string key) => Key = key;
} }
public abstract class DspLevelControlPoint :DspControlPoint, IBasicVolumeWithFeedback /// <summary>
/// Base class for DSP level control points with volume and mute functionality
/// </summary>
public abstract class DspLevelControlPoint : DspControlPoint, IBasicVolumeWithFeedback
{ {
/// <summary> /// <summary>
/// Gets or sets the MuteFeedback /// Gets or sets the MuteFeedback
@@ -66,30 +89,63 @@ namespace PepperDash.Essentials.Devices.Common.DSP
/// </summary> /// </summary>
public IntFeedback VolumeLevelFeedback { get; } public IntFeedback VolumeLevelFeedback { get; }
/// <summary>
/// Initializes a new instance of the DspLevelControlPoint class
/// </summary>
/// <param name="key">The control point key</param>
/// <param name="muteFeedbackFunc">Function to get mute status</param>
/// <param name="volumeLevelFeedbackFunc">Function to get volume level</param>
protected DspLevelControlPoint(string key, Func<bool> muteFeedbackFunc, Func<int> volumeLevelFeedbackFunc) : base(key) protected DspLevelControlPoint(string key, Func<bool> muteFeedbackFunc, Func<int> volumeLevelFeedbackFunc) : base(key)
{ {
MuteFeedback = new BoolFeedback(muteFeedbackFunc); MuteFeedback = new BoolFeedback("mute", muteFeedbackFunc);
VolumeLevelFeedback = new IntFeedback(volumeLevelFeedbackFunc); VolumeLevelFeedback = new IntFeedback("volume", volumeLevelFeedbackFunc);
} }
/// <summary>
/// Turns mute off
/// </summary>
public abstract void MuteOff(); public abstract void MuteOff();
/// <summary>
/// Turns mute on
/// </summary>
public abstract void MuteOn(); public abstract void MuteOn();
/// <summary>
/// Toggles mute state
/// </summary>
public abstract void MuteToggle(); public abstract void MuteToggle();
/// <summary>
/// Sets the volume level
/// </summary>
/// <param name="level">The volume level to set</param>
public abstract void SetVolume(ushort level); public abstract void SetVolume(ushort level);
/// <summary>
/// Decreases volume
/// </summary>
/// <param name="pressRelease">True when pressed, false when released</param>
public abstract void VolumeDown(bool pressRelease); public abstract void VolumeDown(bool pressRelease);
/// <summary>
/// Increases volume
/// </summary>
/// <param name="pressRelease">True when pressed, false when released</param>
public abstract void VolumeUp(bool pressRelease); public abstract void VolumeUp(bool pressRelease);
} }
/// <summary>
public abstract class DspDialerBase:DspControlPoint /// Base class for DSP dialer control points
{ /// </summary>
public abstract class DspDialerBase : DspControlPoint
{
/// <summary>
/// Initializes a new instance of the DspDialerBase class
/// </summary>
/// <param name="key">The dialer control point key</param>
protected DspDialerBase(string key) : base(key) { } protected DspDialerBase(string key) : base(key) { }
} }
// Main program // Main program
// VTC // VTC
// ATC // ATC
// Mics, unusual // Mics, unusual
} }

View File

@@ -14,7 +14,9 @@ namespace PepperDash.Essentials.Devices.Common
/// </summary> /// </summary>
public class DeviceFactory public class DeviceFactory
{ {
/// <summary>
/// Initializes a new instance of the DeviceFactory class
/// </summary>
public DeviceFactory() public DeviceFactory()
{ {
var assy = Assembly.GetExecutingAssembly(); var assy = Assembly.GetExecutingAssembly();

View File

@@ -11,37 +11,53 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Displays namespace PepperDash.Essentials.Devices.Common.Displays
{ {
/// <summary> /// <summary>
/// Represents a BasicIrDisplay /// Represents a BasicIrDisplay
/// </summary> /// </summary>
public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced
{ {
/// <summary> /// <summary>
/// Gets or sets the IrPort /// Gets or sets the IrPort
/// </summary> /// </summary>
public IrOutputPortController IrPort { get; private set; } public IrOutputPortController IrPort { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the IrPulseTime /// Gets or sets the IrPulseTime
/// </summary> /// </summary>
public ushort IrPulseTime { get; set; } public ushort IrPulseTime { get; set; }
protected Func<bool> PowerIsOnFeedbackFunc /// <summary>
{ /// Gets the power is on feedback function
get { return () => _PowerIsOn; } /// </summary>
} protected Func<bool> PowerIsOnFeedbackFunc
{
get { return () => _PowerIsOn; }
}
/// <summary>
/// Gets the is cooling down feedback function
/// </summary>
protected override Func<bool> IsCoolingDownFeedbackFunc protected override Func<bool> IsCoolingDownFeedbackFunc
{ {
get { return () => _IsCoolingDown; } get { return () => _IsCoolingDown; }
} }
/// <summary>
/// Gets the is warming up feedback function
/// </summary>
protected override Func<bool> IsWarmingUpFeedbackFunc protected override Func<bool> IsWarmingUpFeedbackFunc
{ {
get { return () => _IsWarmingUp; } get { return () => _IsWarmingUp; }
} }
bool _PowerIsOn; bool _PowerIsOn;
bool _IsWarmingUp; bool _IsWarmingUp;
bool _IsCoolingDown; bool _IsCoolingDown;
/// <summary>
/// Initializes a new instance of the BasicIrDisplay class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
/// <param name="port">The IR output port</param>
/// <param name="irDriverFilepath">The path to the IR driver file</param>
public BasicIrDisplay(string key, string name, IROutputPort port, string irDriverFilepath) public BasicIrDisplay(string key, string name, IROutputPort port, string irDriverFilepath)
: base(key, name) : base(key, name)
{ {
@@ -53,74 +69,74 @@ namespace PepperDash.Essentials.Devices.Common.Displays
InputPorts.AddRange(new RoutingPortCollection<RoutingInputPort> InputPorts.AddRange(new RoutingPortCollection<RoutingInputPort>
{ {
new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Hdmi1), this, false), eRoutingPortConnectionType.Hdmi, new Action(Hdmi1), this, false),
new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Hdmi2), this, false), eRoutingPortConnectionType.Hdmi, new Action(Hdmi2), this, false),
new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Hdmi3), this, false), eRoutingPortConnectionType.Hdmi, new Action(Hdmi3), this, false),
new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Hdmi4), this, false), eRoutingPortConnectionType.Hdmi, new Action(Hdmi4), this, false),
new RoutingInputPort(RoutingPortNames.ComponentIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.ComponentIn, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Component1), this, false), eRoutingPortConnectionType.Hdmi, new Action(Component1), this, false),
new RoutingInputPort(RoutingPortNames.CompositeIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.CompositeIn, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Video1), this, false), eRoutingPortConnectionType.Hdmi, new Action(Video1), this, false),
new RoutingInputPort(RoutingPortNames.AntennaIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, new RoutingInputPort(RoutingPortNames.AntennaIn, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, new Action(Antenna), this, false), eRoutingPortConnectionType.Hdmi, new Action(Antenna), this, false),
}); });
} }
/// <summary> /// <summary>
/// Hdmi1 method /// Hdmi1 method
/// </summary> /// </summary>
public void Hdmi1() public void Hdmi1()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_1, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_1, IrPulseTime);
} }
/// <summary> /// <summary>
/// Hdmi2 method /// Hdmi2 method
/// </summary> /// </summary>
public void Hdmi2() public void Hdmi2()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_2, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_2, IrPulseTime);
} }
/// <summary> /// <summary>
/// Hdmi3 method /// Hdmi3 method
/// </summary> /// </summary>
public void Hdmi3() public void Hdmi3()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_3, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_3, IrPulseTime);
} }
/// <summary> /// <summary>
/// Hdmi4 method /// Hdmi4 method
/// </summary> /// </summary>
public void Hdmi4() public void Hdmi4()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_4, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_4, IrPulseTime);
} }
/// <summary> /// <summary>
/// Component1 method /// Component1 method
/// </summary> /// </summary>
public void Component1() public void Component1()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_COMPONENT_1, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_COMPONENT_1, IrPulseTime);
} }
/// <summary> /// <summary>
/// Video1 method /// Video1 method
/// </summary> /// </summary>
public void Video1() public void Video1()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_VIDEO_1, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_VIDEO_1, IrPulseTime);
} }
/// <summary> /// <summary>
/// Antenna method /// Antenna method
/// </summary> /// </summary>
public void Antenna() public void Antenna()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_ANTENNA, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_ANTENNA, IrPulseTime);
@@ -128,31 +144,31 @@ namespace PepperDash.Essentials.Devices.Common.Displays
#region IPower Members #region IPower Members
/// <summary> /// <summary>
/// PowerOn method /// PowerOn method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override void PowerOn() public override void PowerOn()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_POWER_ON, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_POWER_ON, IrPulseTime);
_PowerIsOn = true; _PowerIsOn = true;
} }
/// <summary> /// <summary>
/// PowerOff method /// PowerOff method
/// </summary> /// </summary>
public override void PowerOff() public override void PowerOff()
{ {
_PowerIsOn = false; _PowerIsOn = false;
IrPort.Pulse(IROutputStandardCommands.IROut_POWER_OFF, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_POWER_OFF, IrPulseTime);
} }
/// <summary> /// <summary>
/// PowerToggle method /// PowerToggle method
/// </summary> /// </summary>
public override void PowerToggle() public override void PowerToggle()
{ {
_PowerIsOn = false; _PowerIsOn = false;
IrPort.Pulse(IROutputStandardCommands.IROut_POWER, IrPulseTime); IrPort.Pulse(IROutputStandardCommands.IROut_POWER, IrPulseTime);
} }
@@ -160,25 +176,25 @@ namespace PepperDash.Essentials.Devices.Common.Displays
#region IBasicVolumeControls Members #region IBasicVolumeControls Members
/// <summary> /// <summary>
/// VolumeUp method /// VolumeUp method
/// </summary> /// </summary>
public void VolumeUp(bool pressRelease) public void VolumeUp(bool pressRelease)
{ {
IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_PLUS, pressRelease); IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_PLUS, pressRelease);
} }
/// <summary> /// <summary>
/// VolumeDown method /// VolumeDown method
/// </summary> /// </summary>
public void VolumeDown(bool pressRelease) public void VolumeDown(bool pressRelease)
{ {
IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_MINUS, pressRelease); IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_MINUS, pressRelease);
} }
/// <summary> /// <summary>
/// MuteToggle method /// MuteToggle method
/// </summary> /// </summary>
public void MuteToggle() public void MuteToggle()
{ {
IrPort.Pulse(IROutputStandardCommands.IROut_MUTE, 200); IrPort.Pulse(IROutputStandardCommands.IROut_MUTE, 200);
@@ -190,7 +206,8 @@ namespace PepperDash.Essentials.Devices.Common.Displays
{ {
_IsWarmingUp = true; _IsWarmingUp = true;
IsWarmingUpFeedback.FireUpdate(); IsWarmingUpFeedback.FireUpdate();
new CTimer(o => { new CTimer(o =>
{
_IsWarmingUp = false; _IsWarmingUp = false;
IsWarmingUpFeedback.FireUpdate(); IsWarmingUpFeedback.FireUpdate();
}, 10000); }, 10000);
@@ -213,13 +230,13 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// Typically called by the discovery routing algorithm. /// Typically called by the discovery routing algorithm.
/// </summary> /// </summary>
/// <param name="inputSelector">A delegate containing the input selector method to call</param> /// <param name="inputSelector">A delegate containing the input selector method to call</param>
/// <summary> /// <summary>
/// ExecuteSwitch method /// ExecuteSwitch method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override void ExecuteSwitch(object inputSelector) public override void ExecuteSwitch(object inputSelector)
{ {
Debug.LogMessage(LogEventLevel.Verbose, this, "Switching to input '{0}'", (inputSelector as Action).ToString()); Debug.LogMessage(LogEventLevel.Verbose, this, "Switching to input '{0}'", (inputSelector as Action).ToString());
Action finishSwitch = () => Action finishSwitch = () =>
{ {
@@ -246,42 +263,45 @@ namespace PepperDash.Essentials.Devices.Common.Displays
#endregion #endregion
/// <summary> /// <summary>
/// LinkToApi method /// LinkToApi method
/// </summary> /// </summary>
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{ {
LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge);
} }
} }
/// <summary> /// <summary>
/// Represents a BasicIrDisplayFactory /// Represents a BasicIrDisplayFactory
/// </summary> /// </summary>
public class BasicIrDisplayFactory : EssentialsDeviceFactory<BasicIrDisplay> public class BasicIrDisplayFactory : EssentialsDeviceFactory<BasicIrDisplay>
{ {
public BasicIrDisplayFactory() /// <summary>
{ /// Initializes a new instance of the BasicIrDisplayFactory class
TypeNames = new List<string>() { "basicirdisplay" }; /// </summary>
} public BasicIrDisplayFactory()
{
TypeNames = new List<string>() { "basicirdisplay" };
}
/// <summary> /// <summary>
/// BuildDevice method /// BuildDevice method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) public override EssentialsDevice BuildDevice(DeviceConfig dc)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new BasicIrDisplay Device"); Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new BasicIrDisplay Device");
var ir = IRPortHelper.GetIrPort(dc.Properties); var ir = IRPortHelper.GetIrPort(dc.Properties);
if (ir != null) if (ir != null)
{ {
var display = new BasicIrDisplay(dc.Key, dc.Name, ir.Port, ir.FileName); var display = new BasicIrDisplay(dc.Key, dc.Name, ir.Port, ir.FileName);
display.IrPulseTime = 200; // Set default pulse time for IR commands. display.IrPulseTime = 200; // Set default pulse time for IR commands.
return display; return display;
} }
return null; return null;
} }
} }
} }

View File

@@ -19,7 +19,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// Abstract base class for display devices that provides common display functionality /// Abstract base class for display devices that provides common display functionality
/// including power control, input switching, and routing capabilities. /// including power control, input switching, and routing capabilities.
/// </summary> /// </summary>
public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources, IHasFeedback
{ {
private RoutingInputPort _currentInputPort; private RoutingInputPort _currentInputPort;
@@ -73,13 +73,11 @@ namespace PepperDash.Essentials.Devices.Common.Displays
var handler = CurrentSourceChange; var handler = CurrentSourceChange;
if (handler != null) handler?.Invoke(_CurrentSourceInfo, ChangeType.WillChange);
handler(_CurrentSourceInfo, ChangeType.WillChange);
_CurrentSourceInfo = value; _CurrentSourceInfo = value;
if (handler != null) handler?.Invoke(_CurrentSourceInfo, ChangeType.DidChange);
handler(_CurrentSourceInfo, ChangeType.DidChange);
} }
} }
SourceListItem _CurrentSourceInfo; SourceListItem _CurrentSourceInfo;
@@ -160,6 +158,9 @@ namespace PepperDash.Essentials.Devices.Common.Displays
IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc); IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc);
IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc); IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc);
Feedbacks.Add(IsCoolingDownFeedback);
Feedbacks.Add(IsWarmingUpFeedback);
InputPorts = new RoutingPortCollection<RoutingInputPort>(); InputPorts = new RoutingPortCollection<RoutingInputPort>();
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem> CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
@@ -194,17 +195,8 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// Gets the collection of feedback objects for this display device. /// Gets the collection of feedback objects for this display device.
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public virtual FeedbackCollection<Feedback> Feedbacks public virtual FeedbackCollection<Feedback> Feedbacks { get; private set; } = new FeedbackCollection<Feedback>();
{
get
{
return new FeedbackCollection<Feedback>
{
IsCoolingDownFeedback,
IsWarmingUpFeedback
};
}
}
/// <summary> /// <summary>
/// Executes a switch to the specified input on the display device. Must be implemented by derived classes. /// Executes a switch to the specified input on the display device. Must be implemented by derived classes.
@@ -252,47 +244,35 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// <param name="joinMap">The join map configuration for the device.</param> /// <param name="joinMap">The join map configuration for the device.</param>
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap) protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); this.LogDebug("Linking to Trilist {ipId}", trilist.ID.ToString("X"));
Debug.LogMessage(LogEventLevel.Information, "Linking to Display: {0}", displayDevice.Name); this.LogDebug("Linking to Display: {displayName}", displayDevice.Name);
trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name; trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name;
var commMonitor = displayDevice as ICommunicationMonitor; if (displayDevice is ICommunicationMonitor commMonitor)
if (commMonitor != null)
{ {
commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]);
} }
// TODO: revisit this as there could be issues with this approach
var inputNumber = 0; var inputNumber = 0;
var inputKeys = new List<string>(); var inputKeys = new List<string>();
var inputNumberFeedback = new IntFeedback(() => inputNumber); var inputNumberFeedback = new IntFeedback("inputNumber", () => inputNumber);
// Two way feedbacks // Add input number feedback to the device feedback collection to keep it around...
var twoWayDisplay = displayDevice as TwoWayDisplayBase; Feedbacks.Add(inputNumberFeedback);
if (twoWayDisplay != null) // Two way feedbacks
if (displayDevice is TwoWayDisplayBase twoWayDisplay)
{ {
trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true); trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true);
twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Information, "CurrentInputFeedback_OutputChange {0}", a.StringValue); twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => this.LogDebug("CurrentInputFeedback_OutputChange {input}", a.StringValue);
inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]); inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]);
}
// Power Off twoWayDisplay.PowerIsOnFeedback.OutputChange += (o, a) =>
trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () =>
{
inputNumber = 102;
inputNumberFeedback.FireUpdate();
displayDevice.PowerOff();
});
var twoWayDisplayDevice = displayDevice as TwoWayDisplayBase;
if (twoWayDisplayDevice != null)
{
twoWayDisplayDevice.PowerIsOnFeedback.OutputChange += (o, a) =>
{ {
if (!a.BoolValue) if (!a.BoolValue)
{ {
@@ -307,10 +287,18 @@ namespace PepperDash.Essentials.Devices.Common.Displays
} }
}; };
twoWayDisplayDevice.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); twoWayDisplay.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]);
twoWayDisplayDevice.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); twoWayDisplay.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]);
} }
// Power Off
trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () =>
{
inputNumber = 102;
inputNumberFeedback.FireUpdate();
displayDevice.PowerOff();
});
// PowerOn // PowerOn
trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () =>
{ {
@@ -320,44 +308,61 @@ namespace PepperDash.Essentials.Devices.Common.Displays
}); });
for (int i = 0; i < displayDevice.InputPorts.Count; i++) for (int i = 0; i < displayDevice.InputPorts.Count; i++)
{ {
if (i < joinMap.InputNamesOffset.JoinSpan) var localindex = i;
if (localindex >= joinMap.InputNamesOffset.JoinSpan)
{ {
inputKeys.Add(displayDevice.InputPorts[i].Key); this.LogWarning("Device has {inputCount} inputs. The Join Map allows up to {joinSpan} inputs. Discarding inputs {discardStart} - {discardEnd} from bridge.",
var tempKey = inputKeys.ElementAt(i); displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, localindex + 1, displayDevice.InputPorts.Count);
trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + i),
() => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector)); continue;
Debug.LogMessage(LogEventLevel.Verbose, displayDevice, "Setting Input Select Action on Digital Join {0} to Input: {1}",
joinMap.InputSelectOffset.JoinNumber + i, displayDevice.InputPorts[tempKey].Key.ToString());
trilist.StringInput[(ushort)(joinMap.InputNamesOffset.JoinNumber + i)].StringValue = displayDevice.InputPorts[i].Key.ToString();
} }
else else
Debug.LogMessage(LogEventLevel.Information, displayDevice, "Device has {0} inputs. The Join Map allows up to {1} inputs. Discarding inputs {2} - {3} from bridge.", {
displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, i + 1, displayDevice.InputPorts.Count); inputKeys.Add(displayDevice.InputPorts[localindex].Key);
var tempKey = inputKeys.ElementAt(localindex);
trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + localindex), () => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector));
this.LogDebug("Setting Input Select Action on Digital Join {joinNumber} to Input: {input}", joinMap.InputSelectOffset.JoinNumber + localindex, displayDevice.InputPorts[tempKey].Key);
trilist.SetString((uint)(joinMap.InputNamesOffset.JoinNumber + localindex), displayDevice.InputPorts[localindex].Key);
}
} }
Debug.LogMessage(LogEventLevel.Verbose, displayDevice, "Setting Input Select Action on Analog Join {0}", joinMap.InputSelect); this.LogDebug("Setting Input Select Action on Analog Join {inputSelectJoin}", joinMap.InputSelect);
trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (a) =>
trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (requestedInput) =>
{ {
if (a == 0) if (requestedInput == 0)
{ {
displayDevice.PowerOff(); displayDevice.PowerOff();
inputNumber = 0; inputNumber = 0;
return;
} }
else if (a > 0 && a < displayDevice.InputPorts.Count && a != inputNumber)
// using 1-based indexing for inputs coming from SIMPL, so need to check if the input is <= the count, not <
if (requestedInput > 0 && requestedInput <= displayDevice.InputPorts.Count && requestedInput != inputNumber)
{ {
displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(a - 1).Selector); displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(requestedInput - 1).Selector);
inputNumber = a;
inputNumber = requestedInput;
return;
} }
else if (a == 102)
if (requestedInput == 102)
{ {
displayDevice.PowerToggle(); displayDevice.PowerToggle();
return;
}
if (displayDevice is TwoWayDisplayBase)
{
inputNumberFeedback?.FireUpdate();
} }
if (twoWayDisplay != null)
inputNumberFeedback.FireUpdate();
}); });
@@ -430,95 +435,4 @@ namespace PepperDash.Essentials.Devices.Common.Displays
} }
} }
/// <summary>
/// Abstract base class for two-way display devices that provide feedback capabilities.
/// Extends DisplayBase with routing feedback and power control feedback functionality.
/// </summary>
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
{
/// <summary>
/// Gets feedback for the current input selection on the display.
/// </summary>
public StringFeedback CurrentInputFeedback { get; private set; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the current input feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
/// <summary>
/// Gets feedback indicating whether the display is currently powered on.
/// </summary>
public BoolFeedback PowerIsOnFeedback { get; protected set; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the power state feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
/// <summary>
/// Gets the default mock display instance for testing and development purposes.
/// </summary>
public static MockDisplay DefaultDisplay
{
get
{
if (_DefaultDisplay == null)
_DefaultDisplay = new MockDisplay("default", "Default Display");
return _DefaultDisplay;
}
}
static MockDisplay _DefaultDisplay;
/// <summary>
/// Initializes a new instance of the TwoWayDisplayBase class.
/// </summary>
/// <param name="key">The unique key identifier for this display device.</param>
/// <param name="name">The friendly name for this display device.</param>
public TwoWayDisplayBase(string key, string name)
: base(key, name)
{
CurrentInputFeedback = new StringFeedback(CurrentInputFeedbackFunc);
WarmupTime = 7000;
CooldownTime = 15000;
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
Feedbacks.Add(CurrentInputFeedback);
Feedbacks.Add(PowerIsOnFeedback);
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
}
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
{
if (UsageTracker != null)
{
if (PowerIsOnFeedback.BoolValue)
UsageTracker.StartDeviceUsage();
else
UsageTracker.EndDeviceUsage();
}
}
/// <summary>
/// Event that is raised when a numeric switch change occurs on the display.
/// </summary>
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
/// <summary>
/// Raise an event when the status of a switch object changes.
/// </summary>
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
protected void OnSwitchChange(RoutingNumericEventArgs e)
{
var newEvent = NumericSwitchChange;
if (newEvent != null) newEvent(this, e);
}
}
} }

View File

@@ -1,38 +1,89 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Devices.Displays namespace PepperDash.Essentials.Devices.Displays
{ {
/// <summary> /// <summary>
/// Defines the contract for IInputHdmi1 /// Defines the contract for IInputHdmi1
/// </summary> /// </summary>
public interface IInputHdmi1 { void InputHdmi1(); } [Obsolete()]
public interface IInputHdmi1
{
/// <summary>
/// Switches to HDMI 1 input
/// </summary>
void InputHdmi1();
}
/// <summary> /// <summary>
/// Defines the contract for IInputHdmi2 /// Defines the contract for IInputHdmi2
/// </summary> /// </summary>
public interface IInputHdmi2 { void InputHdmi2(); } [Obsolete()]
public interface IInputHdmi2
{
/// <summary>
/// Switches to HDMI 2 input
/// </summary>
void InputHdmi2();
}
/// <summary> /// <summary>
/// Defines the contract for IInputHdmi3 /// Defines the contract for IInputHdmi3
/// </summary> /// </summary>
public interface IInputHdmi3 { void InputHdmi3(); } [Obsolete()]
public interface IInputHdmi3
{
/// <summary>
/// Switches to HDMI 3 input
/// </summary>
void InputHdmi3();
}
/// <summary> /// <summary>
/// Defines the contract for IInputHdmi4 /// Defines the contract for IInputHdmi4
/// </summary> /// </summary>
public interface IInputHdmi4 { void InputHdmi4(); } [Obsolete()]
public interface IInputHdmi4
{
/// <summary>
/// Switches to HDMI 4 input
/// </summary>
void InputHdmi4();
}
/// <summary> /// <summary>
/// Defines the contract for IInputDisplayPort1 /// Defines the contract for IInputDisplayPort1
/// </summary> /// </summary>
public interface IInputDisplayPort1 { void InputDisplayPort1(); } [Obsolete()]
public interface IInputDisplayPort1
{
/// <summary>
/// Switches to DisplayPort 1 input
/// </summary>
void InputDisplayPort1();
}
/// <summary> /// <summary>
/// Defines the contract for IInputDisplayPort2 /// Defines the contract for IInputDisplayPort2
/// </summary> /// </summary>
public interface IInputDisplayPort2 { void InputDisplayPort2(); } [Obsolete()]
public interface IInputDisplayPort2
{
/// <summary>
/// Switches to DisplayPort 2 input
/// </summary>
void InputDisplayPort2();
}
/// <summary> /// <summary>
/// Defines the contract for IInputVga1 /// Defines the contract for IInputVga1
/// </summary> /// </summary>
public interface IInputVga1 { void InputVga1(); } [Obsolete()]
public interface IInputVga1
{
/// <summary>
/// Switches to VGA 1 input
/// </summary>
void InputVga1();
}
} }

View File

@@ -16,16 +16,19 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// Represents a MockDisplay /// Represents a MockDisplay
/// </summary> /// </summary>
public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback
{ {
/// <summary> /// <summary>
/// Gets or sets the Inputs /// Gets or sets the Inputs
/// </summary> /// </summary>
public ISelectableItems<string> Inputs { get; private set; } public ISelectableItems<string> Inputs { get; private set; }
bool _PowerIsOn; bool _PowerIsOn;
bool _IsWarmingUp; bool _IsWarmingUp;
bool _IsCoolingDown; bool _IsCoolingDown;
/// <summary>
/// Gets the power is on feedback function
/// </summary>
protected override Func<bool> PowerIsOnFeedbackFunc protected override Func<bool> PowerIsOnFeedbackFunc
{ {
get get
@@ -34,8 +37,12 @@ namespace PepperDash.Essentials.Devices.Common.Displays
{ {
return _PowerIsOn; return _PowerIsOn;
}; };
} } }
protected override Func<bool> IsCoolingDownFeedbackFunc }
/// <summary>
/// Gets the is cooling down feedback function
/// </summary>
protected override Func<bool> IsCoolingDownFeedbackFunc
{ {
get get
{ {
@@ -45,7 +52,10 @@ namespace PepperDash.Essentials.Devices.Common.Displays
}; };
} }
} }
protected override Func<bool> IsWarmingUpFeedbackFunc /// <summary>
/// Gets the is warming up feedback function
/// </summary>
protected override Func<bool> IsWarmingUpFeedbackFunc
{ {
get get
{ {
@@ -55,120 +65,128 @@ namespace PepperDash.Essentials.Devices.Common.Displays
}; };
} }
} }
/// <summary>
/// Gets the current input feedback function
/// </summary>
protected override Func<string> CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } } protected override Func<string> CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } }
int VolumeHeldRepeatInterval = 200; int VolumeHeldRepeatInterval = 200;
ushort VolumeInterval = 655; ushort VolumeInterval = 655;
ushort _FakeVolumeLevel = 31768; ushort _FakeVolumeLevel = 31768;
bool _IsMuted; bool _IsMuted;
public MockDisplay(string key, string name) /// <summary>
: base(key, name) /// Initializes a new instance of the MockDisplay class
{ /// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
public MockDisplay(string key, string name)
: base(key, name)
{
Inputs = new MockDisplayInputs Inputs = new MockDisplayInputs
{ {
Items = new Dictionary<string, ISelectableItem> Items = new Dictionary<string, ISelectableItem>
{ {
{ "HDMI1", new MockDisplayInput ( "HDMI1", "HDMI 1",this ) }, { "HDMI1", new MockDisplayInput ( "HDMI1", "HDMI 1",this ) },
{ "HDMI2", new MockDisplayInput ("HDMI2", "HDMI 2",this ) }, { "HDMI2", new MockDisplayInput ("HDMI2", "HDMI 2",this ) },
{ "HDMI3", new MockDisplayInput ("HDMI3", "HDMI 3",this ) }, { "HDMI3", new MockDisplayInput ("HDMI3", "HDMI 3",this ) },
{ "HDMI4", new MockDisplayInput ("HDMI4", "HDMI 4",this )}, { "HDMI4", new MockDisplayInput ("HDMI4", "HDMI 4",this )},
{ "DP", new MockDisplayInput ("DP", "DisplayPort", this ) } { "DP", new MockDisplayInput ("DP", "DisplayPort", this ) }
} }
}; };
Inputs.CurrentItemChanged += (o, a) => CurrentInputFeedback.FireUpdate(); Inputs.CurrentItemChanged += (o, a) => CurrentInputFeedback.FireUpdate();
var hdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI1", this);
var hdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI2", this);
var hdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI3", this);
var hdmiIn4 = new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI4", this);
var dpIn = new RoutingInputPort(RoutingPortNames.DisplayPortIn, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.DisplayPort, "DP", this);
InputPorts.AddRange(new[] { hdmiIn1, hdmiIn2, hdmiIn3, hdmiIn4, dpIn });
VolumeLevelFeedback = new IntFeedback(() => { return _FakeVolumeLevel; }); var hdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.AudioVideo,
MuteFeedback = new BoolFeedback("MuteOn", () => _IsMuted); eRoutingPortConnectionType.Hdmi, "HDMI1", this);
var hdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI2", this);
var hdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI3", this);
var hdmiIn4 = new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, "HDMI4", this);
var dpIn = new RoutingInputPort(RoutingPortNames.DisplayPortIn, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.DisplayPort, "DP", this);
InputPorts.AddRange(new[] { hdmiIn1, hdmiIn2, hdmiIn3, hdmiIn4, dpIn });
VolumeLevelFeedback = new IntFeedback("volume", () => { return _FakeVolumeLevel; });
MuteFeedback = new BoolFeedback("muteOn", () => _IsMuted);
WarmupTime = 10000; WarmupTime = 10000;
CooldownTime = 10000; CooldownTime = 10000;
} }
/// <summary> /// <summary>
/// PowerOn method /// PowerOn method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override void PowerOn() public override void PowerOn()
{ {
if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown)
{ {
_IsWarmingUp = true; _IsWarmingUp = true;
IsWarmingUpFeedback.InvokeFireUpdate(); IsWarmingUpFeedback.InvokeFireUpdate();
// Fake power-up cycle // Fake power-up cycle
WarmupTimer = new CTimer(o => WarmupTimer = new CTimer(o =>
{ {
_IsWarmingUp = false; _IsWarmingUp = false;
_PowerIsOn = true; _PowerIsOn = true;
IsWarmingUpFeedback.InvokeFireUpdate(); IsWarmingUpFeedback.InvokeFireUpdate();
PowerIsOnFeedback.InvokeFireUpdate(); PowerIsOnFeedback.InvokeFireUpdate();
}, WarmupTime); }, WarmupTime);
} }
} }
/// <summary> /// <summary>
/// PowerOff method /// PowerOff method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override void PowerOff() public override void PowerOff()
{ {
// If a display has unreliable-power off feedback, just override this and // If a display has unreliable-power off feedback, just override this and
// remove this check. // remove this check.
if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown)
{ {
_IsCoolingDown = true; _IsCoolingDown = true;
IsCoolingDownFeedback.InvokeFireUpdate(); IsCoolingDownFeedback.InvokeFireUpdate();
// Fake cool-down cycle // Fake cool-down cycle
CooldownTimer = new CTimer(o => CooldownTimer = new CTimer(o =>
{ {
Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this); Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this);
_IsCoolingDown = false; _IsCoolingDown = false;
IsCoolingDownFeedback.InvokeFireUpdate(); IsCoolingDownFeedback.InvokeFireUpdate();
_PowerIsOn = false; _PowerIsOn = false;
PowerIsOnFeedback.InvokeFireUpdate(); PowerIsOnFeedback.InvokeFireUpdate();
}, CooldownTime); }, CooldownTime);
} }
} }
/// <summary>
/// PowerToggle method
/// </summary>
/// <inheritdoc />
public override void PowerToggle()
{
if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue)
PowerOff();
else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue)
PowerOn();
}
/// <summary> /// <summary>
/// ExecuteSwitch method /// PowerToggle method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override void ExecuteSwitch(object selector) public override void PowerToggle()
{ {
if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue)
PowerOff();
else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue)
PowerOn();
}
/// <summary>
/// ExecuteSwitch method
/// </summary>
/// <inheritdoc />
public override void ExecuteSwitch(object selector)
{
try try
{ {
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector); Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector);
if (!_PowerIsOn) if (!_PowerIsOn)
{ {
PowerOn(); PowerOn();
} }
if (!Inputs.Items.TryGetValue(selector.ToString(), out var input)) if (!Inputs.Items.TryGetValue(selector.ToString(), out var input))
return; return;
@@ -184,13 +202,14 @@ namespace PepperDash.Essentials.Devices.Common.Displays
if (inputPort == null) if (inputPort == null)
{ {
Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector); Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector);
return; return;
} }
Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort); Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort);
CurrentInputPort = inputPort; CurrentInputPort = inputPort;
} catch (Exception ex) }
catch (Exception ex)
{ {
Debug.LogMessage(ex, "Error making switch: {Exception}", this, ex); Debug.LogMessage(ex, "Error making switch: {Exception}", this, ex);
} }
@@ -201,14 +220,14 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// </summary> /// </summary>
public void SetInput(string selector) public void SetInput(string selector)
{ {
ISelectableItem currentInput = null; ISelectableItem currentInput = null;
try
{
currentInput = Inputs.Items.SingleOrDefault(Inputs => Inputs.Value.IsSelected).Value;
}
catch { }
try
{
currentInput = Inputs.Items.SingleOrDefault(Inputs => Inputs.Value.IsSelected).Value;
}
catch { }
if (currentInput != null) if (currentInput != null)
{ {
@@ -216,12 +235,12 @@ namespace PepperDash.Essentials.Devices.Common.Displays
currentInput.IsSelected = false; currentInput.IsSelected = false;
} }
if (!Inputs.Items.TryGetValue(selector, out var input)) if (!Inputs.Items.TryGetValue(selector, out var input))
return; return;
input.IsSelected = true; input.IsSelected = true;
Inputs.CurrentItem = selector; Inputs.CurrentItem = selector;
} }
@@ -232,37 +251,37 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// </summary> /// </summary>
public IntFeedback VolumeLevelFeedback { get; private set; } public IntFeedback VolumeLevelFeedback { get; private set; }
/// <summary> /// <summary>
/// SetVolume method /// SetVolume method
/// </summary> /// </summary>
public void SetVolume(ushort level) public void SetVolume(ushort level)
{ {
_FakeVolumeLevel = level; _FakeVolumeLevel = level;
VolumeLevelFeedback.InvokeFireUpdate(); VolumeLevelFeedback.InvokeFireUpdate();
} }
/// <summary> /// <summary>
/// MuteOn method /// MuteOn method
/// </summary> /// </summary>
public void MuteOn() public void MuteOn()
{ {
_IsMuted = true; _IsMuted = true;
MuteFeedback.InvokeFireUpdate(); MuteFeedback.InvokeFireUpdate();
} }
/// <summary> /// <summary>
/// MuteOff method /// MuteOff method
/// </summary> /// </summary>
public void MuteOff() public void MuteOff()
{ {
_IsMuted = false; _IsMuted = false;
MuteFeedback.InvokeFireUpdate(); MuteFeedback.InvokeFireUpdate();
} }
/// <summary> /// <summary>
/// Gets or sets the MuteFeedback /// Gets or sets the MuteFeedback
/// </summary> /// </summary>
public BoolFeedback MuteFeedback { get; private set; } public BoolFeedback MuteFeedback { get; private set; }
#endregion #endregion
@@ -273,74 +292,77 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// VolumeUp method /// VolumeUp method
/// </summary> /// </summary>
public void VolumeUp(bool pressRelease) public void VolumeUp(bool pressRelease)
{ {
//while (pressRelease) //while (pressRelease)
//{ //{
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Down {0}", pressRelease); Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Down {0}", pressRelease);
if (pressRelease) if (pressRelease)
{ {
var newLevel = _FakeVolumeLevel + VolumeInterval; var newLevel = _FakeVolumeLevel + VolumeInterval;
SetVolume((ushort)newLevel); SetVolume((ushort)newLevel);
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); CrestronEnvironment.Sleep(VolumeHeldRepeatInterval);
} }
//} //}
} }
/// <summary> /// <summary>
/// VolumeDown method /// VolumeDown method
/// </summary> /// </summary>
public void VolumeDown(bool pressRelease) public void VolumeDown(bool pressRelease)
{ {
//while (pressRelease) //while (pressRelease)
//{ //{
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Up {0}", pressRelease); Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Up {0}", pressRelease);
if (pressRelease) if (pressRelease)
{ {
var newLevel = _FakeVolumeLevel - VolumeInterval; var newLevel = _FakeVolumeLevel - VolumeInterval;
SetVolume((ushort)newLevel); SetVolume((ushort)newLevel);
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); CrestronEnvironment.Sleep(VolumeHeldRepeatInterval);
} }
//} //}
} }
/// <summary> /// <summary>
/// MuteToggle method /// MuteToggle method
/// </summary> /// </summary>
public void MuteToggle() public void MuteToggle()
{ {
_IsMuted = !_IsMuted; _IsMuted = !_IsMuted;
MuteFeedback.InvokeFireUpdate(); MuteFeedback.InvokeFireUpdate();
} }
#endregion #endregion
/// <summary> /// <summary>
/// LinkToApi method /// LinkToApi method
/// </summary> /// </summary>
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{ {
LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge);
} }
} }
/// <summary> /// <summary>
/// Represents a MockDisplayFactory /// Represents a MockDisplayFactory
/// </summary> /// </summary>
public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay> public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay>
{ {
public MockDisplayFactory() /// <summary>
{ /// Initializes a new instance of the MockDisplayFactory class
TypeNames = new List<string>() { "mockdisplay" , "mockdisplay2" }; /// </summary>
} public MockDisplayFactory()
{
TypeNames = new List<string>() { "mockdisplay", "mockdisplay2" };
}
/// <summary> /// <summary>
/// BuildDevice method /// BuildDevice method
/// </summary> /// </summary>
/// <inheritdoc /> /// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) public override EssentialsDevice BuildDevice(DeviceConfig dc)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device"); Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");
return new MockDisplay(dc.Key, dc.Name); return new MockDisplay(dc.Key, dc.Name);
} }
} }
} }

View File

@@ -1,10 +1,6 @@
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Displays namespace PepperDash.Essentials.Devices.Common.Displays
{ {
@@ -15,6 +11,9 @@ namespace PepperDash.Essentials.Devices.Common.Displays
{ {
private Dictionary<string, ISelectableItem> _items; private Dictionary<string, ISelectableItem> _items;
/// <summary>
/// Gets or sets the collection of selectable items
/// </summary>
public Dictionary<string, ISelectableItem> Items public Dictionary<string, ISelectableItem> Items
{ {
get get
@@ -34,8 +33,11 @@ namespace PepperDash.Essentials.Devices.Common.Displays
private string _currentItem; private string _currentItem;
/// <summary>
/// Gets or sets the currently selected item
/// </summary>
public string CurrentItem public string CurrentItem
{ {
get get
{ {
return _currentItem; return _currentItem;
@@ -51,7 +53,13 @@ namespace PepperDash.Essentials.Devices.Common.Displays
} }
} }
/// <summary>
/// Occurs when the items collection is updated
/// </summary>
public event EventHandler ItemsUpdated; public event EventHandler ItemsUpdated;
/// <summary>
/// Occurs when the current item changes
/// </summary>
public event EventHandler CurrentItemChanged; public event EventHandler CurrentItemChanged;
} }
@@ -63,7 +71,10 @@ namespace PepperDash.Essentials.Devices.Common.Displays
private MockDisplay _parent; private MockDisplay _parent;
private bool _isSelected; private bool _isSelected;
/// <summary>
/// Gets or sets a value indicating whether this input is selected
/// </summary>
public bool IsSelected public bool IsSelected
{ {
get get
@@ -91,8 +102,17 @@ namespace PepperDash.Essentials.Devices.Common.Displays
/// </summary> /// </summary>
public string Key { get; set; } public string Key { get; set; }
/// <summary>
/// Occurs when this item is updated
/// </summary>
public event EventHandler ItemUpdated; public event EventHandler ItemUpdated;
/// <summary>
/// Initializes a new instance of the MockDisplayInput class
/// </summary>
/// <param name="key">The input key</param>
/// <param name="name">The input name</param>
/// <param name="parent">The parent mock display</param>
public MockDisplayInput(string key, string name, MockDisplay parent) public MockDisplayInput(string key, string name, MockDisplay parent)
{ {
Key = key; Key = key;
@@ -107,7 +127,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
{ {
if (!_parent.PowerIsOnFeedback.BoolValue) _parent.PowerOn(); if (!_parent.PowerIsOnFeedback.BoolValue) _parent.PowerOn();
foreach(var input in _parent.Inputs.Items) foreach (var input in _parent.Inputs.Items)
{ {
input.Value.IsSelected = input.Key == this.Key; input.Value.IsSelected = input.Key == this.Key;
} }

View File

@@ -1,14 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.CrestronIO; using PepperDash.Essentials.Core.CrestronIO;
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Shades namespace PepperDash.Essentials.Devices.Common.Shades
@@ -28,6 +25,9 @@ namespace PepperDash.Essentials.Devices.Common.Shades
ISwitchedOutput LowerRelay; ISwitchedOutput LowerRelay;
ISwitchedOutput LatchedRelay; ISwitchedOutput LatchedRelay;
/// <summary>
/// Gets or sets the InUpPosition
/// </summary>
public bool InUpPosition public bool InUpPosition
{ {
get { return _isInUpPosition; } get { return _isInUpPosition; }
@@ -41,10 +41,12 @@ namespace PepperDash.Essentials.Devices.Common.Shades
} }
private bool _isInUpPosition { get; set; } private bool _isInUpPosition { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Type /// Gets or sets the Type
/// </summary> /// </summary>
public eScreenLiftControlType Type { get; private set; } public eScreenLiftControlType Type { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the Mode /// Gets or sets the Mode
/// </summary> /// </summary>
@@ -54,13 +56,20 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// Gets or sets the DisplayDeviceKey /// Gets or sets the DisplayDeviceKey
/// </summary> /// </summary>
public string DisplayDeviceKey { get; private set; } public string DisplayDeviceKey { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the IsInUpPosition /// Gets or sets the IsInUpPosition
/// </summary> /// </summary>
public BoolFeedback IsInUpPosition { get; private set; } public BoolFeedback IsInUpPosition { get; private set; }
/// <summary>
/// Event that fires when the position changes
/// </summary>
public event EventHandler<EventArgs> PositionChanged; public event EventHandler<EventArgs> PositionChanged;
/// <summary>
/// Constructor for ScreenLiftController
/// </summary>
public ScreenLiftController(string key, string name, ScreenLiftControllerConfigProperties config) public ScreenLiftController(string key, string name, ScreenLiftControllerConfigProperties config)
: base(key, name) : base(key, name)
{ {
@@ -69,7 +78,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
Mode = Config.Mode; Mode = Config.Mode;
Type = Config.Type; Type = Config.Type;
IsInUpPosition = new BoolFeedback(() => _isInUpPosition); IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
switch (Mode) switch (Mode)
{ {
@@ -206,14 +215,14 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// <summary> /// <summary>
/// Attempts to get the port on teh specified device from config /// Attempts to get the port on teh specified device from config
/// </summary> /// </summary>
/// <param name="relayConfig"></param> /// <param name="relayKey"></param>
/// <returns></returns> /// <returns></returns>
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey) ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
{ {
var portDevice = DeviceManager.GetDeviceForKey(relayKey); var portDevice = DeviceManager.GetDeviceForKey(relayKey);
if (portDevice != null) if (portDevice != null)
{ {
return (portDevice as ISwitchedOutput); return portDevice as ISwitchedOutput;
} }
else else
{ {
@@ -238,58 +247,14 @@ namespace PepperDash.Essentials.Devices.Common.Shades
} }
/// <summary>
/// Represents a ScreenLiftControllerConfigProperties
/// </summary>
public class ScreenLiftControllerConfigProperties
{
[JsonProperty("displayDeviceKey")]
/// <summary>
/// Gets or sets the DisplayDeviceKey
/// </summary>
public string DisplayDeviceKey { get; set; }
[JsonProperty("type")]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary>
/// Gets or sets the Type
/// </summary>
public eScreenLiftControlType Type { get; set; }
[JsonProperty("mode")]
[JsonConverter(typeof(StringEnumConverter))]
/// <summary>
/// Gets or sets the Mode
/// </summary>
public eScreenLiftControlMode Mode { get; set; }
[JsonProperty("relays")]
public Dictionary<string,ScreenLiftRelaysConfig> Relays { get; set; }
}
/// <summary>
/// Represents a ScreenLiftRelaysConfig
/// </summary>
public class ScreenLiftRelaysConfig
{
[JsonProperty("deviceKey")]
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
public string DeviceKey { get; set; }
[JsonProperty("pulseTimeInMs")]
/// <summary>
/// Gets or sets the PulseTimeInMs
/// </summary>
public int PulseTimeInMs { get; set; }
}
/// <summary> /// <summary>
/// Represents a ScreenLiftControllerFactory /// Represents a ScreenLiftControllerFactory
/// </summary> /// </summary>
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade> public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
{ {
/// <summary>
/// Constructor for ScreenLiftControllerFactory
/// </summary>
public ScreenLiftControllerFactory() public ScreenLiftControllerFactory()
{ {
TypeNames = new List<string>() { "screenliftcontroller" }; TypeNames = new List<string>() { "screenliftcontroller" };
@@ -307,13 +272,4 @@ namespace PepperDash.Essentials.Devices.Common.Shades
return new ScreenLiftController(dc.Key, dc.Name, props); return new ScreenLiftController(dc.Key, dc.Name, props);
} }
} }
/// <summary>
/// Enumeration of eScreenLiftControlMode values
/// </summary>
public enum eScreenLiftControlMode
{
momentary,
latched
}
} }

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
namespace PepperDash.Essentials.Devices.Common.Shades
{
/// <summary>
/// Represents a ScreenLiftControllerConfigProperties
/// </summary>
public class ScreenLiftControllerConfigProperties
{
/// <summary>
/// Gets or sets the DisplayDeviceKey
/// </summary>
[JsonProperty("displayDeviceKey")]
public string DisplayDeviceKey { get; set; }
/// <summary>
/// Gets or sets the Type
/// </summary>
[JsonProperty("type")]
[JsonConverter(typeof(StringEnumConverter))]
public eScreenLiftControlType Type { get; set; }
/// <summary>
/// Gets or sets the Mode
/// </summary>
[JsonProperty("mode")]
[JsonConverter(typeof(StringEnumConverter))]
public eScreenLiftControlMode Mode { get; set; }
/// <summary>
/// Gets or sets the Relays
/// </summary>
[JsonProperty("relays")]
public Dictionary<string, ScreenLiftRelaysConfig> Relays { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Shades
{
/// <summary>
/// Represents a ScreenLiftRelaysConfig
/// </summary>
public class ScreenLiftRelaysConfig
{
/// <summary>
/// Gets or sets the DeviceKey
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
/// <summary>
/// Gets or sets the PulseTimeInMs
/// </summary>
[JsonProperty("pulseTimeInMs")]
public int PulseTimeInMs { get; set; }
}
}

View File

@@ -0,0 +1,95 @@
using System;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Devices.Common.Displays
{
/// <summary>
/// Abstract base class for two-way display devices that provide feedback capabilities.
/// Extends DisplayBase with routing feedback and power control feedback functionality.
/// </summary>
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
{
/// <summary>
/// Gets feedback for the current input selection on the display.
/// </summary>
public StringFeedback CurrentInputFeedback { get; private set; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the current input feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
/// <summary>
/// Gets feedback indicating whether the display is currently powered on.
/// </summary>
public BoolFeedback PowerIsOnFeedback { get; protected set; }
/// <summary>
/// Abstract function that must be implemented by derived classes to provide the power state feedback value.
/// Must be implemented by concrete sub-classes.
/// </summary>
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
/// <summary>
/// Gets the default mock display instance for testing and development purposes.
/// </summary>
public static MockDisplay DefaultDisplay
{
get
{
if (_DefaultDisplay == null)
_DefaultDisplay = new MockDisplay("default", "Default Display");
return _DefaultDisplay;
}
}
static MockDisplay _DefaultDisplay;
/// <summary>
/// Initializes a new instance of the TwoWayDisplayBase class.
/// </summary>
/// <param name="key">The unique key identifier for this display device.</param>
/// <param name="name">The friendly name for this display device.</param>
public TwoWayDisplayBase(string key, string name)
: base(key, name)
{
CurrentInputFeedback = new StringFeedback("currentInput", CurrentInputFeedbackFunc);
WarmupTime = 7000;
CooldownTime = 15000;
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
Feedbacks.Add(CurrentInputFeedback);
Feedbacks.Add(PowerIsOnFeedback);
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
}
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
{
if (UsageTracker != null)
{
if (PowerIsOnFeedback.BoolValue)
UsageTracker.StartDeviceUsage();
else
UsageTracker.EndDeviceUsage();
}
}
/// <summary>
/// Event that is raised when a numeric switch change occurs on the display.
/// </summary>
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
/// <summary>
/// Raise an event when the status of a switch object changes.
/// </summary>
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
protected void OnSwitchChange(RoutingNumericEventArgs e)
{
NumericSwitchChange?.Invoke(this, e);
}
}
}

View File

@@ -0,0 +1,17 @@
namespace PepperDash.Essentials.Devices.Common.Shades
{
/// <summary>
/// Enumeration of eScreenLiftControlMode values
/// </summary>
public enum eScreenLiftControlMode
{
/// <summary>
/// Momentary control mode for screen lift
/// </summary>
momentary,
/// <summary>
/// Latched control mode for screen lift
/// </summary>
latched
}
}

View File

@@ -1,22 +1,26 @@
using PepperDash.Core; using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Serilog.Events; using Serilog.Events;
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Devices.Common.Generic namespace PepperDash.Essentials.Devices.Common.Generic
{ {
/// <summary> /// <summary>
/// Represents a GenericSink /// Represents a GenericSink
/// </summary> /// </summary>
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort
{ {
/// <summary>
/// Initializes a new instance of the GenericSink class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
public GenericSink(string key, string name) : base(key, name) public GenericSink(string key, string name) : base(key, name)
{ {
InputPorts = new RoutingPortCollection<RoutingInputPort>(); InputPorts = new RoutingPortCollection<RoutingInputPort>();
var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this);
InputPorts.Add(inputPort); InputPorts.Add(inputPort);
} }
@@ -35,10 +39,12 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// <summary> /// <summary>
/// Gets or sets the CurrentSourceInfo /// Gets or sets the CurrentSourceInfo
/// </summary> /// </summary>
public SourceListItem CurrentSourceInfo { public SourceListItem CurrentSourceInfo
{
get => _currentSource; get => _currentSource;
set { set
if(value == _currentSource) {
if (value == _currentSource)
{ {
return; return;
} }
@@ -51,9 +57,24 @@ namespace PepperDash.Essentials.Devices.Common.Generic
} }
} }
/// <summary>
/// Gets the current input port
/// </summary>
public RoutingInputPort CurrentInputPort => InputPorts[0]; public RoutingInputPort CurrentInputPort => InputPorts[0];
/// <summary>
/// Event fired when the current source changes
/// </summary>
public event SourceInfoChangeHandler CurrentSourceChange; public event SourceInfoChangeHandler CurrentSourceChange;
/// <inheritdoc />
public event InputChangedEventHandler InputChanged;
/// <inheritdoc />
public void ExecuteSwitch(object inputSelector)
{
throw new System.NotImplementedException();
}
} }
/// <summary> /// <summary>
@@ -61,6 +82,9 @@ namespace PepperDash.Essentials.Devices.Common.Generic
/// </summary> /// </summary>
public class GenericSinkFactory : EssentialsDeviceFactory<GenericSink> public class GenericSinkFactory : EssentialsDeviceFactory<GenericSink>
{ {
/// <summary>
/// Initializes a new instance of the GenericSinkFactory class
/// </summary>
public GenericSinkFactory() public GenericSinkFactory()
{ {
TypeNames = new List<string>() { "genericsink", "genericdestination" }; TypeNames = new List<string>() { "genericsink", "genericdestination" };

View File

@@ -1,10 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
@@ -13,38 +7,43 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common
{ {
/// <summary> /// <summary>
/// Represents a GenericSource /// Represents a GenericSource
/// </summary> /// </summary>
public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking
{ {
/// <summary> /// <summary>
/// Gets or sets the DisplayUiType /// Gets or sets the DisplayUiType
/// </summary> /// </summary>
public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } } public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } }
/// <summary>
/// Initializes a new instance of the GenericSource class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
public GenericSource(string key, string name) public GenericSource(string key, string name)
: base(key, name) : base(key, name)
{ {
AnyOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, AnyOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.Hdmi, null, this); eRoutingPortConnectionType.Hdmi, null, this);
OutputPorts = new RoutingPortCollection<RoutingOutputPort> { AnyOut }; OutputPorts = new RoutingPortCollection<RoutingOutputPort> { AnyOut };
} }
#region IRoutingOutputs Members #region IRoutingOutputs Members
/// <summary> /// <summary>
/// Gets or sets the AnyOut /// Gets or sets the AnyOut
/// </summary> /// </summary>
public RoutingOutputPort AnyOut { get; private set; } public RoutingOutputPort AnyOut { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the OutputPorts /// Gets or sets the OutputPorts
/// </summary> /// </summary>
public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; private set; } public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; private set; }
#endregion #endregion
#region IUsageTracking Members #region IUsageTracking Members
@@ -54,13 +53,16 @@ namespace PepperDash.Essentials.Devices.Common
public UsageTracking UsageTracker { get; set; } public UsageTracking UsageTracker { get; set; }
#endregion #endregion
} }
/// <summary> /// <summary>
/// Represents a GenericSourceFactory /// Represents a GenericSourceFactory
/// </summary> /// </summary>
public class GenericSourceFactory : EssentialsDeviceFactory<GenericSource> public class GenericSourceFactory : EssentialsDeviceFactory<GenericSource>
{ {
/// <summary>
/// Initializes a new instance of the GenericSourceFactory class
/// </summary>
public GenericSourceFactory() public GenericSourceFactory()
{ {
TypeNames = new List<string>() { "genericsource" }; TypeNames = new List<string>() { "genericsource" };

View File

@@ -3,9 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
@@ -16,134 +13,165 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Lighting namespace PepperDash.Essentials.Devices.Common.Lighting
{ {
public abstract class LightingBase : EssentialsBridgeableDevice, ILightingScenes
{
#region ILightingScenes Members
public event EventHandler<LightingSceneChangeEventArgs> LightingSceneChange;
/// <summary>
/// Gets or sets the LightingScenes
/// </summary>
public List<LightingScene> LightingScenes { get; protected set; }
/// <summary>
/// Gets or sets the CurrentLightingScene
/// </summary>
public LightingScene CurrentLightingScene { get; protected set; }
/// <summary> /// <summary>
/// Gets or sets the CurrentLightingSceneFeedback /// Base class for lighting devices that support scenes
/// </summary> /// </summary>
public IntFeedback CurrentLightingSceneFeedback { get; protected set; } public abstract class LightingBase : EssentialsBridgeableDevice, ILightingScenes
{
#region ILightingScenes Members
#endregion /// <summary>
/// Event fired when lighting scene changes
/// </summary>
public event EventHandler<LightingSceneChangeEventArgs> LightingSceneChange;
protected LightingBase(string key, string name) /// <summary>
: base(key, name) /// Gets or sets the LightingScenes
{ /// </summary>
LightingScenes = new List<LightingScene>(); public List<LightingScene> LightingScenes { get; protected set; }
CurrentLightingScene = new LightingScene(); /// <summary>
//CurrentLightingSceneFeedback = new IntFeedback(() => { return int.Parse(this.CurrentLightingScene.ID); }); /// Gets or sets the CurrentLightingScene
} /// </summary>
public LightingScene CurrentLightingScene { get; protected set; }
public abstract void SelectScene(LightingScene scene); /// <summary>
/// Gets or sets the CurrentLightingSceneFeedback
/// </summary>
public IntFeedback CurrentLightingSceneFeedback { get; protected set; }
/// <summary> #endregion
/// SimulateSceneSelect method
/// </summary>
public void SimulateSceneSelect(string sceneName)
{
Debug.LogMessage(LogEventLevel.Debug, this, "Simulating selection of scene '{0}'", sceneName);
var scene = LightingScenes.FirstOrDefault(s => s.Name.Equals(sceneName)); /// <summary>
/// Initializes a new instance of the LightingBase class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
protected LightingBase(string key, string name)
: base(key, name)
{
LightingScenes = new List<LightingScene>();
if (scene != null) CurrentLightingScene = new LightingScene();
{ //CurrentLightingSceneFeedback = new IntFeedback(() => { return int.Parse(this.CurrentLightingScene.ID); });
CurrentLightingScene = scene; }
OnLightingSceneChange();
}
}
/// <summary> /// <summary>
/// Sets the IsActive property on each scene and fires the LightingSceneChange event /// Selects the specified lighting scene
/// </summary> /// </summary>
protected void OnLightingSceneChange() /// <param name="scene">The lighting scene to select</param>
{ public abstract void SelectScene(LightingScene scene);
foreach (var scene in LightingScenes)
{
if (scene == CurrentLightingScene)
scene.IsActive = true;
else
scene.IsActive = false;
}
LightingSceneChange?.Invoke(this, new LightingSceneChangeEventArgs(CurrentLightingScene));
}
protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart, /// <summary>
string joinMapKey, EiscApiAdvanced bridge) /// SimulateSceneSelect method
{ /// </summary>
var joinMap = new GenericLightingJoinMap(joinStart); public void SimulateSceneSelect(string sceneName)
{
Debug.LogMessage(LogEventLevel.Debug, this, "Simulating selection of scene '{0}'", sceneName);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); var scene = LightingScenes.FirstOrDefault(s => s.Name.Equals(sceneName));
if (!string.IsNullOrEmpty(joinMapSerialized)) if (scene != null)
joinMap = JsonConvert.DeserializeObject<GenericLightingJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
return LinkLightingToApi(lightingDevice, trilist, joinMap);
}
protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, GenericLightingJoinMap joinMap)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); CurrentLightingScene = scene;
OnLightingSceneChange();
}
}
Debug.LogMessage(LogEventLevel.Information, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString()); /// <summary>
/// Sets the IsActive property on each scene and fires the LightingSceneChange event
/// </summary>
protected void OnLightingSceneChange()
{
foreach (var scene in LightingScenes)
{
if (scene == CurrentLightingScene)
scene.IsActive = true;
// GenericLighitng Actions & FeedBack else
trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); scene.IsActive = false;
}
LightingSceneChange?.Invoke(this, new LightingSceneChangeEventArgs(CurrentLightingScene));
}
var sceneIndex = 0; /// <summary>
/// Links the lighting device to API with join map configuration
/// </summary>
/// <param name="lightingDevice">The lighting device to link</param>
/// <param name="trilist">The trilist to link to</param>
/// <param name="joinStart">The starting join number</param>
/// <param name="joinMapKey">The join map key</param>
/// <param name="bridge">The EISC API bridge</param>
/// <returns>The configured join map</returns>
protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart,
string joinMapKey, EiscApiAdvanced bridge)
{
var joinMap = new GenericLightingJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!string.IsNullOrEmpty(joinMapSerialized))
joinMap = JsonConvert.DeserializeObject<GenericLightingJoinMap>(joinMapSerialized);
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
else
{
Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
}
return LinkLightingToApi(lightingDevice, trilist, joinMap);
}
/// <summary>
/// Links the lighting device to API using an existing join map
/// </summary>
/// <param name="lightingDevice">The lighting device to link</param>
/// <param name="trilist">The trilist to link to</param>
/// <param name="joinMap">The join map to use</param>
/// <returns>The join map used for linking</returns>
protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, GenericLightingJoinMap joinMap)
{
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X"));
Debug.LogMessage(LogEventLevel.Information, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString());
// GenericLighitng Actions & FeedBack
trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u]));
var sceneIndex = 0;
foreach (var scene in lightingDevice.LightingScenes)
{
var index = sceneIndex;
trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + index), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[index]));
scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)]);
trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name;
trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true;
sceneIndex++;
}
trilist.OnlineStatusChange += (sender, args) =>
{
if (!args.DeviceOnLine) return;
sceneIndex = 0;
foreach (var scene in lightingDevice.LightingScenes) foreach (var scene in lightingDevice.LightingScenes)
{ {
var index = sceneIndex; var index = sceneIndex;
trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + index), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[index]));
scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)]);
trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name;
trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true;
scene.IsActiveFeedback.FireUpdate();
sceneIndex++; sceneIndex++;
} }
};
trilist.OnlineStatusChange += (sender, args) => return joinMap;
{
if (!args.DeviceOnLine) return;
sceneIndex = 0;
foreach (var scene in lightingDevice.LightingScenes)
{
var index = sceneIndex;
trilist.StringInput[(uint) (joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name;
trilist.BooleanInput[(uint) (joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true;
scene.IsActiveFeedback.FireUpdate();
sceneIndex++;
}
};
return joinMap;
}
} }
}
} }

View File

@@ -9,10 +9,20 @@ namespace PepperDash.Essentials.Devices.Common.Room
public interface IEssentialsHuddleSpaceRoom : IEssentialsRoom, IHasCurrentSourceInfoChange, IRunRouteAction, IHasDefaultDisplay, IHasCurrentVolumeControls, IRoomOccupancy, public interface IEssentialsHuddleSpaceRoom : IEssentialsRoom, IHasCurrentSourceInfoChange, IRunRouteAction, IHasDefaultDisplay, IHasCurrentVolumeControls, IRoomOccupancy,
IEmergency, IMicrophonePrivacy IEmergency, IMicrophonePrivacy
{ {
/// <summary>
/// Gets whether to exclude this room from global functions
/// </summary>
bool ExcludeFromGlobalFunctions { get; } bool ExcludeFromGlobalFunctions { get; }
/// <summary>
/// Runs the route action for the given routeKey and sourceListKey
/// </summary>
/// <param name="routeKey">The route key</param>
void RunRouteAction(string routeKey); void RunRouteAction(string routeKey);
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; } EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; }
} }
} }

View File

@@ -12,18 +12,40 @@ namespace PepperDash.Essentials.Devices.Common.Room
public interface IEssentialsHuddleVtc1Room : IEssentialsRoom, IHasCurrentSourceInfoChange, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback, public interface IEssentialsHuddleVtc1Room : IEssentialsRoom, IHasCurrentSourceInfoChange, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback,
IRoomOccupancy, IEmergency, IMicrophonePrivacy IRoomOccupancy, IEmergency, IMicrophonePrivacy
{ {
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; } EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; }
/// <summary>
/// Gets whether to exclude this room from global functions
/// </summary>
bool ExcludeFromGlobalFunctions { get; } bool ExcludeFromGlobalFunctions { get; }
/// <summary>
/// Runs the route action for the given routeKey and sourceListKey
/// </summary>
/// <param name="routeKey">The route key</param>
void RunRouteAction(string routeKey); void RunRouteAction(string routeKey);
/// <summary>
/// Gets the ScheduleSource
/// </summary>
IHasScheduleAwareness ScheduleSource { get; } IHasScheduleAwareness ScheduleSource { get; }
/// <summary>
/// Gets the InCallFeedback
/// </summary>
new BoolFeedback InCallFeedback { get; } new BoolFeedback InCallFeedback { get; }
/// <summary>
/// Gets the PrivacyModeIsOnFeedback
/// </summary>
new BoolFeedback PrivacyModeIsOnFeedback { get; } new BoolFeedback PrivacyModeIsOnFeedback { get; }
/// <summary>
/// Gets the DefaultCodecRouteString
/// </summary>
string DefaultCodecRouteString { get; } string DefaultCodecRouteString { get; }
} }
} }

View File

@@ -1,9 +1,4 @@
using PepperDash.Essentials.Room.Config; using PepperDash.Essentials.Room.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Room namespace PepperDash.Essentials.Devices.Common.Room
{ {
@@ -12,6 +7,9 @@ namespace PepperDash.Essentials.Devices.Common.Room
/// </summary> /// </summary>
public interface IEssentialsRoomPropertiesConfig public interface IEssentialsRoomPropertiesConfig
{ {
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
EssentialsRoomPropertiesConfig PropertiesConfig { get; } EssentialsRoomPropertiesConfig PropertiesConfig { get; }
} }
} }

View File

@@ -1,9 +1,9 @@
using PepperDash.Essentials.Core; using System.Collections.Generic;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common.Displays;
using PepperDash.Essentials.Room.Config; using PepperDash.Essentials.Room.Config;
using System.Collections.Generic;
using TwoWayDisplayBase = PepperDash.Essentials.Devices.Common.Displays.TwoWayDisplayBase;
namespace PepperDash.Essentials.Devices.Common.Room namespace PepperDash.Essentials.Devices.Common.Room
@@ -11,15 +11,31 @@ namespace PepperDash.Essentials.Devices.Common.Room
/// <summary> /// <summary>
/// Defines the contract for IEssentialsTechRoom /// Defines the contract for IEssentialsTechRoom
/// </summary> /// </summary>
public interface IEssentialsTechRoom:IEssentialsRoom, ITvPresetsProvider,IBridgeAdvanced,IRunDirectRouteAction public interface IEssentialsTechRoom : IEssentialsRoom, ITvPresetsProvider, IBridgeAdvanced, IRunDirectRouteAction
{ {
/// <summary>
/// Gets the PropertiesConfig
/// </summary>
EssentialsTechRoomConfig PropertiesConfig { get; } EssentialsTechRoomConfig PropertiesConfig { get; }
/// <summary>
/// Gets the Tuners
/// </summary>
Dictionary<string, IRSetTopBoxBase> Tuners { get; } Dictionary<string, IRSetTopBoxBase> Tuners { get; }
/// <summary>
/// Gets the Displays
/// </summary>
Dictionary<string, TwoWayDisplayBase> Displays { get; } Dictionary<string, TwoWayDisplayBase> Displays { get; }
/// <summary>
/// Powers on the room
/// </summary>
void RoomPowerOn(); void RoomPowerOn();
/// <summary>
/// Powers off the room
/// </summary>
void RoomPowerOff(); void RoomPowerOff();
} }
} }

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
/// <summary>
/// Represents a IRSetTopBoxBaseFactory
/// </summary>
public class IRSetTopBoxBaseFactory : EssentialsDeviceFactory<IRSetTopBoxBase>
{
/// <summary>
/// Initializes a new instance of the <see cref="IRSetTopBoxBaseFactory"/> class
/// </summary>
public IRSetTopBoxBaseFactory()
{
TypeNames = new List<string>() { "settopbox" };
}
/// <summary>
/// BuildDevice method
/// </summary>
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new SetTopBox Device");
var irCont = IRPortHelper.GetIrOutputPortController(dc);
var config = dc.Properties.ToObject<SetTopBoxPropertiesConfig>();
var stb = new IRSetTopBoxBase(dc.Key, dc.Name, irCont, config);
var listName = dc.Properties.Value<string>("presetsList");
if (listName != null)
stb.LoadPresets(listName);
return stb;
}
}
}

View File

@@ -1,10 +1,4 @@
using System; using PepperDash.Core;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Devices.Common namespace PepperDash.Essentials.Devices.Common
{ {

View File

@@ -27,6 +27,12 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// </summary> /// </summary>
public string StopOrPresetButtonLabel { get; set; } public string StopOrPresetButtonLabel { get; set; }
/// <summary>
/// Initializes a new instance of the RelayControlledShade class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
/// <param name="config">The relay controlled shade configuration</param>
public RelayControlledShade(string key, string name, RelayControlledShadeConfigProperties config) public RelayControlledShade(string key, string name, RelayControlledShadeConfigProperties config)
: base(key, name) : base(key, name)
{ {
@@ -157,6 +163,9 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// </summary> /// </summary>
public class RelayControlledShadeFactory : EssentialsDeviceFactory<RelayControlledShade> public class RelayControlledShadeFactory : EssentialsDeviceFactory<RelayControlledShade>
{ {
/// <summary>
/// Initializes a new instance of the RelayControlledShadeFactory class
/// </summary>
public RelayControlledShadeFactory() public RelayControlledShadeFactory()
{ {
TypeNames = new List<string>() { "relaycontrolledshade" }; TypeNames = new List<string>() { "relaycontrolledshade" };

View File

@@ -1,10 +1,18 @@
using PepperDash.Essentials.Core.Shades; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Shades;
namespace PepperDash.Essentials.Devices.Common.Shades namespace PepperDash.Essentials.Devices.Common.Shades
{ {
/// <summary>
/// Base class for shade devices
/// </summary>
public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop
{ {
/// <summary>
/// Initializes a new instance of the ShadeBase class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
public ShadeBase(string key, string name) public ShadeBase(string key, string name)
: base(key, name) : base(key, name)
{ {
@@ -13,8 +21,17 @@ namespace PepperDash.Essentials.Devices.Common.Shades
#region iShadesOpenClose Members #region iShadesOpenClose Members
/// <summary>
/// Opens the shade
/// </summary>
public abstract void Open(); public abstract void Open();
/// <summary>
/// Stops the shade
/// </summary>
public abstract void Stop(); public abstract void Stop();
/// <summary>
/// Closes the shade
/// </summary>
public abstract void Close(); public abstract void Close();
#endregion #endregion

View File

@@ -14,8 +14,17 @@ namespace PepperDash.Essentials.Devices.Common.Shades
{ {
ShadeControllerConfigProperties Config; ShadeControllerConfigProperties Config;
/// <summary>
/// Gets the collection of shades controlled by this controller
/// </summary>
public List<IShadesOpenCloseStop> Shades { get; private set; } public List<IShadesOpenCloseStop> Shades { get; private set; }
/// <summary>
/// Initializes a new instance of the ShadeController class
/// </summary>
/// <param name="key">The device key</param>
/// <param name="name">The device name</param>
/// <param name="config">The shade controller configuration</param>
public ShadeController(string key, string name, ShadeControllerConfigProperties config) public ShadeController(string key, string name, ShadeControllerConfigProperties config)
: base(key, name) : base(key, name)
{ {
@@ -76,6 +85,9 @@ namespace PepperDash.Essentials.Devices.Common.Shades
/// </summary> /// </summary>
public class ShadeControllerFactory : EssentialsDeviceFactory<ShadeController> public class ShadeControllerFactory : EssentialsDeviceFactory<ShadeController>
{ {
/// <summary>
/// Initializes a new instance of the ShadeControllerFactory class
/// </summary>
public ShadeControllerFactory() public ShadeControllerFactory()
{ {
TypeNames = new List<string>() { "shadecontroller" }; TypeNames = new List<string>() { "shadecontroller" };

Some files were not shown because too many files have changed in this diff Show More