diff --git a/.github/scripts/ZipBuildOutput.ps1 b/.github/scripts/ZipBuildOutput.ps1 index bf3c9d94..5ca867ef 100644 --- a/.github/scripts/ZipBuildOutput.ps1 +++ b/.github/scripts/ZipBuildOutput.ps1 @@ -10,7 +10,7 @@ Get-ChildItem ($destination) $exclusions = @(git submodule foreach --quiet 'echo $name') # Trying to get any .json schema files (not currently working) # Gets any files with the listed extensions. -Get-ChildItem -recurse -Path "$($Env:GITHUB_WORKSPACE)" -include "*.clz", "*.cpz", "*.cplz", "*.dll", "*.nuspec" | ForEach-Object { +Get-ChildItem -recurse -Path "$($Env:GITHUB_WORKSPACE)" -include "*.clz", "*.cpz", "*.cplz", "*.dll" | ForEach-Object { $allowed = $true; # Exclude any files in submodules foreach ($exclude in $exclusions) { diff --git a/.github/workflows/.vscode/settings.json b/.github/workflows/.vscode/settings.json deleted file mode 100644 index 2c6fe730..00000000 --- a/.github/workflows/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.background": "#332848", - "titleBar.activeBackground": "#483764", - "titleBar.activeForeground": "#FBFBFD" - } -} \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e14d60d6..315fbd67 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,20 +32,7 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - # And any submodules - #- name: Checkout submodules - # shell: bash - # run: | - # git config --global url."https://github.com/".insteadOf "git@github.com:" - # auth_header="$(git config --local --get http.https://github.com/.extraheader)" - # git submodule sync --recursive - # git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - name: Add nuget.exe - uses: nuget/setup-nuget@v1 - - name: Add Github Packages source - run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }} - - name: restore Nuget Packages - run: nuget install .\packages.config -OutputDirectory .\packages -ExcludeVersion + submodules: true # Fetch all tags - name: Fetch tags run: git fetch --tags @@ -120,49 +107,6 @@ jobs: asset_content_type: application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Push_Nuget_Package: - needs: Build_Project - runs-on: windows-latest - steps: - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - Write-Output "::set-env name=VERSION::$version" - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - - name: Add nuget.exe - uses: nuget/setup-nuget@v1 - - name: Add Github Packages source - run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }} - - name: Add nuget.org API Key - run: nuget setApiKey ${{ secrets.NUGET_API_KEY }} - - name: Create nuget package - run: nuget pack "./PepperDash_Essentials_Core.nuspec" -version ${{ env.VERSION }} - - name: Publish nuget package to Github registry - run: nuget push **/*.nupkg -source github - - name: Publish nuget package to nuget.org - run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json # This step always runs and pushes the build to the internal build rep Internal_Push_Output: needs: Build_Project diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 959dd894..7bbbd646 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,19 +25,13 @@ jobs: - name: Checkout repo uses: actions/checkout@v2 # And any submodules - #- name: Checkout submodules - # shell: bash - # run: | - # git config --global url."https://github.com/".insteadOf "git@github.com:" - # auth_header="$(git config --local --get http.https://github.com/.extraheader)" - # git submodule sync --recursive - # git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - - name: Add nuget.exe - uses: nuget/setup-nuget@v1 - - name: Add Github Packages source - run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }} - - name: restore Nuget Packages - run: nuget install .\packages.config -OutputDirectory .\packages -ExcludeVersion + - name: Checkout submodules + shell: bash + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + auth_header="$(git config --local --get http.https://github.com/.extraheader)" + git submodule sync --recursive + git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 # Generate the appropriate version number - name: Set Version Number shell: powershell @@ -91,49 +85,6 @@ jobs: asset_content_type: application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Push_Nuget_Package: - needs: Build_Project - runs-on: windows-latest - steps: - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - Write-Output "::set-env name=VERSION::$version" - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - - name: Add nuget.exe - uses: nuget/setup-nuget@v1 - - name: Add Github Packages source - run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }} - - name: Add nuget.org API Key - run: nuget setApiKey ${{ secrets.NUGET_API_KEY }} - - name: Create nuget package - run: nuget pack "./PepperDash_Essentials_Core.nuspec" -version ${{ env.VERSION }} - - name: Publish nuget package to Github registry - run: nuget push **/*.nupkg -source github - - name: Publish nuget package to nuget.org - run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json Internal_Push_Output: needs: Build_Project runs-on: windows-latest diff --git a/.gitignore b/.gitignore index 47084fd0..5ba628b2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,5 @@ SIMPLSharpLogs/ *.projectinfo essentials-framework/EssentialDMTestConfig/ output/ -packages/ - PepperDashEssentials-0.0.0-buildType-test.zip diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index cd9a99cb..57cd454e 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -53,7 +53,7 @@ namespace PepperDash.Essentials if (Debug.DoNotLoadOnNextBoot) { - CrestronConsole.AddNewConsoleCommand(s => GoWithLoadDeferred(), "go", "Loads configuration file", + CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file", ConsoleAccessLevelEnum.AccessOperator); } @@ -93,18 +93,13 @@ namespace PepperDash.Essentials if (!Debug.DoNotLoadOnNextBoot) { - GoWithLoad(null); + GoWithLoad(); return; } SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } - private void GoWithLoadDeferred() - { - CrestronInvoke.BeginInvoke(GoWithLoad); - } - /// /// Determines if the program is running on a processor (appliance) or server (VC-4). /// @@ -177,7 +172,7 @@ namespace PepperDash.Essentials /// /// Begins the process of loading resources including plugins and configuration data /// - public void GoWithLoad(object notUsed) + public void GoWithLoad() { try { diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index ff65954b..c522b52e 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -71,9 +71,9 @@ ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.UI.dll - + False - ..\packages\PepperDashCore\lib\net35\PepperDash_Core.dll + ..\essentials-framework\pepperdashcore-builds\PepperDash_Core.dll False diff --git a/README.md b/README.md index 1286ae6e..e411cabf 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,26 @@ + # PepperDash Essentials Framework (c) 2020 ## [Latest Release](https://github.com/PepperDash/Essentials/releases/latest) ## License - Provided under MIT license ## Overview - PepperDash Essentials is an open source Crestron framework that can be configured as a standalone program capable of running a wide variety of system designs and can also be utilized as a plug-in architecture to augment other Simpl# Pro and Simpl Windows programs. Essentials Framework is a collection of C# / Simpl# Pro libraries that can be utilized in several different manners. It is currently operating as a 100% configuration-driven system, and can be extended to add different workflows and behaviors, either through the addition of further device "types" or via the plug-in mechanism. The framework is a collection of "things" that are all related and interconnected, but in general do not have dependencies on each other. ## Minimum Requirements - - Essentials Framework runs on any Crestron 3-series processor, **4-series** processor or Crestron's VC-4 platform. - To edit and compile the source, Microsoft Visual Studio 2008 Professional with SP1 is required. - Crestron's Simpl# Plugin is also required (must be obtained from Crestron). ## Dependencies -The [PepperDash.Core](https://github.com/PepperDash/PepperDashCore) SIMPL# library is required. It is referenced via nuget. You must have nuget.exe installed and in the `PATH` environment variable to use the following command. Nuget.exe is available at [nuget.org](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe). - -### Installing Dependencies - -To install dependencies once nuget.exe is installed, run the following command: `nuget install .\packages.config -OutputDirectory .\packages -excludeVersion`. To verify that the packages installed correctly, open Essentials and make sure that all references are found, then try and build it. - -### Installing Different versions of PepperDash Core - -If you need a different version of PepperDash Core, use the command `nuget install .\packages.config -OutputDirectory .\packages -excludeVersion -Version {versionToGet}`. Omitting the `-Version` option will pull the latest release version of PepperDash Core. +The [PepperDash.Core](https://github.com/PepperDash/PepperDashCore) SIMPL# library is required. It is referenced as a submodule and will be automatically checked out when cloning this repo if set to recurse submodules. This allows different builds of the PepperDash.Core library to be referenced by checking out the desired submodule commit. ## Utilization - Essentials was originally conceptualized as a standalone application for running control system logic entirely in Simpl# Pro. It is primarily designed around accomplishing this goal, but during development, it became obvious that it could easily be leveraged to also serve as a partner application to one or more SIMPL Windows programs. Utilization of Essentials Framework falls into the following categories: @@ -45,13 +34,13 @@ Utilization of Essentials Framework falls into the following categories: - Advanced logic. Some logic operations that cannot be affectively accomplished in SIMPL Windows (ex. JSON/XML serialization/deserialization, database operations, etc.) can be done in the Simpl# Pro environment and the necessary input and output bridged to a SIMPL Windows program via EISC. 3. Hybrid Application that may contain elements of both standalone control and SIMPL partner application integration. +- There may be a use case where a device can only be defined in a single application, but that device may need to be interacted with from multiple applications. The device can be defined in an Essentials application, interacted with in that application and also bridged to one or more SIMPL Windows applications. -- There may be a use case where a device can only be defined in a single application, but that device may need to be interacted with from multiple applications. The device can be defined in an Essentials application, interacted with in that application and also bridged to one or more SIMPL Windows applications. - -## Documentation - -For detailed documentation, see the [Wiki](https://github.com/PepperDash/EssentialsFramework/wiki). + ## Documentation + For detailed documentation, see the [Wiki](https://github.com/PepperDash/EssentialsFramework/wiki). ## How-To (Getting Started) See [Getting Started](https://github.com/PepperDash/Essentials/wiki/Get-started#how-to-get-started) + + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index 2fb9db12..52c30e19 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -165,29 +165,34 @@ namespace PepperDash.Essentials.Core.Fusion { // Exists. Read GUIDs ReadGuidFile(guidFilePath); + } + + if (Room.RoomOccupancy != null) + { + if (Room.OccupancyStatusProviderIsRemote) + SetUpRemoteOccupancy(); + else + { + SetUpLocalOccupancy(); + } } - CreateSymbolAndBasicSigs(IpId); - SetUpSources(); - SetUpCommunitcationMonitors(); - SetUpDisplay(); - SetUpError(); - ExecuteCustomSteps(); + - if (Room.RoomOccupancy != null) - { - if (Room.OccupancyStatusProviderIsRemote) - SetUpRemoteOccupancy(); - else - { - SetUpLocalOccupancy(); - } - } + AddPostActivationAction(() => + { + CreateSymbolAndBasicSigs(IpId); + SetUpSources(); + SetUpCommunitcationMonitors(); + SetUpDisplay(); + SetUpError(); + ExecuteCustomSteps(); + + FusionRVI.GenerateFileForAllFusionDevices(); + + GenerateGuidFile(guidFilePath); + }); - // Make it so! - FusionRVI.GenerateFileForAllFusionDevices(); - - GenerateGuidFile(guidFilePath); } catch (Exception e) { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index a5e764c2..e57b6a62 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -83,9 +83,9 @@ ..\..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.UI.dll - + False - ..\..\..\packages\PepperDashCore\lib\net35\PepperDash_Core.dll + ..\..\pepperdashcore-builds\PepperDash_Core.dll False diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.nuspec b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.nuspec deleted file mode 100644 index d5bb4a57..00000000 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - PepperDashEssentials - 1.5.6 - PepperDash Essentials - PepperDash Technologies - pepperdash - false - MIT - https://github.com/PepperDash/PepperDashCore - Copyright 2020 - PepperDash Essentials is an open source Crestron framework that can be configured as a standalone program capable of running a wide variety of system designs and can also be utilized as a plug-in architecture to augment other Simpl# Pro and Simpl Windows programs. Essentials Framework is a collection of C# / Simpl# Pro libraries that can be utilized in several different manners. It is currently operating as a 100% configuration-driven system, and can be extended to add different workflows and behaviors, either through the addition of further device "types" or via the plug-in mechanism. The framework is a collection of "things" that are all related and interconnected, but in general do not have dependencies on each other. - crestron 3series 4series - - - - - - - - - - \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs index 87f5d1c2..0a25dadb 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs @@ -329,56 +329,64 @@ namespace PepperDash.Essentials.DM InputEndpointOnlineFeedbacks[tempX] = new BoolFeedback(() => { return Chassis.Inputs[tempX].EndpointOnlineFeedback; }); InputCardHdcpStateFeedbacks[tempX] = new IntFeedback(() => { - var inputCard = Chassis.Inputs[tempX]; - - if (inputCard.Card is DmcHd) + try { - InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; + var inputCard = Chassis.Inputs[tempX]; - if ((inputCard.Card as DmcHd).HdmiInput.HdcpSupportOnFeedback.BoolValue) - return 1; - return 0; - } - - if (inputCard.Card is DmcHdDsp) - { - InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; - - if ((inputCard.Card as DmcHdDsp).HdmiInput.HdcpSupportOnFeedback.BoolValue) - return 1; - return 0; - } - if (inputCard.Card is Dmc4kHdBase) - { - InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.Hdcp2_2Support; - return (int)(inputCard.Card as Dmc4kHdBase).HdmiInput.HdcpReceiveCapability; - } - if (inputCard.Card is Dmc4kCBase) - { - if (PropertiesConfig.InputSlotSupportsHdcp2[tempX]) + if (inputCard.Card is DmcHd) { InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; - return (int)(inputCard.Card as Dmc4kCBase).DmInput.HdcpReceiveCapability; + + if ((inputCard.Card as DmcHd).HdmiInput.HdcpSupportOnFeedback.BoolValue) + return 1; + return 0; } - if ((inputCard.Card as Dmc4kCBase).DmInput.HdcpSupportOnFeedback.BoolValue) - return 1; - return 0; - } - if (inputCard.Card is Dmc4kCDspBase) - { - if (PropertiesConfig.InputSlotSupportsHdcp2[tempX]) + if (inputCard.Card is DmcHdDsp) { InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; - return (int)(inputCard.Card as Dmc4kCDspBase).DmInput.HdcpReceiveCapability; - } - if ((inputCard.Card as Dmc4kCDspBase).DmInput.HdcpSupportOnFeedback.BoolValue) - return 1; - + if ((inputCard.Card as DmcHdDsp).HdmiInput.HdcpSupportOnFeedback.BoolValue) + return 1; + return 0; + } + if (inputCard.Card is Dmc4kHdBase) + { + InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.Hdcp2_2Support; + return (int)(inputCard.Card as Dmc4kHdBase).HdmiInput.HdcpReceiveCapability; + } + if (inputCard.Card is Dmc4kCBase) + { + if (PropertiesConfig.InputSlotSupportsHdcp2[tempX]) + { + InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; + return (int)(inputCard.Card as Dmc4kCBase).DmInput.HdcpReceiveCapability; + } + + if ((inputCard.Card as Dmc4kCBase).DmInput.HdcpSupportOnFeedback.BoolValue) + return 1; + return 0; + } + if (inputCard.Card is Dmc4kCDspBase) + { + if (PropertiesConfig.InputSlotSupportsHdcp2[tempX]) + { + InputCardHdcpCapabilityTypes[tempX] = eHdcpCapabilityType.HdcpAutoSupport; + return (int)(inputCard.Card as Dmc4kCDspBase).DmInput.HdcpReceiveCapability; + } + + if ((inputCard.Card as Dmc4kCDspBase).DmInput.HdcpSupportOnFeedback.BoolValue) + return 1; + + return 0; + } return 0; } - return 0; + catch (InvalidOperationException iopex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "The Input Card in slot: {0} supports HDCP 2. Please update the configuration value in the inputCardSupportsHdcp2 object to true. Error: {1}", tempX, iopex); + return 0; + } }); } } @@ -614,6 +622,13 @@ namespace PepperDash.Essentials.DM var cecPort2 = outputCard.Card2.HdmiOutput; AddDmcHdoPorts(number, cecPort1, cecPort2); } + else if (type == "dmc4kzhdo") + { + var outputCard = new Dmc4kzHdoSingle(number, Chassis); + var cecPort1 = outputCard.Card1.HdmiOutput; + var cecPort2 = outputCard.Card2.HdmiOutput; + AddDmcHdoPorts(number, cecPort1, cecPort2); + } else if (type == "dmchdo") { var outputCard = new DmcHdoSingle(number, Chassis); @@ -800,56 +815,63 @@ namespace PepperDash.Essentials.DM void Chassis_DMInputChange(Switch device, DMInputEventArgs args) { - switch (args.EventId) + try { - case DMInputEventIds.EndpointOnlineEventId: + switch (args.EventId) { - Debug.Console(2, this, "DM Input EndpointOnlineEventId for input: {0}. State: {1}", args.Number, device.Inputs[args.Number].EndpointOnlineFeedback); - InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); - break; - } - case DMInputEventIds.OnlineFeedbackEventId: - { - Debug.Console(2, this, "DM Input OnlineFeedbackEventId for input: {0}. State: {1}", args.Number, device.Inputs[args.Number].EndpointOnlineFeedback); - InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); - break; - } - case DMInputEventIds.VideoDetectedEventId: - { - Debug.Console(2, this, "DM Input {0} VideoDetectedEventId", args.Number); - VideoInputSyncFeedbacks[args.Number].FireUpdate(); - break; - } - case DMInputEventIds.InputNameEventId: - { - Debug.Console(2, this, "DM Input {0} NameFeedbackEventId", args.Number); - InputNameFeedbacks[args.Number].FireUpdate(); - break; - } - case DMInputEventIds.UsbRoutedToEventId: - { - Debug.Console(2, this, "DM Input {0} UsbRoutedToEventId", args.Number); - if (UsbInputRoutedToFeebacks[args.Number] != null) - UsbInputRoutedToFeebacks[args.Number].FireUpdate(); - else - Debug.Console(1, this, "No index of {0} found in UsbInputRoutedToFeedbacks"); - break; - } - case DMInputEventIds.HdcpCapabilityFeedbackEventId: - { - Debug.Console(2, this, "DM Input {0} HdcpCapabilityFeedbackEventId", args.Number); - if (InputCardHdcpStateFeedbacks[args.Number] != null) - InputCardHdcpStateFeedbacks[args.Number].FireUpdate(); - else - Debug.Console(1, this, "No index of {0} found in InputCardHdcpStateFeedbacks"); - break; - } - default: - { - Debug.Console(2, this, "DMInputChange fired for Input {0} with Unhandled EventId: {1}", args.Number, args.EventId); - break; + case DMInputEventIds.EndpointOnlineEventId: + { + Debug.Console(2, this, "DM Input EndpointOnlineEventId for input: {0}. State: {1}", args.Number, device.Inputs[args.Number].EndpointOnlineFeedback); + InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); + break; + } + case DMInputEventIds.OnlineFeedbackEventId: + { + Debug.Console(2, this, "DM Input OnlineFeedbackEventId for input: {0}. State: {1}", args.Number, device.Inputs[args.Number].EndpointOnlineFeedback); + InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); + break; + } + case DMInputEventIds.VideoDetectedEventId: + { + Debug.Console(2, this, "DM Input {0} VideoDetectedEventId", args.Number); + VideoInputSyncFeedbacks[args.Number].FireUpdate(); + break; + } + case DMInputEventIds.InputNameEventId: + { + Debug.Console(2, this, "DM Input {0} NameFeedbackEventId", args.Number); + InputNameFeedbacks[args.Number].FireUpdate(); + break; + } + case DMInputEventIds.UsbRoutedToEventId: + { + Debug.Console(2, this, "DM Input {0} UsbRoutedToEventId", args.Number); + if (UsbInputRoutedToFeebacks[args.Number] != null) + UsbInputRoutedToFeebacks[args.Number].FireUpdate(); + else + Debug.Console(1, this, "No index of {0} found in UsbInputRoutedToFeedbacks"); + break; + } + case DMInputEventIds.HdcpCapabilityFeedbackEventId: + { + Debug.Console(2, this, "DM Input {0} HdcpCapabilityFeedbackEventId", args.Number); + if (InputCardHdcpStateFeedbacks[args.Number] != null) + InputCardHdcpStateFeedbacks[args.Number].FireUpdate(); + else + Debug.Console(1, this, "No index of {0} found in InputCardHdcpCapabilityFeedbacks"); + break; + } + default: + { + Debug.Console(2, this, "DMInputChange fired for Input {0} with Unhandled EventId: {1}", args.Number, args.EventId); + break; + } } } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Chassis_DMInputChange: {0}", ex); + } } /// diff --git a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj index 81426594..76aae649 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj +++ b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj @@ -59,9 +59,9 @@ ..\..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.UI.dll - + False - ..\..\..\packages\PepperDashCore\lib\net35\PepperDash_Core.dll + ..\..\pepperdashcore-builds\PepperDash_Core.dll False diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj index c4e29032..e33e54fd 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj @@ -63,9 +63,9 @@ ..\..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.Lighting.dll - + False - ..\..\..\packages\PepperDashCore\lib\net35\PepperDash_Core.dll + ..\..\pepperdashcore-builds\PepperDash_Core.dll False diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs index bd4fee99..802471b3 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs @@ -1,1683 +1,1738 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; -using PepperDash.Essentials.Devices.Common.Cameras; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco -{ - enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; - - public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, - IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView, - ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets - { - public event EventHandler DirectoryResultReturned; - - public CommunicationGather PortGather { get; private set; } - - public StatusMonitorBase CommunicationMonitor { get; private set; } - - public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } - - string CurrentPresentationView; - - public BoolFeedback RoomIsOccupiedFeedback { get; private set; } - - public IntFeedback PeopleCountFeedback { get; private set; } - - public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } - - public BoolFeedback SelfviewIsOnFeedback { get; private set; } - - public StringFeedback SelfviewPipPositionFeedback { get; private set; } - - public StringFeedback LocalLayoutFeedback { get; private set; } - - public BoolFeedback LocalLayoutIsProminentFeedback { get; private set; } - - public BoolFeedback FarEndIsSharingContentFeedback { get; private set; } - - private CodecCommandWithLabel CurrentSelfviewPipPosition; - - private CodecCommandWithLabel CurrentLocalLayout; - - /// - /// List the available positions for the selfview PIP window - /// - public List SelfviewPipPositions = new List() - { - new CodecCommandWithLabel("CenterLeft", "Center Left"), - new CodecCommandWithLabel("CenterRight", "Center Right"), - new CodecCommandWithLabel("LowerLeft", "Lower Left"), - new CodecCommandWithLabel("LowerRight", "Lower Right"), - new CodecCommandWithLabel("UpperCenter", "Upper Center"), - new CodecCommandWithLabel("UpperLeft", "Upper Left"), - new CodecCommandWithLabel("UpperRight", "Upper Right"), - }; - - /// - /// Lists the available options for local layout - /// - public List LocalLayouts = new List() - { - //new CodecCommandWithLabel("auto", "Auto"), - //new CiscoCodecLocalLayout("custom", "Custom"), // Left out for now - new CodecCommandWithLabel("equal","Equal"), - new CodecCommandWithLabel("overlay","Overlay"), - new CodecCommandWithLabel("prominent","Prominent"), - new CodecCommandWithLabel("single","Single") - }; - - private CiscoCodecConfiguration.RootObject CodecConfiguration = new CiscoCodecConfiguration.RootObject(); - - private CiscoCodecStatus.RootObject CodecStatus = new CiscoCodecStatus.RootObject(); - - public CodecCallHistory CallHistory { get; private set; } - - public CodecCallFavorites CallFavorites { get; private set; } - - /// - /// The root level of the directory - /// - public CodecDirectory DirectoryRoot { get; private set; } - - /// - /// Represents the current state of the directory and is computed on get - /// - public CodecDirectory CurrentDirectoryResult - { - get - { - if (DirectoryBrowseHistory.Count > 0) - return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; - else - return DirectoryRoot; - } - } - - public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } - - /// - /// Tracks the directory browse history when browsing beyond the root directory - /// - public List DirectoryBrowseHistory { get; private set; } - - public CodecScheduleAwareness CodecSchedule { get; private set; } - - /// - /// Gets and returns the scaled volume of the codec - /// - protected override Func VolumeLevelFeedbackFunc - { - get - { - return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0); - } - } - - protected override Func PrivacyModeIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue; - } - } - - protected override Func StandbyIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Standby.State.BoolValue; - } - } - - /// - /// Gets the value of the currently shared source, or returns null - /// - protected override Func SharingSourceFeedbackFunc - { - get - { - return () => PresentationSourceKey; - } - } - - protected override Func SharingContentIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue; - } - } - - protected Func FarEndIsSharingContentFeedbackFunc - { - get - { - return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving"; - } - } - - protected override Func MuteFeedbackFunc - { - get - { - return () => CodecStatus.Status.Audio.VolumeMute.BoolValue; - } - } - - protected Func RoomIsOccupiedFeedbackFunc - { - get - { - return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue; - } - } - - protected Func PeopleCountFeedbackFunc - { - get - { - return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue; - } - } - - protected Func SpeakerTrackIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue; - } - } - - protected Func SelfViewIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue; - } - } - - protected Func SelfviewPipPositionFeedbackFunc - { - get - { - return () => CurrentSelfviewPipPosition.Label; - } - } - - protected Func LocalLayoutFeedbackFunc - { - get - { - return () => CurrentLocalLayout.Label; - } - } - - protected Func LocalLayoutIsProminentFeedbackFunc - { - get - { - return () => CurrentLocalLayout.Label == "Prominent"; - } - } - - - private string CliFeedbackRegistrationExpression; - - private CodecSyncState SyncState; - - public CodecPhonebookSyncState PhonebookSyncState { get; private set; } - - private StringBuilder JsonMessage; - - private bool JsonFeedbackMessageIsIncoming; - - public bool CommDebuggingIsOn; - - string Delimiter = "\r\n"; - - /// - /// Used to track the current connector used for the presentation source - /// - int PresentationSource; - - string PresentationSourceKey; - - string PhonebookMode = "Local"; // Default to Local - - int PhonebookResultsLimit = 255; // Could be set later by config. - - CTimer LoginMessageReceivedTimer; - CTimer RetryConnectionTimer; - - // **___________________________________________________________________** - // Timers to be moved to the global system timer at a later point.... - CTimer BookingsRefreshTimer; - CTimer PhonebookRefreshTimer; - // **___________________________________________________________________** - - public RoutingInputPort CodecOsdIn { get; private set; } - public RoutingInputPort HdmiIn2 { get; private set; } - public RoutingInputPort HdmiIn3 { get; private set; } - public RoutingOutputPort HdmiOut1 { get; private set; } - public RoutingOutputPort HdmiOut2 { get; private set; } - - - // Constructor for IBasicCommunication - public CiscoSparkCodec(DeviceConfig config, IBasicCommunication comm) - : base(config) - { - - - var props = JsonConvert.DeserializeObject(config.Properties.ToString()); - - RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); - PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); - CameraAutoModeIsOnFeedback = new BoolFeedback(SpeakerTrackIsOnFeedbackFunc); - SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); - SelfviewPipPositionFeedback = new StringFeedback(SelfviewPipPositionFeedbackFunc); - LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc); - LocalLayoutIsProminentFeedback = new BoolFeedback(LocalLayoutIsProminentFeedbackFunc); - FarEndIsSharingContentFeedback = new BoolFeedback(FarEndIsSharingContentFeedbackFunc); - - PresentationViewMaximizedFeedback = new BoolFeedback(() => CurrentPresentationView == "Maximized"); - - Communication = comm; - - if (props.CommunicationMonitorProperties != null) - { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); - } - else - { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, "xStatus SystemUnit Software Version\r"); - } - - if (props.Sharing != null) - AutoShareContentWhileInCall = props.Sharing.AutoShareContentWhileInCall; - - ShowSelfViewByDefault = props.ShowSelfViewByDefault; - - DeviceManager.AddDevice(CommunicationMonitor); - - PhonebookMode = props.PhonebookMode; - - SyncState = new CodecSyncState(Key + "--Sync"); - - PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); - - SyncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); - - PortGather = new CommunicationGather(Communication, Delimiter); - PortGather.IncludeDelimiter = true; - PortGather.LineReceived += this.Port_LineReceived; - - CodecInfo = new CiscoCodecInfo(CodecStatus, CodecConfiguration); - - CallHistory = new CodecCallHistory(); - - if (props.Favorites != null) - { - CallFavorites = new CodecCallFavorites(); - CallFavorites.Favorites = props.Favorites; - } - - DirectoryRoot = new CodecDirectory(); - - DirectoryBrowseHistory = new List(); - - CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0); - - CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); - - CodecSchedule = new CodecScheduleAwareness(); - - //Set Feedback Actions - CodecStatus.Status.Audio.Volume.ValueChangedAction = VolumeLevelFeedback.FireUpdate; - CodecStatus.Status.Audio.VolumeMute.ValueChangedAction = MuteFeedback.FireUpdate; - CodecStatus.Status.Audio.Microphones.Mute.ValueChangedAction = PrivacyModeIsOnFeedback.FireUpdate; - CodecStatus.Status.Standby.State.ValueChangedAction = StandbyIsOnFeedback.FireUpdate; - CodecStatus.Status.RoomAnalytics.PeoplePresence.ValueChangedAction = RoomIsOccupiedFeedback.FireUpdate; - CodecStatus.Status.RoomAnalytics.PeopleCount.Current.ValueChangedAction = PeopleCountFeedback.FireUpdate; - CodecStatus.Status.Cameras.SpeakerTrack.Status.ValueChangedAction = CameraAutoModeIsOnFeedback.FireUpdate; - CodecStatus.Status.Video.Selfview.Mode.ValueChangedAction = SelfviewIsOnFeedback.FireUpdate; - CodecStatus.Status.Video.Selfview.PIPPosition.ValueChangedAction = ComputeSelfviewPipStatus; - CodecStatus.Status.Video.Layout.LayoutFamily.Local.ValueChangedAction = ComputeLocalLayout; - CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction += SharingContentIsOnFeedback.FireUpdate; - CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction += FarEndIsSharingContentFeedback.FireUpdate; - - CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); - HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource1), this); - HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource2), this); - - HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - - InputPorts.Add(CodecOsdIn); - InputPorts.Add(HdmiIn2); - InputPorts.Add(HdmiIn3); - OutputPorts.Add(HdmiOut1); - - SetUpCameras(); - - CreateOsdSource(); - } - - - /// - /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input - /// to enable routing - /// - void CreateOsdSource() - { - OsdSource = new DummyRoutingInputsDevice(Key + "[osd]"); - DeviceManager.AddDevice(OsdSource); - var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn); - TieLineCollection.Default.Add(tl); - } - - /// - /// Starts the HTTP feedback server and syncronizes state of codec - /// - /// - public override bool CustomActivate() - { - CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator); - - var socket = Communication as ISocketStatus; - if (socket != null) - { - socket.ConnectionChange += new EventHandler(socket_ConnectionChange); - } - - Communication.Connect(); - - CommunicationMonitor.Start(); - - string prefix = "xFeedback register "; - - CliFeedbackRegistrationExpression = - prefix + "/Configuration" + Delimiter + - prefix + "/Status/Audio" + Delimiter + - prefix + "/Status/Call" + Delimiter + - prefix + "/Status/Conference/Presentation" + Delimiter + - prefix + "/Status/Cameras/SpeakerTrack" + Delimiter + - prefix + "/Status/RoomAnalytics" + Delimiter + - prefix + "/Status/RoomPreset" + Delimiter + - prefix + "/Status/Standby" + Delimiter + - prefix + "/Status/Video/Selfview" + Delimiter + - prefix + "/Status/Video/Layout" + Delimiter + - prefix + "/Bookings" + Delimiter + - prefix + "/Event/CallDisconnect" + Delimiter + - prefix + "/Event/Bookings" + Delimiter + - prefix + "/Event/CameraPresetListUpdated" + Delimiter; - - return base.CustomActivate(); - } - - /// - /// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc. - /// - /// - /// - void SyncState_InitialSyncCompleted(object sender, EventArgs e) - { - // Fire the ready event - SetIsReady(); - //CommDebuggingIsOn = false; - - GetCallHistory(); - - PhonebookRefreshTimer = new CTimer(CheckCurrentHour, 3600000, 3600000); // check each hour to see if the phonebook should be downloaded - GetPhonebook(null); - - BookingsRefreshTimer = new CTimer(GetBookings, 900000, 900000); // 15 minute timer to check for new booking info - GetBookings(null); - } - - public void SetCommDebug(string s) - { - if (s == "1") - { - CommDebuggingIsOn = true; - Debug.Console(0, this, "Comm Debug Enabled."); - } - else - { - CommDebuggingIsOn = false; - Debug.Console(0, this, "Comm Debug Disabled."); - } - } - - void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) - { - Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); - if (e.Client.IsConnected) - { - if(!SyncState.LoginMessageWasReceived) - LoginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); - } - else - { - SyncState.CodecDisconnected(); - PhonebookSyncState.CodecDisconnected(); - - if (PhonebookRefreshTimer != null) - { - PhonebookRefreshTimer.Stop(); - PhonebookRefreshTimer = null; - } - - if (BookingsRefreshTimer != null) - { - BookingsRefreshTimer.Stop(); - BookingsRefreshTimer = null; - } - } - } - - void DisconnectClientAndReconnect() - { - Debug.Console(1, this, "Retrying connection to codec."); - - Communication.Disconnect(); - - RetryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); - - //CrestronEnvironment.Sleep(2000); - - //Communication.Connect(); - } - - /// - /// Gathers responses from the codec (including the delimiter. Responses are checked to see if they contain JSON data and if so, the data is collected until a complete JSON - /// message is received before forwarding the message to be deserialized. - /// - /// - /// - void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) - { - if (CommDebuggingIsOn) - { - if(!JsonFeedbackMessageIsIncoming) - Debug.Console(1, this, "RX: '{0}'", args.Text); - } - - if (args.Text == "{" + Delimiter) // Check for the beginning of a new JSON message - { - JsonFeedbackMessageIsIncoming = true; - - if (CommDebuggingIsOn) - Debug.Console(1, this, "Incoming JSON message..."); - - JsonMessage = new StringBuilder(); - } - else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message - { - JsonFeedbackMessageIsIncoming = false; - - JsonMessage.Append(args.Text); - - if (CommDebuggingIsOn) - Debug.Console(1, this, "Complete JSON Received:\n{0}", JsonMessage.ToString()); - - // Forward the complete message to be deserialized - DeserializeResponse(JsonMessage.ToString()); - return; - } - - if(JsonFeedbackMessageIsIncoming) - { - JsonMessage.Append(args.Text); - - //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); - return; - } - - if (!SyncState.InitialSyncComplete) - { - switch (args.Text.Trim().ToLower()) // remove the whitespace - { - case "*r login successful": - { - SyncState.LoginMessageReceived(); - - if(LoginMessageReceivedTimer != null) - LoginMessageReceivedTimer.Stop(); - - SendText("xPreferences outputmode json"); - break; - } - case "xpreferences outputmode json": - { - if (!SyncState.InitialStatusMessageWasReceived) - SendText("xStatus"); - break; - } - case "xfeedback register /event/calldisconnect": - { - SyncState.FeedbackRegistered(); - break; - } - } - } - - } - - public void SendText(string command) - { - if (CommDebuggingIsOn) - Debug.Console(1, this, "Sending: '{0}'", command); - - Communication.SendText(command + Delimiter); - } - - void DeserializeResponse(string response) - { - try - { - //// Serializer settings. We want to ignore null values and missing members - //JsonSerializerSettings settings = new JsonSerializerSettings(); - //settings.NullValueHandling = NullValueHandling.Ignore; - //settings.MissingMemberHandling = MissingMemberHandling.Ignore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.CrestronThread; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Devices.Common.Cameras; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.VideoCodec; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco +{ + enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; + + public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, + IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView, + ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets + { + public event EventHandler DirectoryResultReturned; + + public CommunicationGather PortGather { get; private set; } + + public StatusMonitorBase CommunicationMonitor { get; private set; } + + private CrestronQueue ReceiveQueue; + + private Thread ReceiveThread; + + public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } + + string CurrentPresentationView; + + public BoolFeedback RoomIsOccupiedFeedback { get; private set; } + + public IntFeedback PeopleCountFeedback { get; private set; } + + public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } + + public BoolFeedback SelfviewIsOnFeedback { get; private set; } + + public StringFeedback SelfviewPipPositionFeedback { get; private set; } + + public StringFeedback LocalLayoutFeedback { get; private set; } + + public BoolFeedback LocalLayoutIsProminentFeedback { get; private set; } + + public BoolFeedback FarEndIsSharingContentFeedback { get; private set; } + + private CodecCommandWithLabel CurrentSelfviewPipPosition; + + private CodecCommandWithLabel CurrentLocalLayout; + + /// + /// List the available positions for the selfview PIP window + /// + public List SelfviewPipPositions = new List() + { + new CodecCommandWithLabel("CenterLeft", "Center Left"), + new CodecCommandWithLabel("CenterRight", "Center Right"), + new CodecCommandWithLabel("LowerLeft", "Lower Left"), + new CodecCommandWithLabel("LowerRight", "Lower Right"), + new CodecCommandWithLabel("UpperCenter", "Upper Center"), + new CodecCommandWithLabel("UpperLeft", "Upper Left"), + new CodecCommandWithLabel("UpperRight", "Upper Right"), + }; + + /// + /// Lists the available options for local layout + /// + public List LocalLayouts = new List() + { + //new CodecCommandWithLabel("auto", "Auto"), + //new CiscoCodecLocalLayout("custom", "Custom"), // Left out for now + new CodecCommandWithLabel("equal","Equal"), + new CodecCommandWithLabel("overlay","Overlay"), + new CodecCommandWithLabel("prominent","Prominent"), + new CodecCommandWithLabel("single","Single") + }; + + private CiscoCodecConfiguration.RootObject CodecConfiguration = new CiscoCodecConfiguration.RootObject(); + + private CiscoCodecStatus.RootObject CodecStatus = new CiscoCodecStatus.RootObject(); + + public CodecCallHistory CallHistory { get; private set; } + + public CodecCallFavorites CallFavorites { get; private set; } + + /// + /// The root level of the directory + /// + public CodecDirectory DirectoryRoot { get; private set; } + + /// + /// Represents the current state of the directory and is computed on get + /// + public CodecDirectory CurrentDirectoryResult + { + get + { + if (DirectoryBrowseHistory.Count > 0) + return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; + else + return DirectoryRoot; + } + } + + public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } + + /// + /// Tracks the directory browse history when browsing beyond the root directory + /// + public List DirectoryBrowseHistory { get; private set; } + + public CodecScheduleAwareness CodecSchedule { get; private set; } + + /// + /// Gets and returns the scaled volume of the codec + /// + protected override Func VolumeLevelFeedbackFunc + { + get + { + return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0); + } + } + + protected override Func PrivacyModeIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue; + } + } + + protected override Func StandbyIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Standby.State.BoolValue; + } + } + + /// + /// Gets the value of the currently shared source, or returns null + /// + protected override Func SharingSourceFeedbackFunc + { + get + { + return () => PresentationSourceKey; + } + } + + protected override Func SharingContentIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue; + } + } + + protected Func FarEndIsSharingContentFeedbackFunc + { + get + { + return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving"; + } + } + + protected override Func MuteFeedbackFunc + { + get + { + return () => CodecStatus.Status.Audio.VolumeMute.BoolValue; + } + } + + protected Func RoomIsOccupiedFeedbackFunc + { + get + { + return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue; + } + } + + protected Func PeopleCountFeedbackFunc + { + get + { + return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue; + } + } + + protected Func SpeakerTrackIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue; + } + } + + protected Func SelfViewIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue; + } + } + + protected Func SelfviewPipPositionFeedbackFunc + { + get + { + return () => CurrentSelfviewPipPosition.Label; + } + } + + protected Func LocalLayoutFeedbackFunc + { + get + { + return () => CurrentLocalLayout.Label; + } + } + + protected Func LocalLayoutIsProminentFeedbackFunc + { + get + { + return () => CurrentLocalLayout.Label == "Prominent"; + } + } + + + private string CliFeedbackRegistrationExpression; + + private CodecSyncState SyncState; + + public CodecPhonebookSyncState PhonebookSyncState { get; private set; } + + private StringBuilder JsonMessage; + + private bool JsonFeedbackMessageIsIncoming; + + public bool CommDebuggingIsOn; + + string Delimiter = "\r\n"; + + /// + /// Used to track the current connector used for the presentation source + /// + int PresentationSource; + + string PresentationSourceKey; + + string PhonebookMode = "Local"; // Default to Local + + uint PhonebookResultsLimit = 255; // Could be set later by config. + + CTimer LoginMessageReceivedTimer; + CTimer RetryConnectionTimer; + + // **___________________________________________________________________** + // Timers to be moved to the global system timer at a later point.... + CTimer BookingsRefreshTimer; + CTimer PhonebookRefreshTimer; + // **___________________________________________________________________** + + public RoutingInputPort CodecOsdIn { get; private set; } + public RoutingInputPort HdmiIn2 { get; private set; } + public RoutingInputPort HdmiIn3 { get; private set; } + public RoutingOutputPort HdmiOut1 { get; private set; } + public RoutingOutputPort HdmiOut2 { get; private set; } + + + // Constructor for IBasicCommunication + public CiscoSparkCodec(DeviceConfig config, IBasicCommunication comm) + : base(config) + { + var props = JsonConvert.DeserializeObject(config.Properties.ToString()); + + // Use the configured phonebook results limit if present + if (props.PhonebookResultsLimit > 0) + { + PhonebookResultsLimit = props.PhonebookResultsLimit; + } + + // The queue that will collect the repsonses in the order they are received + ReceiveQueue = new CrestronQueue(25); + + // The thread responsible for dequeuing and processing the messages + ReceiveThread = new Thread((o) => ProcessQueue(), null); + ReceiveThread.Priority = Thread.eThreadPriority.MediumPriority; + + + RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); + PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); + CameraAutoModeIsOnFeedback = new BoolFeedback(SpeakerTrackIsOnFeedbackFunc); + SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); + SelfviewPipPositionFeedback = new StringFeedback(SelfviewPipPositionFeedbackFunc); + LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc); + LocalLayoutIsProminentFeedback = new BoolFeedback(LocalLayoutIsProminentFeedbackFunc); + FarEndIsSharingContentFeedback = new BoolFeedback(FarEndIsSharingContentFeedbackFunc); + + PresentationViewMaximizedFeedback = new BoolFeedback(() => CurrentPresentationView == "Maximized"); + + Communication = comm; + + if (props.CommunicationMonitorProperties != null) + { + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); + } + else + { + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, "xStatus SystemUnit Software Version\r"); + } + + if (props.Sharing != null) + AutoShareContentWhileInCall = props.Sharing.AutoShareContentWhileInCall; + + ShowSelfViewByDefault = props.ShowSelfViewByDefault; + + DeviceManager.AddDevice(CommunicationMonitor); + + PhonebookMode = props.PhonebookMode; + + SyncState = new CodecSyncState(Key + "--Sync"); + + PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); + + SyncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); + + PortGather = new CommunicationGather(Communication, Delimiter); + PortGather.IncludeDelimiter = true; + PortGather.LineReceived += this.Port_LineReceived; + + CodecInfo = new CiscoCodecInfo(CodecStatus, CodecConfiguration); + + CallHistory = new CodecCallHistory(); + + if (props.Favorites != null) + { + CallFavorites = new CodecCallFavorites(); + CallFavorites.Favorites = props.Favorites; + } + + DirectoryRoot = new CodecDirectory(); + + DirectoryBrowseHistory = new List(); + + CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0); + + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + CodecSchedule = new CodecScheduleAwareness(); + + //Set Feedback Actions + CodecStatus.Status.Audio.Volume.ValueChangedAction = VolumeLevelFeedback.FireUpdate; + CodecStatus.Status.Audio.VolumeMute.ValueChangedAction = MuteFeedback.FireUpdate; + CodecStatus.Status.Audio.Microphones.Mute.ValueChangedAction = PrivacyModeIsOnFeedback.FireUpdate; + CodecStatus.Status.Standby.State.ValueChangedAction = StandbyIsOnFeedback.FireUpdate; + CodecStatus.Status.RoomAnalytics.PeoplePresence.ValueChangedAction = RoomIsOccupiedFeedback.FireUpdate; + CodecStatus.Status.RoomAnalytics.PeopleCount.Current.ValueChangedAction = PeopleCountFeedback.FireUpdate; + CodecStatus.Status.Cameras.SpeakerTrack.Status.ValueChangedAction = CameraAutoModeIsOnFeedback.FireUpdate; + CodecStatus.Status.Video.Selfview.Mode.ValueChangedAction = SelfviewIsOnFeedback.FireUpdate; + CodecStatus.Status.Video.Selfview.PIPPosition.ValueChangedAction = ComputeSelfviewPipStatus; + CodecStatus.Status.Video.Layout.LayoutFamily.Local.ValueChangedAction = ComputeLocalLayout; + CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction += SharingContentIsOnFeedback.FireUpdate; + CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction += FarEndIsSharingContentFeedback.FireUpdate; + + CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); + HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource1), this); + HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource2), this); + + HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + + InputPorts.Add(CodecOsdIn); + InputPorts.Add(HdmiIn2); + InputPorts.Add(HdmiIn3); + OutputPorts.Add(HdmiOut1); + + SetUpCameras(); + + CreateOsdSource(); + } + + /// + /// Runs in it's own thread to dequeue messages in the order they were received to be processed + /// + /// + object ProcessQueue() + { + try + { + while (true) + { + var message = ReceiveQueue.Dequeue(); + + DeserializeResponse(message); + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error Processing Queue: {0}", e); + } + + return null; + } + + + /// + /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input + /// to enable routing + /// + void CreateOsdSource() + { + OsdSource = new DummyRoutingInputsDevice(Key + "[osd]"); + DeviceManager.AddDevice(OsdSource); + var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn); + TieLineCollection.Default.Add(tl); + } + + /// + /// Starts the HTTP feedback server and syncronizes state of codec + /// + /// + public override bool CustomActivate() + { + CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator); + + var socket = Communication as ISocketStatus; + if (socket != null) + { + socket.ConnectionChange += new EventHandler(socket_ConnectionChange); + } + + Communication.Connect(); + + CommunicationMonitor.Start(); + + string prefix = "xFeedback register "; + + CliFeedbackRegistrationExpression = + prefix + "/Configuration" + Delimiter + + prefix + "/Status/Audio" + Delimiter + + prefix + "/Status/Call" + Delimiter + + prefix + "/Status/Conference/Presentation" + Delimiter + + prefix + "/Status/Cameras/SpeakerTrack" + Delimiter + + prefix + "/Status/RoomAnalytics" + Delimiter + + prefix + "/Status/RoomPreset" + Delimiter + + prefix + "/Status/Standby" + Delimiter + + prefix + "/Status/Video/Selfview" + Delimiter + + prefix + "/Status/Video/Layout" + Delimiter + + prefix + "/Bookings" + Delimiter + + prefix + "/Event/CallDisconnect" + Delimiter + + prefix + "/Event/Bookings" + Delimiter + + prefix + "/Event/CameraPresetListUpdated" + Delimiter; + + return base.CustomActivate(); + } + + /// + /// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc. + /// + /// + /// + void SyncState_InitialSyncCompleted(object sender, EventArgs e) + { + // Fire the ready event + SetIsReady(); + //CommDebuggingIsOn = false; + + GetCallHistory(); + + PhonebookRefreshTimer = new CTimer(CheckCurrentHour, 3600000, 3600000); // check each hour to see if the phonebook should be downloaded + GetPhonebook(null); + + BookingsRefreshTimer = new CTimer(GetBookings, 900000, 900000); // 15 minute timer to check for new booking info + GetBookings(null); + } + + public void SetCommDebug(string s) + { + if (s == "1") + { + CommDebuggingIsOn = true; + Debug.Console(0, this, "Comm Debug Enabled."); + } + else + { + CommDebuggingIsOn = false; + Debug.Console(0, this, "Comm Debug Disabled."); + } + } + + void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) + { + Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); + if (e.Client.IsConnected) + { + if(!SyncState.LoginMessageWasReceived) + LoginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); + } + else + { + SyncState.CodecDisconnected(); + PhonebookSyncState.CodecDisconnected(); + + if (PhonebookRefreshTimer != null) + { + PhonebookRefreshTimer.Stop(); + PhonebookRefreshTimer = null; + } + + if (BookingsRefreshTimer != null) + { + BookingsRefreshTimer.Stop(); + BookingsRefreshTimer = null; + } + } + } + + void DisconnectClientAndReconnect() + { + Debug.Console(1, this, "Retrying connection to codec."); + + Communication.Disconnect(); + + RetryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); + + //CrestronEnvironment.Sleep(2000); + + //Communication.Connect(); + } + + /// + /// Gathers responses from the codec (including the delimiter. Responses are checked to see if they contain JSON data and if so, the data is collected until a complete JSON + /// message is received before forwarding the message to be deserialized. + /// + /// + /// + void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) + { + if (CommDebuggingIsOn) + { + if(!JsonFeedbackMessageIsIncoming) + Debug.Console(1, this, "RX: '{0}'", args.Text); + } + + if (args.Text == "{" + Delimiter) // Check for the beginning of a new JSON message + { + JsonFeedbackMessageIsIncoming = true; + + if (CommDebuggingIsOn) + Debug.Console(1, this, "Incoming JSON message..."); + + JsonMessage = new StringBuilder(); + } + else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message + { + JsonFeedbackMessageIsIncoming = false; + + JsonMessage.Append(args.Text); + + if (CommDebuggingIsOn) + Debug.Console(1, this, "Complete JSON Received:\n{0}", JsonMessage.ToString()); + + // Enqueue the complete message to be deserialized + + ReceiveQueue.Enqueue(JsonMessage.ToString()); + //DeserializeResponse(JsonMessage.ToString()); + + if (ReceiveThread.ThreadState != Thread.eThreadStates.ThreadRunning) + ReceiveThread.Start(); + + return; + } + + if(JsonFeedbackMessageIsIncoming) + { + JsonMessage.Append(args.Text); + + //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); + return; + } + + if (!SyncState.InitialSyncComplete) + { + switch (args.Text.Trim().ToLower()) // remove the whitespace + { + case "*r login successful": + { + SyncState.LoginMessageReceived(); + + if(LoginMessageReceivedTimer != null) + LoginMessageReceivedTimer.Stop(); + + SendText("xPreferences outputmode json"); + break; + } + case "xpreferences outputmode json": + { + if (!SyncState.InitialStatusMessageWasReceived) + SendText("xStatus"); + break; + } + case "xfeedback register /event/calldisconnect": + { + SyncState.FeedbackRegistered(); + break; + } + } + } + + } + + public void SendText(string command) + { + if (CommDebuggingIsOn) + Debug.Console(1, this, "Sending: '{0}'", command); + + Communication.SendText(command + Delimiter); + } + + void DeserializeResponse(string response) + { + try + { + //// Serializer settings. We want to ignore null values and missing members + //JsonSerializerSettings settings = new JsonSerializerSettings(); + //settings.NullValueHandling = NullValueHandling.Ignore; + //settings.MissingMemberHandling = MissingMemberHandling.Ignore; //settings.ObjectCreationHandling = ObjectCreationHandling.Auto; - if (response.IndexOf("\"Status\":{") > -1 || response.IndexOf("\"Status\": {") > -1) - { - // Status Message - - // Temp object so we can inpsect for call data before simply deserializing - CiscoCodecStatus.RootObject tempCodecStatus = new CiscoCodecStatus.RootObject(); - - JsonConvert.PopulateObject(response, tempCodecStatus); - - // Check to see if the message contains /Status/Conference/Presentation/LocalInstance and extract source value - var conference = tempCodecStatus.Status.Conference; - - if (conference.Presentation.LocalInstance.Count > 0) - { - if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) - PresentationSource = 0; - else if (conference.Presentation.LocalInstance[0].Source != null) - { - PresentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; - } - } - - // Check to see if this is a call status message received after the initial status message - if (tempCodecStatus.Status.Call.Count > 0) - { - // Iterate through the call objects in the response - foreach (CiscoCodecStatus.Call call in tempCodecStatus.Status.Call) - { - var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(call.id)); - - if (tempActiveCall != null) - { - bool changeDetected = false; - - eCodecCallStatus newStatus = eCodecCallStatus.Unknown; - - // Update properties of ActiveCallItem - if(call.Status != null) - if (!string.IsNullOrEmpty(call.Status.Value)) - { - tempActiveCall.Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value); - - if (newStatus == eCodecCallStatus.Connected) - GetCallHistory(); - - changeDetected = true; - } - if (call.CallType != null) - if (!string.IsNullOrEmpty(call.CallType.Value)) - { - tempActiveCall.Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value); - changeDetected = true; - } - if (call.DisplayName != null) - if (!string.IsNullOrEmpty(call.DisplayName.Value)) - { - tempActiveCall.Name = call.DisplayName.Value; - changeDetected = true; - } - if (call.Direction != null) - { - if (!string.IsNullOrEmpty(call.Direction.Value)) - { - tempActiveCall.Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value); - changeDetected = true; - } - } - - if (changeDetected) - { - SetSelfViewMode(); - OnCallStatusChange(tempActiveCall); - ListCalls(); - } - } - else if( call.ghost == null ) // if the ghost value is present the call has ended already - { - // Create a new call item - var newCallItem = new CodecActiveCallItem() - { - Id = call.id, - Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value), - Name = call.DisplayName.Value, - Number = call.RemoteNumber.Value, - Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value), - Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value) - }; - - // Add it to the ActiveCalls List - ActiveCalls.Add(newCallItem); - - ListCalls(); - - SetSelfViewMode(); - OnCallStatusChange(newCallItem); - } - - } - - } - - // Check for Room Preset data (comes in partial, so we need to handle these responses differently to prevent appending duplicate items - var tempPresets = tempCodecStatus.Status.RoomPreset; - - if (tempPresets.Count > 0) - { - // Create temporary list to store the existing items from the CiscoCodecStatus.RoomPreset collection - List existingRoomPresets = new List(); - // Add the existing items to the temporary list - existingRoomPresets.AddRange(CodecStatus.Status.RoomPreset); - // Populate the CodecStatus object (this will append new values to the RoomPreset collection - JsonConvert.PopulateObject(response, CodecStatus); - - JObject jResponse = JObject.Parse(response); - - IList roomPresets = jResponse["Status"]["RoomPreset"].Children().ToList(); - // Iterate the new items in this response agains the temporary list. Overwrite any existing items and add new ones. - foreach (var preset in tempPresets) - { - // First fine the existing preset that matches the id - var existingPreset = existingRoomPresets.FirstOrDefault(p => p.id.Equals(preset.id)); - if (existingPreset != null) - { - Debug.Console(1, this, "Existing Room Preset with ID: {0} found. Updating.", existingPreset.id); - - JToken updatedPreset = null; - - // Find the JToken from the response with the matching id - foreach (var jPreset in roomPresets) - { - if (jPreset["id"].Value() == existingPreset.id) - updatedPreset = jPreset; - } - - if (updatedPreset != null) - { - // use PopulateObject to overlay the partial data onto the existing object - JsonConvert.PopulateObject(updatedPreset.ToString(), existingPreset); - } - - } - else - { - Debug.Console(1, this, "New Room Preset with ID: {0}. Adding.", preset.id); - existingRoomPresets.Add(preset); - } - } - - // Replace the list in the CodecStatus object with the processed list - CodecStatus.Status.RoomPreset = existingRoomPresets; - - // Generecise the list - NearEndPresets = RoomPresets.GetGenericPresets(CodecStatus.Status.RoomPreset); - - var handler = CodecRoomPresetsListHasChanged; - if (handler != null) - { - handler(this, new EventArgs()); - } - } - else - { - JsonConvert.PopulateObject(response, CodecStatus); - } - - if (!SyncState.InitialStatusMessageWasReceived) - { - SyncState.InitialStatusMessageReceived(); - - if (!SyncState.InitialConfigurationMessageWasReceived) - SendText("xConfiguration"); - } - } - else if (response.IndexOf("\"Configuration\":{") > -1 || response.IndexOf("\"Configuration\": {") > -1) - { - // Configuration Message - - JsonConvert.PopulateObject(response, CodecConfiguration); - - if (!SyncState.InitialConfigurationMessageWasReceived) - { - SyncState.InitialConfigurationMessageReceived(); - if (!SyncState.FeedbackWasRegistered) - { - SendText(CliFeedbackRegistrationExpression); - } - } - - } - else if (response.IndexOf("\"Event\":{") > -1 || response.IndexOf("\"Event\": {") > -1) + if (response.IndexOf("\"Status\":{") > -1 || response.IndexOf("\"Status\": {") > -1) { - if (response.IndexOf("\"CallDisconnect\":{") > -1 || response.IndexOf("\"CallDisconnect\": {") > -1) - { - CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); - - JsonConvert.PopulateObject(response, eventReceived); - - EvalutateDisconnectEvent(eventReceived); + // Status Message + + // Temp object so we can inpsect for call data before simply deserializing + CiscoCodecStatus.RootObject tempCodecStatus = new CiscoCodecStatus.RootObject(); + + JsonConvert.PopulateObject(response, tempCodecStatus); + + // Check to see if the message contains /Status/Conference/Presentation/LocalInstance and extract source value + var conference = tempCodecStatus.Status.Conference; + + if (conference.Presentation.LocalInstance.Count > 0) + { + if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) + PresentationSource = 0; + else if (conference.Presentation.LocalInstance[0].Source != null) + { + PresentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; + } + } + + // Check to see if this is a call status message received after the initial status message + if (tempCodecStatus.Status.Call.Count > 0) + { + // Iterate through the call objects in the response + foreach (CiscoCodecStatus.Call call in tempCodecStatus.Status.Call) + { + var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(call.id)); + + if (tempActiveCall != null) + { + bool changeDetected = false; + + eCodecCallStatus newStatus = eCodecCallStatus.Unknown; + + // Update properties of ActiveCallItem + if(call.Status != null) + if (!string.IsNullOrEmpty(call.Status.Value)) + { + tempActiveCall.Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value); + + if (newStatus == eCodecCallStatus.Connected) + GetCallHistory(); + + changeDetected = true; + } + if (call.CallType != null) + if (!string.IsNullOrEmpty(call.CallType.Value)) + { + tempActiveCall.Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value); + changeDetected = true; + } + if (call.DisplayName != null) + if (!string.IsNullOrEmpty(call.DisplayName.Value)) + { + tempActiveCall.Name = call.DisplayName.Value; + changeDetected = true; + } + if (call.Direction != null) + { + if (!string.IsNullOrEmpty(call.Direction.Value)) + { + tempActiveCall.Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value); + changeDetected = true; + } + } + + if (changeDetected) + { + SetSelfViewMode(); + OnCallStatusChange(tempActiveCall); + ListCalls(); + } + } + else if( call.ghost == null ) // if the ghost value is present the call has ended already + { + // Create a new call item + var newCallItem = new CodecActiveCallItem() + { + Id = call.id, + Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value), + Name = call.DisplayName.Value, + Number = call.RemoteNumber.Value, + Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value), + Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value) + }; + + // Add it to the ActiveCalls List + ActiveCalls.Add(newCallItem); + + ListCalls(); + + SetSelfViewMode(); + OnCallStatusChange(newCallItem); + } + + } + + } + + // Check for Room Preset data (comes in partial, so we need to handle these responses differently to prevent appending duplicate items + var tempPresets = tempCodecStatus.Status.RoomPreset; + + if (tempPresets.Count > 0) + { + // Create temporary list to store the existing items from the CiscoCodecStatus.RoomPreset collection + List existingRoomPresets = new List(); + // Add the existing items to the temporary list + existingRoomPresets.AddRange(CodecStatus.Status.RoomPreset); + // Populate the CodecStatus object (this will append new values to the RoomPreset collection + JsonConvert.PopulateObject(response, CodecStatus); + + JObject jResponse = JObject.Parse(response); + + IList roomPresets = jResponse["Status"]["RoomPreset"].Children().ToList(); + // Iterate the new items in this response agains the temporary list. Overwrite any existing items and add new ones. + foreach (var preset in tempPresets) + { + // First fine the existing preset that matches the id + var existingPreset = existingRoomPresets.FirstOrDefault(p => p.id.Equals(preset.id)); + if (existingPreset != null) + { + Debug.Console(1, this, "Existing Room Preset with ID: {0} found. Updating.", existingPreset.id); + + JToken updatedPreset = null; + + // Find the JToken from the response with the matching id + foreach (var jPreset in roomPresets) + { + if (jPreset["id"].Value() == existingPreset.id) + updatedPreset = jPreset; + } + + if (updatedPreset != null) + { + // use PopulateObject to overlay the partial data onto the existing object + JsonConvert.PopulateObject(updatedPreset.ToString(), existingPreset); + } + + } + else + { + Debug.Console(1, this, "New Room Preset with ID: {0}. Adding.", preset.id); + existingRoomPresets.Add(preset); + } + } + + // Replace the list in the CodecStatus object with the processed list + CodecStatus.Status.RoomPreset = existingRoomPresets; + + // Generecise the list + NearEndPresets = RoomPresets.GetGenericPresets(CodecStatus.Status.RoomPreset); + + var handler = CodecRoomPresetsListHasChanged; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + else + { + JsonConvert.PopulateObject(response, CodecStatus); + } + + if (!SyncState.InitialStatusMessageWasReceived) + { + SyncState.InitialStatusMessageReceived(); + + if (!SyncState.InitialConfigurationMessageWasReceived) + SendText("xConfiguration"); } - else if (response.IndexOf("\"Bookings\":{") > -1 || response.IndexOf("\"Bookings\": {") > -1) // The list has changed, reload it - { - GetBookings(null); - } } - else if (response.IndexOf("\"CommandResponse\":{") > -1 || response.IndexOf("\"CommandResponse\": {") > -1) - { + else if (response.IndexOf("\"Configuration\":{") > -1 || response.IndexOf("\"Configuration\": {") > -1) + { + // Configuration Message + + JsonConvert.PopulateObject(response, CodecConfiguration); + + if (!SyncState.InitialConfigurationMessageWasReceived) + { + SyncState.InitialConfigurationMessageReceived(); + if (!SyncState.FeedbackWasRegistered) + { + SendText(CliFeedbackRegistrationExpression); + } + } + + } + else if (response.IndexOf("\"Event\":{") > -1 || response.IndexOf("\"Event\": {") > -1) + { + if (response.IndexOf("\"CallDisconnect\":{") > -1 || response.IndexOf("\"CallDisconnect\": {") > -1) + { + CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); + + JsonConvert.PopulateObject(response, eventReceived); + + EvalutateDisconnectEvent(eventReceived); + } + else if (response.IndexOf("\"Bookings\":{") > -1 || response.IndexOf("\"Bookings\": {") > -1) // The list has changed, reload it + { + GetBookings(null); + } + } + else if (response.IndexOf("\"CommandResponse\":{") > -1 || response.IndexOf("\"CommandResponse\": {") > -1) + { // CommandResponse Message - if (response.IndexOf("\"CallHistoryRecentsResult\":{") > -1 || response.IndexOf("\"CallHistoryRecentsResult\": {") > -1) - { - var codecCallHistory = new CiscoCallHistory.RootObject(); - - JsonConvert.PopulateObject(response, codecCallHistory); - - CallHistory.ConvertCiscoCallHistoryToGeneric(codecCallHistory.CommandResponse.CallHistoryRecentsResult.Entry); + if (response.IndexOf("\"CallHistoryRecentsResult\":{") > -1 || response.IndexOf("\"CallHistoryRecentsResult\": {") > -1) + { + var codecCallHistory = new CiscoCallHistory.RootObject(); + + JsonConvert.PopulateObject(response, codecCallHistory); + + CallHistory.ConvertCiscoCallHistoryToGeneric(codecCallHistory.CommandResponse.CallHistoryRecentsResult.Entry); } - else if (response.IndexOf("\"CallHistoryDeleteEntryResult\":{") > -1 || response.IndexOf("\"CallHistoryDeleteEntryResult\": {") > -1) - { - GetCallHistory(); + else if (response.IndexOf("\"CallHistoryDeleteEntryResult\":{") > -1 || response.IndexOf("\"CallHistoryDeleteEntryResult\": {") > -1) + { + GetCallHistory(); } - else if (response.IndexOf("\"PhonebookSearchResult\":{") > -1 || response.IndexOf("\"PhonebookSearchResult\": {") > -1) - { - var codecPhonebookResponse = new CiscoCodecPhonebook.RootObject(); - - JsonConvert.PopulateObject(response, codecPhonebookResponse); - - if (!PhonebookSyncState.InitialPhonebookFoldersWasReceived) - { - // Check if the phonebook has any folders - PhonebookSyncState.InitialPhonebookFoldersReceived(); - - PhonebookSyncState.SetPhonebookHasFolders(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.Folder.Count > 0); - - if (PhonebookSyncState.PhonebookHasFolders) - { - DirectoryRoot.AddFoldersToDirectory(CiscoCodecPhonebook.GetRootFoldersFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); - } - - // Get the number of contacts in the phonebook - GetPhonebookContacts(); - } - else if (!PhonebookSyncState.NumberOfContactsWasReceived) - { - // Store the total number of contacts in the phonebook - PhonebookSyncState.SetNumberOfContacts(Int32.Parse(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value)); - - DirectoryRoot.AddContactsToDirectory(CiscoCodecPhonebook.GetRootContactsFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); - - PhonebookSyncState.PhonebookRootEntriesReceived(); - - PrintDirectory(DirectoryRoot); - } - else if (PhonebookSyncState.InitialSyncComplete) - { - var directoryResults = new CodecDirectory(); - - if(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value != "0") - directoryResults = CiscoCodecPhonebook.ConvertCiscoPhonebookToGeneric(codecPhonebookResponse.CommandResponse.PhonebookSearchResult); - - PrintDirectory(directoryResults); - - DirectoryBrowseHistory.Add(directoryResults); - - OnDirectoryResultReturned(directoryResults); - - } - } - else if (response.IndexOf("\"BookingsListResult\":{") > -1) - { - var codecBookings = new CiscoCodecBookings.RootObject(); - - JsonConvert.PopulateObject(response, codecBookings); - - if(codecBookings.CommandResponse.BookingsListResult.ResultInfo.TotalRows.Value != "0") - CodecSchedule.Meetings = CiscoCodecBookings.GetGenericMeetingsFromBookingResult(codecBookings.CommandResponse.BookingsListResult.Booking); - - BookingsRefreshTimer.Reset(900000, 900000); - } - - } - - } - catch (Exception ex) - { - Debug.Console(1, this, "Error Deserializing feedback from codec: {0}", ex); - } - } - - /// - /// Call when directory results are updated - /// - /// - void OnDirectoryResultReturned(CodecDirectory result) - { - CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); - - // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology - var handler = DirectoryResultReturned; - if (handler != null) - { - handler(this, new DirectoryEventArgs() - { - Directory = result, - DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue - }); - } - - PrintDirectory(result); - } - - /// - /// Evaluates an event received from the codec - /// - /// - void EvalutateDisconnectEvent(CiscoCodecEvents.RootObject eventReceived) - { - if (eventReceived.Event.CallDisconnect != null) - { - var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(eventReceived.Event.CallDisconnect.CallId.Value)); - - // Remove the call from the Active calls list - if (tempActiveCall != null) - { - ActiveCalls.Remove(tempActiveCall); - - ListCalls(); - - SetSelfViewMode(); - // Notify of the call disconnection - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, tempActiveCall); - - GetCallHistory(); - } - } - } - - /// - /// - /// - /// - public override void ExecuteSwitch(object selector) - { - (selector as Action)(); - PresentationSourceKey = selector.ToString(); - } - - /// - /// This is necessary for devices that are "routers" in the middle of the path, even though it only has one output and - /// may only have one input. - /// - public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) - { - ExecuteSwitch(inputSelector); - PresentationSourceKey = inputSelector.ToString(); - } - - - /// - /// Gets the ID of the last connected call - /// - /// - public string GetCallId() - { - string callId = null; - - if (ActiveCalls.Count > 1) - { - var lastCallIndex = ActiveCalls.Count - 1; - callId = ActiveCalls[lastCallIndex].Id; - } - else if (ActiveCalls.Count == 1) - callId = ActiveCalls[0].Id; - - return callId; - - } - - public void GetCallHistory() - { - SendText("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); - } - - /// - /// Required for IHasScheduleAwareness - /// - public void GetSchedule() - { - GetBookings(null); - } - - /// - /// Gets the bookings for today - /// - /// - public void GetBookings(object command) - { - Debug.Console(1, this, "Retrieving Booking Info from Codec. Current Time: {0}", DateTime.Now.ToLocalTime()); - - SendText("xCommand Bookings List Days: 1 DayOffset: 0"); - } - - /// - /// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook - /// - /// - public void CheckCurrentHour(object o) - { - if (DateTime.Now.Hour == 2) - { - Debug.Console(1, this, "Checking hour to see if phonebook should be downloaded. Current hour is {0}", DateTime.Now.Hour); - - GetPhonebook(null); - PhonebookRefreshTimer.Reset(3600000, 3600000); - } - } - - /// - /// Triggers a refresh of the codec phonebook - /// - /// Just to allow this method to be called from a console command - public void GetPhonebook(string command) - { - PhonebookSyncState.CodecDisconnected(); - - DirectoryRoot = new CodecDirectory(); - - GetPhonebookFolders(); - } - - private void GetPhonebookFolders() - { - // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", PhonebookMode)); - } - - private void GetPhonebookContacts() - { - // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// Searches the codec phonebook for all contacts matching the search string - /// - /// - public void SearchDirectory(string searchString) - { - SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// // Get contents of a specific folder in the phonebook - /// - /// - public void GetDirectoryFolderContents(string folderId) - { - SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level - /// - /// - public void GetDirectoryParentFolderContents() - { - var currentDirectory = new CodecDirectory(); - - if (DirectoryBrowseHistory.Count > 0) - { - var lastItemIndex = DirectoryBrowseHistory.Count - 1; - var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex]; - - DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]); - - currentDirectory = parentDirectoryContents; - - } - else - { - currentDirectory = DirectoryRoot; - } - - OnDirectoryResultReturned(currentDirectory); - } - - /// - /// Clears the session browse history and fires the event with the directory root - /// - public void SetCurrentDirectoryToRoot() - { - DirectoryBrowseHistory.Clear(); - - OnDirectoryResultReturned(DirectoryRoot); - } - - /// - /// Prints the directory to console - /// - /// - void PrintDirectory(CodecDirectory directory) - { - if (Debug.Level > 0) - { - Debug.Console(1, this, "Directory Results:\n"); - - foreach (DirectoryItem item in directory.CurrentDirectoryResults) - { - if (item is DirectoryFolder) - { - Debug.Console(1, this, "[+] {0}", item.Name); - } - else if (item is DirectoryContact) - { - Debug.Console(1, this, "{0}", item.Name); - } - } - Debug.Console(1, this, "Directory is on Root Level: {0}", !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); - } - - } - - /// - /// Simple dial method - /// - /// - public override void Dial(string number) - { - SendText(string.Format("xCommand Dial Number: \"{0}\"", number)); - } - - /// - /// Dials a specific meeting - /// - /// - public override void Dial(Meeting meeting) - { - foreach (Call c in meeting.Calls) - { - Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id); - } - } - - /// - /// Detailed dial method - /// - /// - /// - /// - /// - /// - public void Dial(string number, string protocol, string callRate, string callType, string meetingId) - { - SendText(string.Format("xCommand Dial Number: \"{0}\" Protocol: {1} CallRate: {2} CallType: {3} BookingId: {4}", number, protocol, callRate, callType, meetingId)); - } - - public override void EndCall(CodecActiveCallItem activeCall) - { - SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); - } - - public override void EndAllCalls() - { - foreach (CodecActiveCallItem activeCall in ActiveCalls) - { - SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); - } - } - - public override void AcceptCall(CodecActiveCallItem item) - { - SendText("xCommand Call Accept"); - } - - public override void RejectCall(CodecActiveCallItem item) - { - SendText("xCommand Call Reject"); - } - - public override void SendDtmf(string s) - { - if (CallFavorites != null) - { - SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s)); - } - } - - public void SelectPresentationSource(int source) - { - PresentationSource = source; - - StartSharing(); - } - - /// - /// Select source 1 as the presetnation source - /// - public void SelectPresentationSource1() - { - SelectPresentationSource(2); - } - - /// - /// Select source 2 as the presetnation source - /// - public void SelectPresentationSource2() - { - SelectPresentationSource(3); - } - - /// - /// Starts presentation sharing - /// - public override void StartSharing() - { - string sendingMode = string.Empty; - - if (IsInCall) - sendingMode = "LocalRemote"; - else - sendingMode = "LocalOnly"; - - if(PresentationSource > 0) - SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); - } - - /// - /// Stops sharing the current presentation - /// - public override void StopSharing() - { - PresentationSource = 0; - - SendText("xCommand Presentation Stop"); - } - - public override void PrivacyModeOn() - { - SendText("xCommand Audio Microphones Mute"); - } - - public override void PrivacyModeOff() - { - SendText("xCommand Audio Microphones Unmute"); - } - - public override void PrivacyModeToggle() - { - SendText("xCommand Audio Microphones ToggleMute"); - } - - public override void MuteOff() - { - SendText("xCommand Audio Volume Unmute"); - } - - public override void MuteOn() - { - SendText("xCommand Audio Volume Mute"); - } - - public override void MuteToggle() - { - SendText("xCommand Audio Volume ToggleMute"); - } - - /// - /// Increments the voluem - /// - /// - public override void VolumeUp(bool pressRelease) - { - SendText("xCommand Audio Volume Increase"); - } - - /// - /// Decrements the volume - /// - /// - public override void VolumeDown(bool pressRelease) - { - SendText("xCommand Audio Volume Decrease"); - } - - /// - /// Scales the level and sets the codec to the specified level within its range - /// - /// level from slider (0-65535 range) - public override void SetVolume(ushort level) - { - var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); - SendText(string.Format("xCommand Audio Volume Set Level: {0}", scaledLevel)); - } - - /// - /// Recalls the default volume on the codec - /// - public void VolumeSetToDefault() - { - SendText("xCommand Audio Volume SetToDefault"); - } - - /// - /// Puts the codec in standby mode - /// - public override void StandbyActivate() - { - SendText("xCommand Standby Activate"); - } - - /// - /// Wakes the codec from standby - /// - public override void StandbyDeactivate() - { - SendText("xCommand Standby Deactivate"); - } - - /// - /// Reboots the codec - /// - public void Reboot() - { - SendText("xCommand SystemUnit Boot Action: Restart"); - } - - /// - /// Sets SelfView Mode based on config - /// - void SetSelfViewMode() - { - if (!IsInCall) - { - SelfViewModeOff(); - } - else - { - if (ShowSelfViewByDefault) - SelfViewModeOn(); - else - SelfViewModeOff(); - } - } - - /// - /// Turns on Selfview Mode - /// - public void SelfViewModeOn() - { - SendText("xCommand Video Selfview Set Mode: On"); - } - - /// - /// Turns off Selfview Mode - /// - public void SelfViewModeOff() - { - SendText("xCommand Video Selfview Set Mode: Off"); - } - - /// - /// Toggles Selfview mode on/off - /// - public void SelfViewModeToggle() - { - string mode = string.Empty; - - if (CodecStatus.Status.Video.Selfview.Mode.BoolValue) - mode = "Off"; - else - mode = "On"; - - SendText(string.Format("xCommand Video Selfview Set Mode: {0}", mode)); - } - - /// - /// Sets a specified position for the selfview PIP window - /// - /// - public void SelfviewPipPositionSet(CodecCommandWithLabel position) - { - SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command)); - } - - /// - /// Toggles to the next selfview PIP position - /// - public void SelfviewPipPositionToggle() - { - if (CurrentSelfviewPipPosition != null) - { - var nextPipPositionIndex = SelfviewPipPositions.IndexOf(CurrentSelfviewPipPosition) + 1; - - if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list - nextPipPositionIndex = 0; - - SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); - } - } - - /// - /// Sets a specific local layout - /// - /// - public void LocalLayoutSet(CodecCommandWithLabel layout) - { - SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command)); - } - - /// - /// Toggles to the next local layout - /// - public void LocalLayoutToggle() - { - if(CurrentLocalLayout != null) - { - var nextLocalLayoutIndex = LocalLayouts.IndexOf(CurrentLocalLayout) + 1; - - if (nextLocalLayoutIndex >= LocalLayouts.Count) // Check if we need to loop back to the first item in the list - nextLocalLayoutIndex = 0; - - LocalLayoutSet(LocalLayouts[nextLocalLayoutIndex]); - } - } - - /// - /// Toggles between single/prominent layouts - /// - public void LocalLayoutToggleSingleProminent() - { - if (CurrentLocalLayout != null) - { - if (CurrentLocalLayout.Label != "Prominent") - LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); - else - LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); - } - - } - - /// - /// - /// - public void MinMaxLayoutToggle() - { - if (PresentationViewMaximizedFeedback.BoolValue) - CurrentPresentationView = "Minimized"; - else - CurrentPresentationView = "Maximized"; - - SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView)); - PresentationViewMaximizedFeedback.FireUpdate(); - } - - /// - /// Calculates the current selfview PIP position - /// - void ComputeSelfviewPipStatus() - { - CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); - - if(CurrentSelfviewPipPosition != null) - SelfviewIsOnFeedback.FireUpdate(); - } - - /// - /// Calculates the current local Layout - /// - void ComputeLocalLayout() - { - CurrentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); - - if (CurrentLocalLayout != null) - LocalLayoutFeedback.FireUpdate(); - } - - public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) - { - SendText(string.Format("xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", entry.OccurrenceHistoryId)); - } - - #region IHasCameraSpeakerTrack - - public void CameraAutoModeToggle() - { - if (!CameraAutoModeIsOnFeedback.BoolValue) - { - SendText("xCommand Cameras SpeakerTrack Activate"); - } - else - { - SendText("xCommand Cameras SpeakerTrack Deactivate"); - } - } - - public void CameraAutoModeOn() - { - SendText("xCommand Cameras SpeakerTrack Activate"); - } - - public void CameraAutoModeOff() - { - SendText("xCommand Cameras SpeakerTrack Deactivate"); - } - - #endregion - - /// - /// Builds the cameras List. Could later be modified to build from config data - /// - void SetUpCameras() - { - // Add the internal camera - Cameras = new List(); - - var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); - - if(CodecStatus.Status.Cameras.Camera.Count > 0) - internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); - else - // Somehow subscribe to the event on the Options.Value property and update when it changes. - - Cameras.Add(internalCamera); - - // Add the far end camera - var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); - Cameras.Add(farEndCamera); - - SelectedCameraFeedback = new StringFeedback(() => SelectedCamera.Key); - - ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); - - DeviceManager.AddDevice(internalCamera); - DeviceManager.AddDevice(farEndCamera); - - NearEndPresets = new List(15); - - FarEndRoomPresets = new List(15); - - // Add the far end presets - for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) - { - var label = string.Format("Far End Preset {0}", i); - FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); - } - - SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. - } - - #region IHasCodecCameras Members - - public event EventHandler CameraSelected; - - public List Cameras { get; private set; } - - public StringFeedback SelectedCameraFeedback { get; private set; } - - private CameraBase _selectedCamera; - - /// - /// Returns the selected camera - /// - public CameraBase SelectedCamera - { - get - { - return _selectedCamera; - } - private set - { - _selectedCamera = value; - SelectedCameraFeedback.FireUpdate(); - ControllingFarEndCameraFeedback.FireUpdate(); - - var handler = CameraSelected; - if (handler != null) - { - handler(this, new CameraSelectedEventArgs(SelectedCamera)); - } - } - } - - public void SelectCamera(string key) - { - var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); - if (camera != null) - { - Debug.Console(2, this, "Selected Camera with key: '{0}'", camera.Key); - SelectedCamera = camera; - } - else - Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); - } - - public CameraBase FarEndCamera { get; private set; } - - public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } - - #endregion - - /// - /// - /// - public class CiscoCodecInfo : VideoCodecInfo - { - public CiscoCodecStatus.RootObject CodecStatus { get; private set; } - - public CiscoCodecConfiguration.RootObject CodecConfiguration { get; private set; } - - public override bool MultiSiteOptionIsEnabled - { - get - { - if (!string.IsNullOrEmpty(CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value) && CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value.ToLower() == "true") - return true; - else - return false; - } - - } - public override string IpAddress - { - get - { - if (CodecConfiguration.Configuration.Network != null) - { - if (CodecConfiguration.Configuration.Network.Count > 0) - return CodecConfiguration.Configuration.Network[0].IPv4.Address.Value; - } - return string.Empty; - } - } - public override string E164Alias - { - get - { - if (CodecConfiguration.Configuration.H323.H323Alias.E164 != null) - return CodecConfiguration.Configuration.H323.H323Alias.E164.Value; - else - return string.Empty; - } - } - public override string H323Id - { - get - { - if (CodecConfiguration.Configuration.H323.H323Alias.ID != null) - return CodecConfiguration.Configuration.H323.H323Alias.ID.Value; - else - return string.Empty; - } - } - public override string SipPhoneNumber - { - get - { - if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.Registration.Count > 0) - { - var match = Regex.Match(CodecStatus.Status.SIP.Registration[0].URI.Value, @"(\d+)"); // extract numbers only - if (match.Success) - { - Debug.Console(1, "Extracted phone number as '{0}' from string '{1}'", match.Groups[1].Value, CodecStatus.Status.SIP.Registration[0].URI.Value); - return match.Groups[1].Value; - } - else - { - Debug.Console(1, "Unable to extract phone number from string: '{0}'", CodecStatus.Status.SIP.Registration[0].URI.Value); - return string.Empty; - } - } - else - { - Debug.Console(1, "Unable to extract phone number. No SIP Registration items found"); - return string.Empty; - } - } + else if (response.IndexOf("\"PhonebookSearchResult\":{") > -1 || response.IndexOf("\"PhonebookSearchResult\": {") > -1) + { + var codecPhonebookResponse = new CiscoCodecPhonebook.RootObject(); + + JsonConvert.PopulateObject(response, codecPhonebookResponse); + + if (!PhonebookSyncState.InitialPhonebookFoldersWasReceived) + { + // Check if the phonebook has any folders + PhonebookSyncState.InitialPhonebookFoldersReceived(); + + PhonebookSyncState.SetPhonebookHasFolders(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.Folder.Count > 0); + + if (PhonebookSyncState.PhonebookHasFolders) + { + DirectoryRoot.AddFoldersToDirectory(CiscoCodecPhonebook.GetRootFoldersFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); + } + + // Get the number of contacts in the phonebook + GetPhonebookContacts(); + } + else if (!PhonebookSyncState.NumberOfContactsWasReceived) + { + // Store the total number of contacts in the phonebook + PhonebookSyncState.SetNumberOfContacts(Int32.Parse(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value)); + + DirectoryRoot.AddContactsToDirectory(CiscoCodecPhonebook.GetRootContactsFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); + + PhonebookSyncState.PhonebookRootEntriesReceived(); + + PrintDirectory(DirectoryRoot); + } + else if (PhonebookSyncState.InitialSyncComplete) + { + var directoryResults = new CodecDirectory(); + + if(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value != "0") + directoryResults = CiscoCodecPhonebook.ConvertCiscoPhonebookToGeneric(codecPhonebookResponse.CommandResponse.PhonebookSearchResult); + + PrintDirectory(directoryResults); + + DirectoryBrowseHistory.Add(directoryResults); + + OnDirectoryResultReturned(directoryResults); + + } + } + else if (response.IndexOf("\"BookingsListResult\":{") > -1) + { + var codecBookings = new CiscoCodecBookings.RootObject(); + + JsonConvert.PopulateObject(response, codecBookings); + + if(codecBookings.CommandResponse.BookingsListResult.ResultInfo.TotalRows.Value != "0") + CodecSchedule.Meetings = CiscoCodecBookings.GetGenericMeetingsFromBookingResult(codecBookings.CommandResponse.BookingsListResult.Booking); + + BookingsRefreshTimer.Reset(900000, 900000); + } + + } + + } + catch (Exception ex) + { + Debug.Console(1, this, "Error Deserializing feedback from codec: {0}", ex); + } + } + + /// + /// Call when directory results are updated + /// + /// + void OnDirectoryResultReturned(CodecDirectory result) + { + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology + var handler = DirectoryResultReturned; + if (handler != null) + { + handler(this, new DirectoryEventArgs() + { + Directory = result, + DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue + }); + } + + PrintDirectory(result); + } + + /// + /// Evaluates an event received from the codec + /// + /// + void EvalutateDisconnectEvent(CiscoCodecEvents.RootObject eventReceived) + { + if (eventReceived.Event.CallDisconnect != null) + { + var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(eventReceived.Event.CallDisconnect.CallId.Value)); + + // Remove the call from the Active calls list + if (tempActiveCall != null) + { + ActiveCalls.Remove(tempActiveCall); + + ListCalls(); + + SetSelfViewMode(); + // Notify of the call disconnection + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, tempActiveCall); + + GetCallHistory(); + } + } + } + + /// + /// + /// + /// + public override void ExecuteSwitch(object selector) + { + (selector as Action)(); + PresentationSourceKey = selector.ToString(); + } + + /// + /// This is necessary for devices that are "routers" in the middle of the path, even though it only has one output and + /// may only have one input. + /// + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + ExecuteSwitch(inputSelector); + PresentationSourceKey = inputSelector.ToString(); + } + + + /// + /// Gets the ID of the last connected call + /// + /// + public string GetCallId() + { + string callId = null; + + if (ActiveCalls.Count > 1) + { + var lastCallIndex = ActiveCalls.Count - 1; + callId = ActiveCalls[lastCallIndex].Id; + } + else if (ActiveCalls.Count == 1) + callId = ActiveCalls[0].Id; + + return callId; + + } + + public void GetCallHistory() + { + SendText("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); + } + + /// + /// Required for IHasScheduleAwareness + /// + public void GetSchedule() + { + GetBookings(null); + } + + /// + /// Gets the bookings for today + /// + /// + public void GetBookings(object command) + { + Debug.Console(1, this, "Retrieving Booking Info from Codec. Current Time: {0}", DateTime.Now.ToLocalTime()); + + SendText("xCommand Bookings List Days: 1 DayOffset: 0"); + } + + /// + /// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook + /// + /// + public void CheckCurrentHour(object o) + { + if (DateTime.Now.Hour == 2) + { + Debug.Console(1, this, "Checking hour to see if phonebook should be downloaded. Current hour is {0}", DateTime.Now.Hour); + + GetPhonebook(null); + PhonebookRefreshTimer.Reset(3600000, 3600000); + } + } + + /// + /// Triggers a refresh of the codec phonebook + /// + /// Just to allow this method to be called from a console command + public void GetPhonebook(string command) + { + PhonebookSyncState.CodecDisconnected(); + + DirectoryRoot = new CodecDirectory(); + + GetPhonebookFolders(); + } + + private void GetPhonebookFolders() + { + // Get Phonebook Folders (determine local/corporate from config, and set results limit) + SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", PhonebookMode)); + } + + private void GetPhonebookContacts() + { + // Get Phonebook Folders (determine local/corporate from config, and set results limit) + SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", PhonebookMode, PhonebookResultsLimit)); + } + + /// + /// Searches the codec phonebook for all contacts matching the search string + /// + /// + public void SearchDirectory(string searchString) + { + SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit)); + } + + /// + /// // Get contents of a specific folder in the phonebook + /// + /// + public void GetDirectoryFolderContents(string folderId) + { + SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit)); + } + + /// + /// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level + /// + /// + public void GetDirectoryParentFolderContents() + { + var currentDirectory = new CodecDirectory(); + + if (DirectoryBrowseHistory.Count > 0) + { + var lastItemIndex = DirectoryBrowseHistory.Count - 1; + var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex]; + + DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]); + + currentDirectory = parentDirectoryContents; + + } + else + { + currentDirectory = DirectoryRoot; + } + + OnDirectoryResultReturned(currentDirectory); + } + + /// + /// Clears the session browse history and fires the event with the directory root + /// + public void SetCurrentDirectoryToRoot() + { + DirectoryBrowseHistory.Clear(); + + OnDirectoryResultReturned(DirectoryRoot); + } + + /// + /// Prints the directory to console + /// + /// + void PrintDirectory(CodecDirectory directory) + { + if (Debug.Level > 0) + { + Debug.Console(1, this, "Directory Results:\n"); + + foreach (DirectoryItem item in directory.CurrentDirectoryResults) + { + if (item is DirectoryFolder) + { + Debug.Console(1, this, "[+] {0}", item.Name); + } + else if (item is DirectoryContact) + { + Debug.Console(1, this, "{0}", item.Name); + } + } + Debug.Console(1, this, "Directory is on Root Level: {0}", !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); + } + + } + + /// + /// Simple dial method + /// + /// + public override void Dial(string number) + { + SendText(string.Format("xCommand Dial Number: \"{0}\"", number)); + } + + /// + /// Dials a specific meeting + /// + /// + public override void Dial(Meeting meeting) + { + foreach (Call c in meeting.Calls) + { + Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id); + } + } + + /// + /// Detailed dial method + /// + /// + /// + /// + /// + /// + public void Dial(string number, string protocol, string callRate, string callType, string meetingId) + { + SendText(string.Format("xCommand Dial Number: \"{0}\" Protocol: {1} CallRate: {2} CallType: {3} BookingId: {4}", number, protocol, callRate, callType, meetingId)); + } + + public override void EndCall(CodecActiveCallItem activeCall) + { + SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); + } + + public override void EndAllCalls() + { + foreach (CodecActiveCallItem activeCall in ActiveCalls) + { + SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); + } + } + + public override void AcceptCall(CodecActiveCallItem item) + { + SendText("xCommand Call Accept"); + } + + public override void RejectCall(CodecActiveCallItem item) + { + SendText("xCommand Call Reject"); + } + + public override void SendDtmf(string s) + { + if (CallFavorites != null) + { + SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s)); + } + } + + public void SelectPresentationSource(int source) + { + PresentationSource = source; + + StartSharing(); + } + + /// + /// Select source 1 as the presetnation source + /// + public void SelectPresentationSource1() + { + SelectPresentationSource(2); + } + + /// + /// Select source 2 as the presetnation source + /// + public void SelectPresentationSource2() + { + SelectPresentationSource(3); + } + + /// + /// Starts presentation sharing + /// + public override void StartSharing() + { + string sendingMode = string.Empty; + + if (IsInCall) + sendingMode = "LocalRemote"; + else + sendingMode = "LocalOnly"; + + if(PresentationSource > 0) + SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); + } + + /// + /// Stops sharing the current presentation + /// + public override void StopSharing() + { + PresentationSource = 0; + + SendText("xCommand Presentation Stop"); + } + + public override void PrivacyModeOn() + { + SendText("xCommand Audio Microphones Mute"); + } + + public override void PrivacyModeOff() + { + SendText("xCommand Audio Microphones Unmute"); + } + + public override void PrivacyModeToggle() + { + SendText("xCommand Audio Microphones ToggleMute"); + } + + public override void MuteOff() + { + SendText("xCommand Audio Volume Unmute"); + } + + public override void MuteOn() + { + SendText("xCommand Audio Volume Mute"); + } + + public override void MuteToggle() + { + SendText("xCommand Audio Volume ToggleMute"); + } + + /// + /// Increments the voluem + /// + /// + public override void VolumeUp(bool pressRelease) + { + SendText("xCommand Audio Volume Increase"); + } + + /// + /// Decrements the volume + /// + /// + public override void VolumeDown(bool pressRelease) + { + SendText("xCommand Audio Volume Decrease"); + } + + /// + /// Scales the level and sets the codec to the specified level within its range + /// + /// level from slider (0-65535 range) + public override void SetVolume(ushort level) + { + var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); + SendText(string.Format("xCommand Audio Volume Set Level: {0}", scaledLevel)); + } + + /// + /// Recalls the default volume on the codec + /// + public void VolumeSetToDefault() + { + SendText("xCommand Audio Volume SetToDefault"); + } + + /// + /// Puts the codec in standby mode + /// + public override void StandbyActivate() + { + SendText("xCommand Standby Activate"); + } + + /// + /// Wakes the codec from standby + /// + public override void StandbyDeactivate() + { + SendText("xCommand Standby Deactivate"); + } + + /// + /// Reboots the codec + /// + public void Reboot() + { + SendText("xCommand SystemUnit Boot Action: Restart"); + } + + /// + /// Sets SelfView Mode based on config + /// + void SetSelfViewMode() + { + if (!IsInCall) + { + SelfViewModeOff(); + } + else + { + if (ShowSelfViewByDefault) + SelfViewModeOn(); + else + SelfViewModeOff(); + } + } + + /// + /// Turns on Selfview Mode + /// + public void SelfViewModeOn() + { + SendText("xCommand Video Selfview Set Mode: On"); + } + + /// + /// Turns off Selfview Mode + /// + public void SelfViewModeOff() + { + SendText("xCommand Video Selfview Set Mode: Off"); + } + + /// + /// Toggles Selfview mode on/off + /// + public void SelfViewModeToggle() + { + string mode = string.Empty; + + if (CodecStatus.Status.Video.Selfview.Mode.BoolValue) + mode = "Off"; + else + mode = "On"; + + SendText(string.Format("xCommand Video Selfview Set Mode: {0}", mode)); + } + + /// + /// Sets a specified position for the selfview PIP window + /// + /// + public void SelfviewPipPositionSet(CodecCommandWithLabel position) + { + SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command)); + } + + /// + /// Toggles to the next selfview PIP position + /// + public void SelfviewPipPositionToggle() + { + if (CurrentSelfviewPipPosition != null) + { + var nextPipPositionIndex = SelfviewPipPositions.IndexOf(CurrentSelfviewPipPosition) + 1; + + if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list + nextPipPositionIndex = 0; + + SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); + } + } + + /// + /// Sets a specific local layout + /// + /// + public void LocalLayoutSet(CodecCommandWithLabel layout) + { + SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command)); + } + + /// + /// Toggles to the next local layout + /// + public void LocalLayoutToggle() + { + if(CurrentLocalLayout != null) + { + var nextLocalLayoutIndex = LocalLayouts.IndexOf(CurrentLocalLayout) + 1; + + if (nextLocalLayoutIndex >= LocalLayouts.Count) // Check if we need to loop back to the first item in the list + nextLocalLayoutIndex = 0; + + LocalLayoutSet(LocalLayouts[nextLocalLayoutIndex]); + } + } + + /// + /// Toggles between single/prominent layouts + /// + public void LocalLayoutToggleSingleProminent() + { + if (CurrentLocalLayout != null) + { + if (CurrentLocalLayout.Label != "Prominent") + LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); + else + LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); + } + + } + + /// + /// + /// + public void MinMaxLayoutToggle() + { + if (PresentationViewMaximizedFeedback.BoolValue) + CurrentPresentationView = "Minimized"; + else + CurrentPresentationView = "Maximized"; + + SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView)); + PresentationViewMaximizedFeedback.FireUpdate(); + } + + /// + /// Calculates the current selfview PIP position + /// + void ComputeSelfviewPipStatus() + { + CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); + + if(CurrentSelfviewPipPosition != null) + SelfviewIsOnFeedback.FireUpdate(); + } + + /// + /// Calculates the current local Layout + /// + void ComputeLocalLayout() + { + CurrentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); + + if (CurrentLocalLayout != null) + LocalLayoutFeedback.FireUpdate(); + } + + public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) + { + SendText(string.Format("xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", entry.OccurrenceHistoryId)); + } + + #region IHasCameraSpeakerTrack + + public void CameraAutoModeToggle() + { + if (!CameraAutoModeIsOnFeedback.BoolValue) + { + SendText("xCommand Cameras SpeakerTrack Activate"); + } + else + { + SendText("xCommand Cameras SpeakerTrack Deactivate"); + } + } + + public void CameraAutoModeOn() + { + SendText("xCommand Cameras SpeakerTrack Activate"); + } + + public void CameraAutoModeOff() + { + SendText("xCommand Cameras SpeakerTrack Deactivate"); + } + + #endregion + + /// + /// Builds the cameras List. Could later be modified to build from config data + /// + void SetUpCameras() + { + // Add the internal camera + Cameras = new List(); + + var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); + + if(CodecStatus.Status.Cameras.Camera.Count > 0) + internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); + else + // Somehow subscribe to the event on the Options.Value property and update when it changes. + + Cameras.Add(internalCamera); + + // Add the far end camera + var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); + Cameras.Add(farEndCamera); + + SelectedCameraFeedback = new StringFeedback(() => SelectedCamera.Key); + + ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); + + DeviceManager.AddDevice(internalCamera); + DeviceManager.AddDevice(farEndCamera); + + NearEndPresets = new List(15); + + FarEndRoomPresets = new List(15); + + // Add the far end presets + for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) + { + var label = string.Format("Far End Preset {0}", i); + FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); + } + + SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. + } + + #region IHasCodecCameras Members + + public event EventHandler CameraSelected; + + public List Cameras { get; private set; } + + public StringFeedback SelectedCameraFeedback { get; private set; } + + private CameraBase _selectedCamera; + + /// + /// Returns the selected camera + /// + public CameraBase SelectedCamera + { + get + { + return _selectedCamera; + } + private set + { + _selectedCamera = value; + SelectedCameraFeedback.FireUpdate(); + ControllingFarEndCameraFeedback.FireUpdate(); + + var handler = CameraSelected; + if (handler != null) + { + handler(this, new CameraSelectedEventArgs(SelectedCamera)); + } + } + } + + public void SelectCamera(string key) + { + var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); + if (camera != null) + { + Debug.Console(2, this, "Selected Camera with key: '{0}'", camera.Key); + SelectedCamera = camera; + } + else + Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); + } + + public CameraBase FarEndCamera { get; private set; } + + public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } + + #endregion + + /// + /// + /// + public class CiscoCodecInfo : VideoCodecInfo + { + public CiscoCodecStatus.RootObject CodecStatus { get; private set; } + + public CiscoCodecConfiguration.RootObject CodecConfiguration { get; private set; } + + public override bool MultiSiteOptionIsEnabled + { + get + { + if (!string.IsNullOrEmpty(CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value) && CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value.ToLower() == "true") + return true; + else + return false; + } + + } + public override string IpAddress + { + get + { + if (CodecConfiguration.Configuration.Network != null) + { + if (CodecConfiguration.Configuration.Network.Count > 0) + return CodecConfiguration.Configuration.Network[0].IPv4.Address.Value; + } + return string.Empty; + } + } + public override string E164Alias + { + get + { + if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias.E164 != null) + { + return CodecConfiguration.Configuration.H323.H323Alias.E164.Value; + } + else + { + return string.Empty; + } + } + } + public override string H323Id + { + get + { + if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias.E164 != null) + { + return CodecConfiguration.Configuration.H323.H323Alias.ID.Value; + } + else + { + return string.Empty; + } + } + } + public override string SipPhoneNumber + { + get + { + if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.Registration.Count > 0) + { + var match = Regex.Match(CodecStatus.Status.SIP.Registration[0].URI.Value, @"(\d+)"); // extract numbers only + if (match.Success) + { + Debug.Console(1, "Extracted phone number as '{0}' from string '{1}'", match.Groups[1].Value, CodecStatus.Status.SIP.Registration[0].URI.Value); + return match.Groups[1].Value; + } + else + { + Debug.Console(1, "Unable to extract phone number from string: '{0}'", CodecStatus.Status.SIP.Registration[0].URI.Value); + return string.Empty; + } + } + else + { + Debug.Console(1, "Unable to extract phone number. No SIP Registration items found"); + return string.Empty; + } + } } public override string SipUri @@ -1698,173 +1753,173 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } } - public override bool AutoAnswerEnabled - { - get - { - if (CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value.ToLower() == "on") - return true; - else - return false; - } - } - - public CiscoCodecInfo(CiscoCodecStatus.RootObject status, CiscoCodecConfiguration.RootObject configuration) - { - CodecStatus = status; - CodecConfiguration = configuration; - } - } - - - #region IHasCameraPresets Members - - public event EventHandler CodecRoomPresetsListHasChanged; - - public List NearEndPresets { get; private set; } - - public List FarEndRoomPresets { get; private set; } - - public void CodecRoomPresetSelect(int preset) - { - Debug.Console(1, this, "Selecting Preset: {0}", preset); - if (SelectedCamera is IAmFarEndCamera) - SelectFarEndPreset(preset); - else - SendText(string.Format("xCommand RoomPreset Activate PresetId: {0}", preset)); - } - - public void CodecRoomPresetStore(int preset, string description) - { - SendText(string.Format("xCommand RoomPreset Store PresetId: {0} Description: \"{1}\" Type: All", preset, description)); - } - - #endregion - - public void SelectFarEndPreset(int preset) - { - SendText(string.Format("xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", GetCallId(), preset)); - } - - } - - /// - /// Represents a codec command that might need to have a friendly label applied for UI feedback purposes - /// - public class CodecCommandWithLabel - { - public string Command { get; set; } - public string Label { get; set; } - - public CodecCommandWithLabel(string command, string label) - { - Command = command; - Label = label; - } - } - - /// - /// Tracks the initial sycnronization state of the codec when making a connection - /// - public class CodecSyncState : IKeyed - { - bool _InitialSyncComplete; - - public event EventHandler InitialSyncCompleted; - - public string Key { get; private set; } - - public bool InitialSyncComplete - { - get { return _InitialSyncComplete; } - private set - { - if (value == true) - { - var handler = InitialSyncCompleted; - if (handler != null) - handler(this, new EventArgs()); - } - _InitialSyncComplete = value; - } - } - - public bool LoginMessageWasReceived { get; private set; } - - public bool InitialStatusMessageWasReceived { get; private set; } - - public bool InitialConfigurationMessageWasReceived { get; private set; } - - public bool FeedbackWasRegistered { get; private set; } - - public CodecSyncState(string key) - { - Key = key; - CodecDisconnected(); - } - - public void LoginMessageReceived() - { - LoginMessageWasReceived = true; - Debug.Console(1, this, "Login Message Received."); - CheckSyncStatus(); - } - - public void InitialStatusMessageReceived() - { - InitialStatusMessageWasReceived = true; - Debug.Console(1, this, "Initial Codec Status Message Received."); - CheckSyncStatus(); - } - - public void InitialConfigurationMessageReceived() - { - InitialConfigurationMessageWasReceived = true; - Debug.Console(1, this, "Initial Codec Configuration Message Received."); - CheckSyncStatus(); - } - - public void FeedbackRegistered() - { - FeedbackWasRegistered = true; - Debug.Console(1, this, "Initial Codec Feedback Registration Successful."); - CheckSyncStatus(); - } - - public void CodecDisconnected() - { - LoginMessageWasReceived = false; - InitialConfigurationMessageWasReceived = false; - InitialStatusMessageWasReceived = false; - FeedbackWasRegistered = false; - InitialSyncComplete = false; - } - - void CheckSyncStatus() - { - if (LoginMessageWasReceived && InitialConfigurationMessageWasReceived && InitialStatusMessageWasReceived && FeedbackWasRegistered) - { - InitialSyncComplete = true; - Debug.Console(1, this, "Initial Codec Sync Complete!"); - } - else - InitialSyncComplete = false; - } - } - - public class CiscoSparkCodecFactory : EssentialsDeviceFactory - { - public CiscoSparkCodecFactory() - { - TypeNames = new List() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.Console(1, "Factory Attempting to create new Cisco Codec Device"); - var comm = CommFactory.CreateCommForDevice(dc); - return new VideoCodec.Cisco.CiscoSparkCodec(dc, comm); - } - } - + public override bool AutoAnswerEnabled + { + get + { + if (CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value.ToLower() == "on") + return true; + else + return false; + } + } + + public CiscoCodecInfo(CiscoCodecStatus.RootObject status, CiscoCodecConfiguration.RootObject configuration) + { + CodecStatus = status; + CodecConfiguration = configuration; + } + } + + + #region IHasCameraPresets Members + + public event EventHandler CodecRoomPresetsListHasChanged; + + public List NearEndPresets { get; private set; } + + public List FarEndRoomPresets { get; private set; } + + public void CodecRoomPresetSelect(int preset) + { + Debug.Console(1, this, "Selecting Preset: {0}", preset); + if (SelectedCamera is IAmFarEndCamera) + SelectFarEndPreset(preset); + else + SendText(string.Format("xCommand RoomPreset Activate PresetId: {0}", preset)); + } + + public void CodecRoomPresetStore(int preset, string description) + { + SendText(string.Format("xCommand RoomPreset Store PresetId: {0} Description: \"{1}\" Type: All", preset, description)); + } + + #endregion + + public void SelectFarEndPreset(int preset) + { + SendText(string.Format("xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", GetCallId(), preset)); + } + + } + + /// + /// Represents a codec command that might need to have a friendly label applied for UI feedback purposes + /// + public class CodecCommandWithLabel + { + public string Command { get; set; } + public string Label { get; set; } + + public CodecCommandWithLabel(string command, string label) + { + Command = command; + Label = label; + } + } + + /// + /// Tracks the initial sycnronization state of the codec when making a connection + /// + public class CodecSyncState : IKeyed + { + bool _InitialSyncComplete; + + public event EventHandler InitialSyncCompleted; + + public string Key { get; private set; } + + public bool InitialSyncComplete + { + get { return _InitialSyncComplete; } + private set + { + if (value == true) + { + var handler = InitialSyncCompleted; + if (handler != null) + handler(this, new EventArgs()); + } + _InitialSyncComplete = value; + } + } + + public bool LoginMessageWasReceived { get; private set; } + + public bool InitialStatusMessageWasReceived { get; private set; } + + public bool InitialConfigurationMessageWasReceived { get; private set; } + + public bool FeedbackWasRegistered { get; private set; } + + public CodecSyncState(string key) + { + Key = key; + CodecDisconnected(); + } + + public void LoginMessageReceived() + { + LoginMessageWasReceived = true; + Debug.Console(1, this, "Login Message Received."); + CheckSyncStatus(); + } + + public void InitialStatusMessageReceived() + { + InitialStatusMessageWasReceived = true; + Debug.Console(1, this, "Initial Codec Status Message Received."); + CheckSyncStatus(); + } + + public void InitialConfigurationMessageReceived() + { + InitialConfigurationMessageWasReceived = true; + Debug.Console(1, this, "Initial Codec Configuration Message Received."); + CheckSyncStatus(); + } + + public void FeedbackRegistered() + { + FeedbackWasRegistered = true; + Debug.Console(1, this, "Initial Codec Feedback Registration Successful."); + CheckSyncStatus(); + } + + public void CodecDisconnected() + { + LoginMessageWasReceived = false; + InitialConfigurationMessageWasReceived = false; + InitialStatusMessageWasReceived = false; + FeedbackWasRegistered = false; + InitialSyncComplete = false; + } + + void CheckSyncStatus() + { + if (LoginMessageWasReceived && InitialConfigurationMessageWasReceived && InitialStatusMessageWasReceived && FeedbackWasRegistered) + { + InitialSyncComplete = true; + Debug.Console(1, this, "Initial Codec Sync Complete!"); + } + else + InitialSyncComplete = false; + } + } + + public class CiscoSparkCodecFactory : EssentialsDeviceFactory + { + public CiscoSparkCodecFactory() + { + TypeNames = new List() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new Cisco Codec Device"); + var comm = CommFactory.CreateCommForDevice(dc); + return new VideoCodec.Cisco.CiscoSparkCodec(dc, comm); + } + } + } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs index 33cda37c..521b9e5c 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs @@ -7,27 +7,41 @@ using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core; +using Newtonsoft.Json; + namespace PepperDash.Essentials.Devices.Common.Codec { public class CiscoSparkCodecPropertiesConfig { + [JsonProperty("communicationMonitorProperties")] public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; } + [JsonProperty("favorites")] public List Favorites { get; set; } /// /// Valid values: "Local" or "Corporate" /// + [JsonProperty("phonebookMode")] public string PhonebookMode { get; set; } + [JsonProperty("showSelfViewByDefault")] public bool ShowSelfViewByDefault { get; set; } + [JsonProperty("sharing")] public SharingProperties Sharing { get; set; } + /// + /// Optionsal property to set the limit of any phonebook queries for directory or searching + /// + [JsonProperty("phonebookResultsLimit")] + public uint PhonebookResultsLimit { get; set; } + } public class SharingProperties { + [JsonProperty("autoShareContentWhileInCall")] public bool AutoShareContentWhileInCall { get; set; } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs index c64bdf36..2f834c36 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro.CrestronThread; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -13,7 +14,6 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Core; using PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom @@ -171,6 +171,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // The thread responsible for dequeuing and processing the messages ReceiveThread = new Thread((o) => ProcessQueue(), null); + ReceiveThread.Priority = Thread.eThreadPriority.MediumPriority; Communication = comm; @@ -401,7 +402,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // If the receive thread has for some reason stopped, this will restart it if (ReceiveThread.ThreadState != Thread.eThreadStates.ThreadRunning) + { ReceiveThread.Start(); + } } diff --git a/essentials-framework/Essentials/PepperDashEssentials/packages.config b/essentials-framework/Essentials/PepperDashEssentials/packages.config deleted file mode 100644 index f1f7bd89..00000000 --- a/essentials-framework/Essentials/PepperDashEssentials/packages.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages.config b/packages.config deleted file mode 100644 index 42d552c6..00000000 --- a/packages.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file